From 285c6e00f1777410b7fa9028f643944b76b2f02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr?= <8machy@seznam.cz> Date: Tue, 7 Nov 2023 17:28:08 +0100 Subject: [PATCH] wip --- packages/signalizejs/ajax/src/index.ts | 25 +- .../signalizejs/asset-loader/src/index.ts | 54 ++-- packages/signalizejs/dialog/src/index.ts | 18 +- packages/signalizejs/directives/src/index.ts | 76 +++--- packages/signalizejs/h/src/index.ts | 100 ++++---- packages/signalizejs/logger/src/index.ts | 16 +- packages/signalizejs/snippets/src/index.ts | 20 +- packages/signalizejs/spa/src/index.ts | 34 +-- packages/signalizejs/src/Signalize.ts | 126 +++++----- packages/signalizejs/src/plugins/bind.ts | 15 +- packages/signalizejs/src/plugins/dispatch.ts | 6 +- packages/signalizejs/src/plugins/domReady.ts | 18 +- .../src/plugins/intersection-observer.ts | 2 +- .../signalizejs/src/plugins/is-in-viewport.ts | 10 +- .../signalizejs/src/plugins/is-visible.ts | 4 +- .../src/plugins/mutation-observer.ts | 11 +- packages/signalizejs/src/plugins/off.ts | 8 +- packages/signalizejs/src/plugins/offset.ts | 8 +- packages/signalizejs/src/plugins/on.ts | 26 +- packages/signalizejs/src/plugins/scope.ts | 238 +++++++++--------- packages/signalizejs/src/plugins/select.ts | 42 ++-- packages/signalizejs/src/plugins/signal.ts | 162 ++++++------ packages/signalizejs/src/plugins/task.ts | 4 +- 23 files changed, 512 insertions(+), 511 deletions(-) diff --git a/packages/signalizejs/ajax/src/index.ts b/packages/signalizejs/ajax/src/index.ts index dda7f81..ab4bedd 100755 --- a/packages/signalizejs/ajax/src/index.ts +++ b/packages/signalizejs/ajax/src/index.ts @@ -1,4 +1,4 @@ -import type { Signalize, CustomEventListener } from 'signalizejs'; +import type { Signalize, SignalizePlugin, CustomEventListener } from 'signalizejs'; declare module 'signalizejs' { interface Signalize { @@ -11,10 +11,6 @@ declare module 'signalizejs' { 'ajax:request:error': CustomEventListener 'ajax:request:end': CustomEventListener } - - interface SignalizeConfig { - ajaxRequestedWithHeader: string - } } export interface AjaxReturn { @@ -22,15 +18,20 @@ export interface AjaxReturn { error: any } -export interface AjaxOptions extends RequestInit { +export interface AjaxRequestOptions extends RequestInit { url: string data?: Record } -export default ($: Signalize): void => { - $.on('signalize:ready', () => { - const { config, dispatch } = $; - $.ajax = async (options: AjaxOptions): Promise => { +export interface AjaxOptions { + requestedWithHeader: string +} + +export default (pluginOptions?: AjaxOptions): SignalizePlugin => { + return ($: Signalize) => { + const { dispatch } = $; + + $.ajax = async (options: AjaxRequestOptions): Promise => { let response: Response | null = null; let error: Error | null = null @@ -54,7 +55,7 @@ export default ($: Signalize): void => { const url = options.url; const requestInit = { ...options }; - requestInit.headers = { ...{ 'X-Requested-With': config.ajaxRequestedWithHeader ?? 'XMLHttpRequest' }, ...requestInit.headers ?? {} } + requestInit.headers = { ...{ 'X-Requested-With': pluginOptions?.requestedWithHeader ?? 'XMLHttpRequest' }, ...requestInit.headers ?? {} } delete requestInit.url; @@ -87,5 +88,5 @@ export default ($: Signalize): void => { error } } - }); + } } diff --git a/packages/signalizejs/asset-loader/src/index.ts b/packages/signalizejs/asset-loader/src/index.ts index 234c8fa..d38ef48 100755 --- a/packages/signalizejs/asset-loader/src/index.ts +++ b/packages/signalizejs/asset-loader/src/index.ts @@ -1,4 +1,4 @@ -import type { Signalize, CustomEventListener } from 'signalizejs'; +import type { Signalize, SignalizePlugin, CustomEventListener } from 'signalizejs'; declare module 'signalizejs' { interface Signalize { @@ -6,38 +6,40 @@ declare module 'signalizejs' { } } -export default ($: Signalize): void => { - $.loadAssets = async (assets: Array): Promise => { - const assetsPromises: Array> = []; +export default (): SignalizePlugin => { + return ($: Signalize) => { + $.loadAssets = async (assets: Array): Promise => { + const assetsPromises: Array> = []; - for (const asset of assets) { - const isAsset = 'src' in asset; - const tagName = isAsset ? 'script' : 'link'; - const attributeName = isAsset ? 'src' : 'href'; + for (const asset of assets) { + const isAsset = 'src' in asset; + const tagName = isAsset ? 'script' : 'link'; + const attributeName = isAsset ? 'src' : 'href'; - if ($.select(`${tagName}[${attributeName}="${asset[attributeName]}"]`) !== null) { - continue; - } - - const assetElement = document.createElement(tagName); + if ($.select(`${tagName}[${attributeName}="${asset[attributeName]}"]`) !== null) { + continue; + } - for (const [key, value] of Object.entries(asset)) { - assetElement.setAttribute(key, value) - } + const assetElement = document.createElement(tagName); - assetsPromises.push(new Promise((resolve, reject) => { - assetElement.onload = () => { - resolve({ asset, assetElement }); + for (const [key, value] of Object.entries(asset)) { + assetElement.setAttribute(key, value) } - assetElement.onerror = (error) => { - reject(error); - assetElement.remove(); - } - })) + assetsPromises.push(new Promise((resolve, reject) => { + assetElement.onload = () => { + resolve({ asset, assetElement }); + } + + assetElement.onerror = (error) => { + reject(error); + assetElement.remove(); + } + })) - document.head.appendChild(assetElement); + document.head.appendChild(assetElement); + } + await Promise.all(assetsPromises); } - await Promise.all(assetsPromises); } } diff --git a/packages/signalizejs/dialog/src/index.ts b/packages/signalizejs/dialog/src/index.ts index 60d751a..02d3d6a 100755 --- a/packages/signalizejs/dialog/src/index.ts +++ b/packages/signalizejs/dialog/src/index.ts @@ -1,4 +1,4 @@ -import type { Signalize, CustomEventListener } from 'signalizejs'; +import type { Signalize, SignalizePlugin, CustomEventListener } from 'signalizejs'; declare module 'signalizejs' { @@ -14,13 +14,13 @@ declare module 'signalizejs' { } } -export default ($: Signalize): void => { - $.on('signalize:ready', () => { - const { dispatch, config, select, on } = $ - const dialogAttribute = `${config.attributePrefix}dialog`; - const dialogModelessAttribute = `${dialogAttribute}${config.attributeSeparator}modeless`; - const dialogCloseButtonAttribute = `${dialogAttribute}${config.attributeSeparator}close`; - const dialogOpenButtonAttribute = `${dialogAttribute}${config.attributeSeparator}open`; +export default (): SignalizePlugin => { + return ($: Signalize): void => { + const { dispatch, attributePrefix, attributeSeparator, select, on } = $ + const dialogAttribute = `${attributePrefix}dialog`; + const dialogModelessAttribute = `${dialogAttribute}${attributeSeparator}modeless`; + const dialogCloseButtonAttribute = `${dialogAttribute}${attributeSeparator}close`; + const dialogOpenButtonAttribute = `${dialogAttribute}${attributeSeparator}open`; const getDialog = (id: string): HTMLDialogElement | null => select(`[${dialogAttribute}=${id}]`); @@ -89,5 +89,5 @@ export default ($: Signalize): void => { $.openDialog = openDialog; $.closeDialog = closeDialog; $.dialog = getDialog; - }) + } } diff --git a/packages/signalizejs/directives/src/index.ts b/packages/signalizejs/directives/src/index.ts index 42f0fd0..6c81270 100755 --- a/packages/signalizejs/directives/src/index.ts +++ b/packages/signalizejs/directives/src/index.ts @@ -1,4 +1,4 @@ -import type { Signalize, Scope } from 'signalizejs' +import type { Signalize, SignalizePlugin, Scope } from 'signalizejs' declare module '..' { interface Signalize { @@ -6,23 +6,17 @@ declare module '..' { createDirectiveFunction: (options: CreateFunctionOptions) => () => Promise AsyncFunction: () => Promise } - - interface SignalizeConfig { - directivesRenderedBlockStart: string - directivesRenderedBlockEnd: string - } } type DirectiveCallback = (data: DirectiveCallbackData) => Promise | void; interface DirectiveCallbackData extends Scope { - element: HTMLElement matches: RegExpMatchArray attribute: Attr } interface DirectiveMatcherParameters { - element: HTMLElement + element: Element attribute: Attr } @@ -40,33 +34,39 @@ interface RegisteredDirective extends Directive { } interface ProcessDirectiveOptions { - root?: HTMLElement + root?: Element directiveName?: string } interface CreateFunctionOptions { functionString: string context: Record - element?: HTMLElement | Document | DocumentFragment + element?: Element | Document | DocumentFragment +} + +interface PluginOptions { + renderedBlockStart?: string + renderedBlockEnd?: string } -export default ($: Signalize): void => { - $.on('signalize:ready', () => { - const { bind, on, Signal, scope, signal, config, globals } = $; +export default (options?: PluginOptions): SignalizePlugin => { + + return ($: Signalize) => { + const { bind, on, Signal, scope, signal, globals, attributePrefix, attributeSeparator } = $; const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor; const directives: Record = {}; - const directivesAttribute = `${config.attributePrefix}directives`; - const ignoreAttribute = `${directivesAttribute}${config.attributeSeparator}ignore`; - const orderAttribute = `${directivesAttribute}${config.attributeSeparator}order` - const renderedTemplateStartComment = config.directivesRenderedBlockStart ?? 'template'; - const renderedTemplateEndComment = config.directivesRenderedBlockEnd ?? '/template'; + const directivesAttribute = `${attributePrefix}directives`; + const ignoreAttribute = `${directivesAttribute}${attributeSeparator}ignore`; + const orderAttribute = `${directivesAttribute}${attributeSeparator}order` + const renderedTemplateStartComment = options?.renderedBlockStart ?? 'template'; + const renderedTemplateEndComment = options?.renderedBlockEnd ?? '/template'; let inited = false; - const processElement = async (element: HTMLElement, directivesToProcess?: string[]): Promise => { - const elementClosestScope = element.closest(`[${config.scopeAttribute}]`); + const processElement = async (element: Element, directivesToProcess?: string[]): Promise => { + const elementClosestScope = element.closest(`[${$.scopeAttribute}]`); let scopeInitPromise = null; - if (elementClosestScope && typeof elementClosestScope[config.scopeKey] === 'undefined') { + if (elementClosestScope && typeof elementClosestScope[$.scopeKey] === 'undefined') { scopeInitPromise = new Promise((resolve) => { on('scope:inited', ({ detail }) => { if (detail.element === elementClosestScope) { @@ -120,7 +120,7 @@ export default ($: Signalize): void => { elementScope.directives.add(directiveName); }); - countdown --; + countdown--; directivesPromises.push( directive.callback({ ...elementScope, @@ -144,24 +144,24 @@ export default ($: Signalize): void => { await processDirectiveFromQueue(directivesQueue.shift()); - element.removeAttribute(config.cloakAttribute); + element.removeAttribute($.cloakAttribute); } return element; }; - const processDirectives = async (options: ProcessDirectiveOptions = {}): Promise => { - const { root = config.root, directiveName } = options; + const processDirectives = async (options: ProcessDirectiveOptions = { root: $.root }): Promise => { + let { root, directiveName } = options; const directivesToProcess = directiveName === undefined ? Object.keys(directives) : [directiveName]; const processElements = async (root): Promise => { - const rootIsHtmlElement = root instanceof HTMLElement; + const rootIsElement = root instanceof Element; - if (rootIsHtmlElement && root?.closest(`[${ignoreAttribute}]`)) { + if (rootIsElement && root?.closest(`[${ignoreAttribute}]`)) { return; } - if (rootIsHtmlElement) { + if (rootIsElement) { await processElement(root, directivesToProcess); } @@ -219,11 +219,11 @@ export default ($: Signalize): void => { return null } } - return createFunctionCache[cacheKey].bind(undefined) + return createFunctionCache[cacheKey]; } directive('signal', { - matcher: new RegExp(`(?:\\$|${config.attributePrefix}signal${config.attributeSeparator})(\\S+)`), + matcher: new RegExp(`(?:\\$|${attributePrefix}signal${attributeSeparator})(\\S+)`), callback: async ({ matches, element, data, attribute }): Promise => { const fn = createFunction({ functionString: `return ${attribute.value.length ? attribute.value : "''"}`, @@ -246,7 +246,7 @@ export default ($: Signalize): void => { return; } - return new RegExp(`(?::|${config.attributePrefix})for`); + return new RegExp(`(?::|${attributePrefix})for`); }, callback: async ({ element, data, attribute }) => { if (element.tagName.toLowerCase() !== 'template') { @@ -486,7 +486,7 @@ export default ($: Signalize): void => { return; } - return new RegExp(`(?::|${config.attributePrefix})if`); + return new RegExp(`(?::|${attributePrefix})if`); }, callback: async ({ element, data, attribute }) => { const fn = createFunction({ @@ -500,7 +500,7 @@ export default ($: Signalize): void => { const signalsToWatch = []; for (const signal of Object.values(data)) { - if (!(signal instanceof Signal)) { + if (!(signal instanceof $.Signal)) { continue; } @@ -611,7 +611,7 @@ export default ($: Signalize): void => { return; } - return new RegExp(`(?::|${config.attributePrefix}bind${config.attributeSeparator})(\\S+)|(\\{([^{}]+)\\})`) + return new RegExp(`(?::|${attributePrefix}bind${attributeSeparator})(\\S+)|(\\{([^{}]+)\\})`) }, callback: async ({ matches, element, data, attribute }) => { const isShorthand = attribute.name.startsWith('{'); @@ -630,7 +630,7 @@ export default ($: Signalize): void => { let signalsToWatch = []; for (const signal of Object.values(data)) { - if (!(signal instanceof Signal)) { + if (!(signal instanceof $.Signal)) { continue; } @@ -649,7 +649,7 @@ export default ($: Signalize): void => { }); directive('on', { - matcher: new RegExp(`(?:\\@|${config.attributePrefix}on${config.attributeSeparator})(\\S+)`), + matcher: new RegExp(`(?:\\@|${attributePrefix}on${attributeSeparator})(\\S+)`), callback: async (scope) => { const { matches, element, data, attribute } = scope; @@ -684,7 +684,7 @@ export default ($: Signalize): void => { on('dom:mutation:node:added', (event) => { const node = event.detail; - if (!(node instanceof HTMLElement) || scope(node)?.directives !== undefined) { + if (!(node instanceof Element) || scope(node)?.directives !== undefined) { return; } @@ -695,5 +695,5 @@ export default ($: Signalize): void => { $.AsyncFunction = AsyncFunction; $.createDirectiveFunction = createFunction; $.directive = directive; - }) + } } diff --git a/packages/signalizejs/h/src/index.ts b/packages/signalizejs/h/src/index.ts index c3e5423..6bfc257 100755 --- a/packages/signalizejs/h/src/index.ts +++ b/packages/signalizejs/h/src/index.ts @@ -1,8 +1,8 @@ -import type { Signalize, Signal } from 'signalizejs'; +import type { Signalize, SignalizePlugin, Signal } from 'signalizejs'; declare module 'signalizejs' { interface Signalize { - h: (tagName: string, ...children: Array) => T + h: (tagName: string, ...children: Array) => T } } @@ -10,67 +10,67 @@ type HyperscriptChild = string | number | Element | Node | Signal; type HyperscriptChildAttrs = Record; -export default ($: Signalize): void => { - const { bind, Signal } = $; +export default (): SignalizePlugin => { + return ($: Signalize): void => { + $.h = (tagName: string, ...children: Array): T => { + let attrs: HyperscriptChildAttrs = {}; - $.h = (tagName: string, ...children: Array): T => { - let attrs: HyperscriptChildAttrs = {}; - - if (children[0]?.constructor?.name === 'Object') { - attrs = children.shift() as HyperscriptChildAttrs; - } + if (children[0]?.constructor?.name === 'Object') { + attrs = children.shift() as HyperscriptChildAttrs; + } - children = children.flat(Infinity); + children = children.flat(Infinity); - const el = document.createElement(tagName); + const el = document.createElement(tagName); - if (Object.keys(attrs).length > 0) { - bind(el, attrs); - } - - const normalizeChild = (child: string | number | Element | Node | Signal): Array => { - const result: Array = []; + if (Object.keys(attrs).length > 0) { + $.bind(el, attrs); + } - if (child instanceof Element || child instanceof Node) { - result.push(child); - } else if (child instanceof Signal) { - result.push(...normalizeChild(child.get())); - child.watch(({ newValue }) => { - const newNormalizedChildren = normalizeChild(newValue); - for (const newNormalizedChild of newNormalizedChildren) { - const oldNormalizedChild = result.shift(); - if (oldNormalizedChild != null) { - if (oldNormalizedChild !== newNormalizedChild) { - el.replaceChild(newNormalizedChild, oldNormalizedChild); + const normalizeChild = (child: string | number | Element | Node | Signal): Array => { + const result: Array = []; + + if (child instanceof Element || child instanceof Node) { + result.push(child); + } else if (child instanceof $.Signal) { + result.push(...normalizeChild(child.get())); + child.watch(({ newValue }) => { + const newNormalizedChildren = normalizeChild(newValue); + for (const newNormalizedChild of newNormalizedChildren) { + const oldNormalizedChild = result.shift(); + if (oldNormalizedChild != null) { + if (oldNormalizedChild !== newNormalizedChild) { + el.replaceChild(newNormalizedChild, oldNormalizedChild); + } + } else { + el.appendChild(newNormalizedChild); } - } else { - el.appendChild(newNormalizedChild); } + for (const oldNormalizedChild of result) { + el.removeChild(oldNormalizedChild); + } + result.push(...newNormalizedChildren); + }); + } else if (child instanceof Array) { + for (const childItem of child) { + result.push(...normalizeChild(childItem)); } - for (const oldNormalizedChild of result) { - el.removeChild(oldNormalizedChild); - } - result.push(...newNormalizedChildren); - }); - } else if (child instanceof Array) { - for (const childItem of child) { - result.push(...normalizeChild(childItem)); + } else { + result.push(document.createTextNode(String(child))); } - } else { - result.push(document.createTextNode(String(child))); - } - return result; - } + return result; + } - const fragment = document.createDocumentFragment(); + const fragment = document.createDocumentFragment(); - for (const child of children) { - fragment.append(...normalizeChild(child)); - } + for (const child of children) { + fragment.append(...normalizeChild(child)); + } - el.appendChild(fragment); + el.appendChild(fragment); - return el as T + return el as T + } } } diff --git a/packages/signalizejs/logger/src/index.ts b/packages/signalizejs/logger/src/index.ts index 1c39655..df860d8 100755 --- a/packages/signalizejs/logger/src/index.ts +++ b/packages/signalizejs/logger/src/index.ts @@ -1,4 +1,4 @@ -import type { Signalize, CustomEventListener } from 'signalizejs'; +import type { Signalize, SignalizePlugin, CustomEventListener } from 'signalizejs'; import type { AjaxOptions, AjaxReturn } from 'signalizejs/ajax'; declare module '..' { @@ -28,9 +28,13 @@ interface CompleteLogData extends Log { type Levels = 'log' | 'error' | 'warn'; -export default ($: Signalize): void => { - $.on('signalize:ready', () => { - const { ajax, dispatch, config } = $; +export interface PluginOptions { + enabledLevels: Levels[] +} + +export default (options: PluginOptions): SignalizePlugin => { + return ($: Signalize) => { + const { ajax, dispatch } = $; let enabledLevels: Levels[] = ['error']; const originalConsoleError = console.error; @@ -119,7 +123,7 @@ export default ($: Signalize): void => { originalWindowOnError(message, file, lineNumber, columnNumber, error); } - enabledLevels = config.logger?.enabledLevels ?? enabledLevels; + enabledLevels = options?.enabledLevels ?? enabledLevels; window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { // TODO test @@ -129,5 +133,5 @@ export default ($: Signalize): void => { }); $.sendToServer = sendToServer; - }); + } } diff --git a/packages/signalizejs/snippets/src/index.ts b/packages/signalizejs/snippets/src/index.ts index bfff7b5..5fd0876 100755 --- a/packages/signalizejs/snippets/src/index.ts +++ b/packages/signalizejs/snippets/src/index.ts @@ -1,8 +1,8 @@ -import type { Signalize, CustomEventListener } from 'signalizejs'; +import type { Signalize, SignalizePlugin, CustomEventListener } from 'signalizejs'; declare module 'signalizejs' { interface Signalize { - redraw: (content: string | DocumentFragment | HTMLElement | HTMLElement) => void + redrawSnippet: (content: string | DocumentFragment | Element | Element) => void } interface CustomEventListeners { @@ -11,17 +11,17 @@ declare module 'signalizejs' { } } -export default ($: Signalize): void => { - $.on('signalize:ready', () => { - const { select, dispatch, config } = $; - const snippetAttribute = `${config.attributePrefix}snippet`; - const snippetRedrawedAttribute = `${snippetAttribute}${config.attributeSeparator}redrawed`; - const snippetActionAttribute = `${snippetAttribute}${config.attributeSeparator}action`; +export default (): SignalizePlugin => { + return ($: Signalize): void => { + const { select, dispatch } = $; + const snippetAttribute = `${$.attributePrefix}snippet`; + const snippetRedrawedAttribute = `${snippetAttribute}${$.attributeSeparator}redrawed`; + const snippetActionAttribute = `${snippetAttribute}${$.attributeSeparator}action`; const parseHtml = (html: string, type: DOMParserSupportedType = 'text/html'): Document => (new DOMParser()).parseFromString(html, type); - $.redraw = (content: string | DocumentFragment | HTMLElement | HTMLElement): void => { + $.redrawSnippet = (content: string | DocumentFragment | Element | Element): void => { const fragment = parseHtml(content); while (true) { @@ -90,5 +90,5 @@ export default ($: Signalize): void => { element.setAttribute(snippetRedrawedAttribute, ''); } } - }) + } } diff --git a/packages/signalizejs/spa/src/index.ts b/packages/signalizejs/spa/src/index.ts index d7bdc55..37cddda 100755 --- a/packages/signalizejs/spa/src/index.ts +++ b/packages/signalizejs/spa/src/index.ts @@ -1,4 +1,4 @@ -import type { Signalize, CustomEventListener } from 'signalizejs'; +import type { Signalize, SignalizePlugin, CustomEventListener } from 'signalizejs'; declare module 'signalizejs' { interface Signalize { @@ -16,11 +16,6 @@ declare module 'signalizejs' { 'spa:popstate': CustomEventListener 'spa:clicked': CustomEventListener } - - interface SignalizeConfig { - spaAppVersionHeader: string - spaCacheHeader: string - } } type StateAction = 'push' | 'replace'; @@ -40,17 +35,22 @@ interface SpaDispatchEventData extends VisitData { success?: boolean } -export default ($: Signalize): void => { - $.on('signalize:ready', () => { - const { dispatch, ajax, redraw, select, on, config } = $; +export interface PluginOptions { + cacheHeader?: string + appVersionHeader?: string +} - const spaAttribute = `${config.attributePrefix}spa`; - const spaUrlAttribute = `${spaAttribute}${config.attributeSeparator}url`; - const spaIgnoreAttribute = `${spaAttribute}${config.attributeSeparator}ignore`; - const spaStateActionAttribute = `${spaAttribute}${config.attributeSeparator}state-action`; - const spaMetaCacheNameAttribute = `${spaAttribute}${config.attributeSeparator}cache-control`; - const spaCacheHeader = config?.spaCacheHeader ?? 'X-Spa-Cache-Control'; - const spaAppVersionHeader = config?.spaAppVersionHeader ?? 'X-Spa-App-Version'; +export default (options?: PluginOptions): SignalizePlugin => { + return ($: Signalize): void => { + const { dispatch, ajax, redraw, select, on } = $; + + const spaAttribute = `${$.attributePrefix}spa`; + const spaUrlAttribute = `${spaAttribute}${$.attributeSeparator}url`; + const spaIgnoreAttribute = `${spaAttribute}${$.attributeSeparator}ignore`; + const spaStateActionAttribute = `${spaAttribute}${$.attributeSeparator}state-action`; + const spaMetaCacheNameAttribute = `${spaAttribute}${$.attributeSeparator}cache-control`; + const spaCacheHeader = options?.cacheHeader ?? 'X-Spa-Cache-Control'; + const spaAppVersionHeader = options?.appVersionHeader ?? 'X-Spa-App-Version'; let currentLocation = new URL(window.location.href); const spaVersion = null; @@ -284,5 +284,5 @@ export default ($: Signalize): void => { }); $.visit = visit; - }) + } } diff --git a/packages/signalizejs/src/Signalize.ts b/packages/signalizejs/src/Signalize.ts index 147d08b..7aea21a 100755 --- a/packages/signalizejs/src/Signalize.ts +++ b/packages/signalizejs/src/Signalize.ts @@ -1,45 +1,45 @@ -import BindPlugin from './plugins/bind'; -import DispatchPlugin from './plugins/dispatch' -import DomReadyPlugin from './plugins/domReady'; -import HeightPlugin from './plugins/height'; -import IntersectionObserverPlugin from './plugins/intersection-observer'; -import IsInViewportPlugin from './plugins/is-in-viewport'; -import IsVisiblePlugin from './plugins/is-visible'; -import MergePlugin from './plugins/merge'; -import MutationsObserverPlugin from './plugins/mutation-observer'; -import OffPlugin from './plugins/off'; -import OffsetPlugin from './plugins/offset'; -import OnPlugin from './plugins/on'; -import ScopePlugin from './plugins/scope'; -import SelectPlugin from './plugins/select'; -import SignalPlugin from './plugins/signal'; -import TaskPlugin from './plugins/task'; -import WidthPlugin from './plugins/width'; +import bind from './plugins/bind'; +import dispatch from './plugins/dispatch' +import domReady from './plugins/domReady'; +import height from './plugins/height'; +import intersectionObserver from './plugins/intersection-observer'; +import isInViewport from './plugins/is-in-viewport'; +import isVisible from './plugins/is-visible'; +import merge from './plugins/merge'; +import mutationsObserver from './plugins/mutation-observer'; +import off from './plugins/off'; +import offset from './plugins/offset'; +import on from './plugins/on'; +import scope from './plugins/scope'; +import select from './plugins/select'; +import signal from './plugins/signal'; +import task from './plugins/task'; +import width from './plugins/width'; -/* export * from './plugins/async-function'; export * from './plugins/bind'; export * from './plugins/dispatch' export * from './plugins/domReady'; +export * from './plugins/height'; +export * from './plugins/intersection-observer'; +export * from './plugins/is-in-viewport'; +export * from './plugins/is-visible'; export * from './plugins/merge'; export * from './plugins/mutation-observer'; -export * from './plugins/on'; export * from './plugins/off'; -export * from './plugins/ref'; +export * from './plugins/offset'; +export * from './plugins/on'; export * from './plugins/scope'; export * from './plugins/select'; export * from './plugins/signal'; -export * from './plugins/task' */ - -export interface SignalizeConfig extends Record { - root: Element | Document | DocumentFragment - attributeSeparator: string - attributePrefix: string -} +export * from './plugins/task'; +export * from './plugins/width'; export type SignalizeGlobals = Record export interface SignalizeOptions { - config?: SignalizeConfig + root: Element | Document + attributeSeparator: string + attributePrefix: string globals?: SignalizeGlobals plugins?: SignalizePlugin } @@ -47,57 +47,51 @@ export interface SignalizeOptions { export type SignalizePlugin = (signalize: Signalize) => void export class Signalize { - config: Partial = { - root: document, - attributeSeparator: '-', - attributePrefix: '', - customEventListeners: {} - } - + root!: Element | Document; + attributeSeparator!: string; + attributePrefix!: string; globals: SignalizeGlobals = {}; - constructor (options?: Partial) { + constructor (options: Partial = {}) { this.#init(options); } - readonly #init = (options?: Partial): void => { - const readyListeners: CallableFunction = []; + use = (plugin: SignalizePlugin): void => { + plugin(this) + } - this.config.customEventListeners['signalize:ready'] = (target: HTMLElement | string, listener: CallableFunction, options: AddEventListenerOptions) => { - readyListeners.push(listener); - } + readonly #init = (options: Partial): void => { + this.root = options?.root ?? document; + this.attributePrefix = options?.attributePrefix ?? ''; + this.attributeSeparator = options?.attributeSeparator ?? ''; - MergePlugin(this); - TaskPlugin(this); - HeightPlugin(this); - WidthPlugin(this); - SelectPlugin(this); - DispatchPlugin(this); - OffsetPlugin(this); - IsVisiblePlugin(this); - IsInViewportPlugin(this); - OnPlugin(this); - DomReadyPlugin(this); - OffPlugin(this); - SignalPlugin(this); - IntersectionObserverPlugin(this); - ScopePlugin(this); - BindPlugin(this); - MutationsObserverPlugin(this); - - for (const plugin of options?.plugins ?? []) { - plugin(this); - } + merge(this); this.globals = this.merge(this.globals, options?.globals ?? {}); - this.config = this.merge(this.config, options?.config ?? {}) as SignalizeConfig; - while(readyListeners.length) { - readyListeners.shift()(this) + task(this); + height(this); + width(this); + select(this); + dispatch(this); + offset(this); + isVisible(this); + isInViewport(this); + on(this); + domReady(this); + off(this); + signal(this); + intersectionObserver(this); + scope(this); + bind(this); + mutationsObserver(this); + + for (const plugin of options?.plugins ?? []) { + plugin(this); } this.on('dom:ready', () => { - this.observeMutations(this.config.root); + this.observeMutations(this.root); }); } } diff --git a/packages/signalizejs/src/plugins/bind.ts b/packages/signalizejs/src/plugins/bind.ts index d19395c..87b666f 100755 --- a/packages/signalizejs/src/plugins/bind.ts +++ b/packages/signalizejs/src/plugins/bind.ts @@ -1,5 +1,4 @@ -import type Signalize from '..' -import { Scope } from './scope'; +import type { Signalize } from '..' declare module '..' { interface Signalize { @@ -8,7 +7,7 @@ declare module '..' { } export default ($: Signalize): void => { - const { scope, Signal, on, off } = $; + const { scope, on, off, Signal, Scope } = $; const reactiveInputAttributes = ['value', 'checked']; const numericInputAttributes = ['range', 'number']; @@ -33,7 +32,7 @@ export default ($: Signalize): void => { html: 'innerHTML' } - $.bind = (element: EventTarget, attributes: Record): void => { + $.bind = (element, attributes) => { const unwatchSignalCallbacks: CallableFunction[] = []; const elementScope = scope(element); @@ -47,10 +46,10 @@ export default ($: Signalize): void => { const attrOptionsAsArray = Array.isArray(attrOptions) ? attrOptions : [attrOptions]; const isNumericInput = numericInputAttributes.includes(element.getAttribute('type') ?? ''); const attributeBinder = attrOptionsAsArray.pop(); + const signalsToWatch = attrOptionsAsArray; const attributeBinderType = typeof attributeBinder; const attributeBinderIsFunction = attributeBinderType === 'function'; const attributeBinderIsSignal = attributeBinder instanceof Signal; - const signalsToWatch = attrOptionsAsArray; let attributeInited = false; let previousSettedValue: any; @@ -87,9 +86,9 @@ export default ($: Signalize): void => { continue; } - if (attributeBinderIsSignal === true) { - //signalsToWatch.push(attributeBinder); - } + /* if (attributeBinderIsSignal === true) { + signalsToWatch.push(attributeBinder); + } */ if (attributeBinderIsSignal === true || signalsToWatch.length === 1) { getListener = () => attributeBinder(); diff --git a/packages/signalizejs/src/plugins/dispatch.ts b/packages/signalizejs/src/plugins/dispatch.ts index f634f5f..d4d0b63 100755 --- a/packages/signalizejs/src/plugins/dispatch.ts +++ b/packages/signalizejs/src/plugins/dispatch.ts @@ -7,14 +7,14 @@ declare module '..' { } interface Options { - target?: Document | HTMLElement | DocumentFragment + target?: Document | Element | DocumentFragment bubbles?: boolean cancelable?: boolean } export default ($: Signalize): void => { - $.dispatch = (eventName: string, eventData?, options?): boolean => { - return (options?.target ?? $.config.root).dispatchEvent( + $.dispatch = (eventName, eventData?, options?) => { + return (options?.target ?? $.root).dispatchEvent( new window.CustomEvent(eventName, { detail: eventData, cancelable: options?.cancelable ?? true, diff --git a/packages/signalizejs/src/plugins/domReady.ts b/packages/signalizejs/src/plugins/domReady.ts index b23ed2b..41bcd26 100755 --- a/packages/signalizejs/src/plugins/domReady.ts +++ b/packages/signalizejs/src/plugins/domReady.ts @@ -21,25 +21,23 @@ export default ($: Signalize): void => { } const isDomReady = (): boolean => { - const documentElement = $.config.root instanceof Document ? $.config.root : $.config.root?.ownerDocument; + const documentElement = $.root instanceof Document ? $.root : $.root?.ownerDocument; return documentElement.readyState !== 'loading' }; - $.config.customEventListeners['dom:ready'] = (target: HTMLElement | string, listener: CallableFunction, options: AddEventListenerOptions) => { + $.customEventListener('dom:ready', (target: Element | string, listener: CallableFunction, options: AddEventListenerOptions) => { if (isDomReady()) { listener() } else { domReadyListeners.push(listener); } - } + }); - $.on('signalize:ready', () => { - if (isDomReady()) { - callOnDomReadyListeners(); - } else { - document.addEventListener('DOMContentLoaded', callOnDomReadyListeners, { once: true }) - } - }) + if (isDomReady()) { + callOnDomReadyListeners(); + } else { + document.addEventListener('DOMContentLoaded', callOnDomReadyListeners, { once: true }) + } $.isDomReady = isDomReady; } diff --git a/packages/signalizejs/src/plugins/intersection-observer.ts b/packages/signalizejs/src/plugins/intersection-observer.ts index b2fdc10..7839a06 100755 --- a/packages/signalizejs/src/plugins/intersection-observer.ts +++ b/packages/signalizejs/src/plugins/intersection-observer.ts @@ -9,7 +9,7 @@ declare module '..' { export default ($: Signalize): void => { $.observeIntersection = (element, callback, options) => { const observer = new IntersectionObserver(callback, { - root: element.closest(`[${$.config.attributePrefix}intersection-observer-root]`), + root: element.closest(`[${$.attributePrefix}intersection-observer-root]`), rootMargin: '0% 0%', threshold: [0.0, 0.1], ...options ?? {} diff --git a/packages/signalizejs/src/plugins/is-in-viewport.ts b/packages/signalizejs/src/plugins/is-in-viewport.ts index e22b8ce..4358783 100755 --- a/packages/signalizejs/src/plugins/is-in-viewport.ts +++ b/packages/signalizejs/src/plugins/is-in-viewport.ts @@ -2,7 +2,7 @@ import type { Signalize } from '..' declare module '..' { interface Signalize { - isInViewport: (element: HTMLElement, options: IsInViewportOptions) => IsInViewportInfo + isInViewport: (element: Element, options: IsInViewportOptions) => IsInViewportInfo } } @@ -17,13 +17,11 @@ interface IsInViewportInfo { } export default ($: Signalize): void => { - const { offset, height } = $; - - $.isInViewport = (element, options): IsInViewportInfo => { + $.isInViewport = (element, options) => { const windowTop = window.scrollY; const windowBottom = windowTop + window.innerHeight; - const elementTop = offset(element).top + (options?.offset ?? 0) - const elementBottom = elementTop + height(element); + const elementTop = $.offset(element).top + (options?.offset ?? 0) + const elementBottom = elementTop + $.height(element); return { top: windowTop < elementTop && elementTop < windowBottom, diff --git a/packages/signalizejs/src/plugins/is-visible.ts b/packages/signalizejs/src/plugins/is-visible.ts index fae1916..0255993 100755 --- a/packages/signalizejs/src/plugins/is-visible.ts +++ b/packages/signalizejs/src/plugins/is-visible.ts @@ -2,12 +2,12 @@ import type { Signalize } from '..' declare module '..' { interface Signalize { - isVisible: (element: HTMLElement) => boolean + isVisible: (element: Element) => boolean } } export default ($: Signalize): void => { - $.isVisible = (element: HTMLElement): boolean => { + $.isVisible = (element: Element): boolean => { if (element.getClientRects().length !== 0) { return true; } diff --git a/packages/signalizejs/src/plugins/mutation-observer.ts b/packages/signalizejs/src/plugins/mutation-observer.ts index ea83f87..64e0921 100755 --- a/packages/signalizejs/src/plugins/mutation-observer.ts +++ b/packages/signalizejs/src/plugins/mutation-observer.ts @@ -3,7 +3,7 @@ import type { CustomEventListener } from './on'; declare module '..' { interface Signalize { - observeMutations: (root: HTMLElement | Document | DocumentFragment, options?: ObserverOptions) => void + observeMutations: (root: Element | Document | DocumentFragment, options?: ObserverOptions) => void } interface CustomEventListeners { @@ -21,8 +21,7 @@ interface ObserverOptions { } export default ($: Signalize): void => { - const { dispatch } = $; - $.observeMutations = (root: HTMLElement | Document | DocumentFragment = document, options?: ObserverOptions): MutationObserver => { + $.observeMutations = (root = document, options?) => { const domMutationEvent = 'dom:mutation'; const domMutationNodeAddedEvent = 'dom:mutation:node:added'; const domMutationNodeRemovedEvent = 'dom:mutation:node:removed'; @@ -31,14 +30,14 @@ export default ($: Signalize): void => { if (callback === undefined) { callback = (mutationRecords: MutationRecord[]) => { for (const mutation of mutationRecords) { - dispatch(domMutationEvent, mutation); + $.dispatch(domMutationEvent, mutation); for (const node of mutation.addedNodes) { - dispatch(domMutationNodeAddedEvent, node) + $.dispatch(domMutationNodeAddedEvent, node) } for (const node of mutation.removedNodes) { - dispatch(domMutationNodeRemovedEvent, node) + $.dispatch(domMutationNodeRemovedEvent, node) } } } diff --git a/packages/signalizejs/src/plugins/off.ts b/packages/signalizejs/src/plugins/off.ts index 53319b8..5de2d0e 100755 --- a/packages/signalizejs/src/plugins/off.ts +++ b/packages/signalizejs/src/plugins/off.ts @@ -2,16 +2,14 @@ import type Signalize from '..'; declare module '..' { interface Signalize { - off: (type: string, element: HTMLElement | Document, listener: EventListenerOrEventListenerObject, options?: Record) => void + off: (type: string, element: Element | Document, listener: EventListenerOrEventListenerObject, options?: Record) => void } } export default ($: Signalize): void => { - const { selectorToIterable } = $ - - $.off = (type: string, element, listener: EventListenerOrEventListenerObject, options = {}): void => { + $.off = (type, element, listener, options = {}) => { const events = type.split(','); - const elements = selectorToIterable(element); + const elements = $.selectorToIterable(element); for (const event of events) { for (const element of elements) { diff --git a/packages/signalizejs/src/plugins/offset.ts b/packages/signalizejs/src/plugins/offset.ts index 573ae5b..e39bea2 100755 --- a/packages/signalizejs/src/plugins/offset.ts +++ b/packages/signalizejs/src/plugins/offset.ts @@ -2,11 +2,7 @@ import type { Signalize } from '..'; declare module '..' { interface Signalize { - offset: (element: HTMLElement) => Offset - } - - interface SignalizeConfig { - intersectionObserverRootAttribute: string + offset: (element: Element) => Offset } } @@ -18,7 +14,7 @@ interface Offset { } export default ($: Signalize): void => { - $.offset = (element: HTMLElement): Offset => { + $.offset = (element) => { const rect = element.getBoundingClientRect(); const defaultView = element.ownerDocument.defaultView; diff --git a/packages/signalizejs/src/plugins/on.ts b/packages/signalizejs/src/plugins/on.ts index c776af1..209637e 100755 --- a/packages/signalizejs/src/plugins/on.ts +++ b/packages/signalizejs/src/plugins/on.ts @@ -1,4 +1,4 @@ -import type Signalize from '..'; +import type { Signalize, SignalizePlugin } from '..'; import type { Selectable } from './select'; declare module '..' { @@ -9,17 +9,13 @@ declare module '..' { callbackOrOptions?: CallableFunction | AddEventListenerOptions, options?: AddEventListenerOptions ) => void - customEventListeners: Record + customEventListener: (name: string, listener: CustomEventListener) => void } interface CustomEventListeners { remove: CustomEventListener clickOutside: CustomEventListener } - - interface SignalizeConfig { - customEventListeners: Record - } } export type EventTarget = string | NodeListOf | Element[] | Element | Window; @@ -31,9 +27,12 @@ export interface CustomEventListeners extends ElementEventMap { remove: CustomEventListener } +export interface PluginOptions { + customEventListeners: Record +} + export default ($: Signalize): void => { - let customEventListeners: Record = { - ...$.config.customEventListeners, + const customEventListeners: Record = { clickOutside: (target: Element | string, listener: CallableFunction, options: AddEventListenerOptions) => { document.addEventListener('click', (listenerEvent) => { const eventTarget = listenerEvent.target as Element; @@ -67,7 +66,7 @@ export default ($: Signalize): void => { const events = event.split(' ').map((event) => event.trim()); let target: Selectable; let callback: CallableFunction; - const root = $.config.root ?? document; + const root = $.root ?? document; options = typeof callbackOrOptions === 'function' ? options : callbackOrOptions; if (typeof targetOrCallback === 'function') { @@ -106,12 +105,9 @@ export default ($: Signalize): void => { } } - on('signalize:ready', () => { - customEventListeners = { - ...customEventListeners, - ...$.config.customEventListeners ?? {} - }; - }); + $.customEventListener = (name, listener) => { + customEventListeners[name] = listener + } $.on = on; } diff --git a/packages/signalizejs/src/plugins/scope.ts b/packages/signalizejs/src/plugins/scope.ts index 940ba74..517e567 100755 --- a/packages/signalizejs/src/plugins/scope.ts +++ b/packages/signalizejs/src/plugins/scope.ts @@ -3,7 +3,11 @@ import type { CustomEventListener } from './on'; declare module '..' { interface Signalize { + Scope: Scope scope: (nameOrElement: string | Element | Document | DocumentFragment, init: ScopeInitFunction) => undefined | Scope + cloakAttribute: string + scopeAttribute: string + scopeKey: string } interface CustomEventListeners { @@ -14,119 +18,122 @@ declare module '..' { type ScopeInitFunction = (Scope: Scope) => void | Promise; -export class Scope { - readonly #$: Signalize; - readonly #cleanups = new Set(); - readonly #localData = {}; - - element: Element | Document | DocumentFragment; - - constructor ({ - signalize, - element - }: { - signalize: Signalize - element: Element | Document | DocumentFragment - }) { - this.element = element; - this.#$ = signalize; - this.element[signalize.config.scopeKey] = this; - } +export interface Scope { + element: Element | Document | DocumentFragment + data: ProxyConstructor + cleanup: (callback: CallableFunction) => void + ref: (id: string) => T | null + refs: (id: string) => T[] +} - set data (newValue) { - this.#localData = newValue; - } +export default ($: Signalize): void => { + const { on, merge } = $; - get data() { - const { scope, merge } = this.#$; - const getScopedData = (element, data = {}) => { - if (element === null) { - return data; - } + $.cloakAttribute = 'cloak'; + $.scopeAttribute = `${$.attributePrefix}scope`; + $.scopeKey = '__signalizeScope'; + + class ElementScope implements Scope { + readonly #cleanups = new Set(); + readonly #localData = {}; - return merge(data, scope(element)?.data ?? getScopedData(element.parentNode, data)) + element: Element | Document | DocumentFragment; + + constructor ({ + element + }: { + element: Element | Document | DocumentFragment + }) { + this.element = element; + this.element[$.scopeKey] = this; } - const getParentData = (element, key: string): any => { - if (element === null) { - return; - } - return element[this.#$.config.scopeKey] === undefined ? getParentData(element.parentNode, key) : scope(element).data[key]; + set data (newValue) { + this.#localData = newValue; } - const setParentData = (element, key: string, value: any) => { - if (element === null) { - return false; - } + get data (): ProxyConstructor { + const getScopedData = (element, data = {}) => { + if (element === null) { + return data; + } - if (element[this.#$.config.scopeKey] !== undefined) { - this.#$.scope(element).data[key] = value; - } else { - setParentData(element.parentNode, key, value); + return merge(data, scope(element)?.data ?? getScopedData(element.parentNode, data)) } - } + const getParentData = (element, key: string): any => { + if (element === null) { + return; + } - return new Proxy(getScopedData(this.element.parentNode, this.#localData), { - set: (target, key: string, newValue: any) => { - this.#localData[key] = newValue; - return true; - }, - get: (target, key) => { - return this.#localData[key] ?? getParentData(this.element.parentNode, key) + return element[$.scopeKey] === undefined ? getParentData(element.parentNode, key) : scope(element).data[key]; } - }) - } - cleanup = (callback: CallableFunction): void => { - if (callback === undefined) { - const cleanChildren = (element) => { - for (const child of [...element.childNodes]) { - setTimeout(() => { - this.#$.scope(child)?.cleanup(); - if (child instanceof Element && child.childNodes.length) { - cleanChildren(child); - } - }, 0) + const setParentData = (element, key: string, value: any) => { + if (element === null) { + return false; } - } - for (const cleanup of this.#cleanups) { - setTimeout(() => { - cleanup(); - }, 0); + if (element[$.scopeKey] !== undefined) { + scope(element).data[key] = value; + } else { + setParentData(element.parentNode, key, value); + } } - this.#cleanups.clear(); - cleanChildren(this.element); - return; + return new Proxy(getScopedData(this.element.parentNode, this.#localData), { + set: (target, key: string, newValue: any) => { + this.#localData[key] = newValue; + return true; + }, + get: (target, key) => { + return this.#localData[key] ?? getParentData(this.element.parentNode, key) + } + }) } - this.#cleanups.add(callback); - } + cleanup = (callback: CallableFunction): void => { + if (callback === undefined) { + const cleanChildren = (element) => { + for (const child of [...element.childNodes]) { + setTimeout(() => { + scope(child)?.cleanup(); + if (child instanceof Element && child.childNodes.length) { + cleanChildren(child); + } + }, 0) + } + } - ref = (id: string): T | null => { - return this.refs(id)[0] ?? null; - } + for (const cleanup of this.#cleanups) { + setTimeout(() => { + cleanup(); + }, 0); + } - refs = (id: string): T[] => { - return [...this.#$.selectAll( - `[${this.#$.config.attributePrefix}ref="${id}"]`, - this.element - )].filter((element: Element) => { - return element.closest( - `[${this.#$.config.attributePrefix}scope]` - ) === this.element - }) - } -} + this.#cleanups.clear(); + cleanChildren(this.element); + return; + } -export default ($: Signalize): void => { - const { on, selectAll, dispatch } = $; + this.#cleanups.add(callback); + } - $.config.cloakAttribute = 'cloak'; + ref = (id: string): T | null => { + return this.refs(id)[0] ?? null; + } + + refs = (id: string): T[] => { + return [...$.selectAll( + `[${$.attributePrefix}ref="${id}"]`, + this.element + )].filter((element: Element) => { + return element.closest( + `[${$.attributePrefix}scope]` + ) === this.element + }) + } + } - let scopeAttribute: string; - const scopeKey = '__signalizeScope'; const definedScopes: Record = {}; const scope = (nameOrElement: string | Element | Document | DocumentFragment, init?: ScopeInitFunction): undefined | Scope => { @@ -135,44 +142,41 @@ export default ($: Signalize): void => { throw new Error(`Scope "${nameOrElement}" is already defined.`); } definedScopes[nameOrElement] = init; - dispatch('scope:defined', { name: nameOrElement }) + $.dispatch('scope:defined', { name: nameOrElement }) } else if (typeof init === 'function') { - if (nameOrElement[scopeKey] === undefined) { - nameOrElement[scopeKey] = new Scope({ signalize: $, element: nameOrElement }) + if (nameOrElement[$.scopeKey] === undefined) { + nameOrElement[$.scopeKey] = new ElementScope({ element: nameOrElement }) } - init(nameOrElement[scopeKey]); + init(nameOrElement[$.scopeKey]); } - return nameOrElement[scopeKey]; + return nameOrElement[$.scopeKey]; }; - on('signalize:ready', () => { - scopeAttribute = `${$.config.attributePrefix}scope`; - - on('dom:ready scope:defined', (event) => { - let selector = `[${scopeAttribute}]` - if (event !== undefined && event.detail?.name === undefined) { - selector += `="${event.detail.name}"` - } + on('dom:ready scope:defined', (event) => { + let selector = `[${$.scopeAttribute}]` + if (event !== undefined && event.detail?.name === undefined) { + selector += `="${event.detail.name}"` + } - for (const element of selectAll(selector)) { - scope(element, definedScopes[element.getAttribute(scopeAttribute)]); - } - }); + for (const element of $.selectAll(selector)) { + scope(element, definedScopes[element.getAttribute($.scopeAttribute)]); + } + }); - on('dom:mutation:node:removed', (event) => { - scope(event.detail)?.cleanup(); - }); + on('dom:mutation:node:removed', (event) => { + scope(event.detail)?.cleanup(); + }); - on('dom:mutation:node:added' as keyof CustomEventListener, $.config.root, ({ detail }: { detail: Node }): void => { - if (!(detail instanceof Element) || scope(detail) !== undefined) { - return; - } + on('dom:mutation:node:added' as keyof CustomEventListener, $.root, ({ detail }: { detail: Node }): void => { + if (!(detail instanceof Element) || scope(detail) !== undefined) { + return; + } - scope(detail, detail.getAttribute(scopeAttribute)); - }); - }) + scope(detail, detail.getAttribute($.scopeAttribute)); + }); + $.Scope = ElementScope; $.scope = scope; } diff --git a/packages/signalizejs/src/plugins/select.ts b/packages/signalizejs/src/plugins/select.ts index c1d11e8..877ce8a 100755 --- a/packages/signalizejs/src/plugins/select.ts +++ b/packages/signalizejs/src/plugins/select.ts @@ -3,52 +3,44 @@ import type Signalize from '..'; declare module '..' { interface Signalize { selectorToIterable: (target: Selectable, normalizeDocument?: boolean) => IterableElements - select: (selector: string, root?: string | HTMLElement) => T | null - selectAll: (selector: string, root?: string | HTMLElement) => NodeListOf + select: (selector: string, root?: string | Element) => T | null + selectAll: (selector: string, root?: string | Element) => NodeListOf } } -export type Selectable = string | NodeListOf | HTMLElement[] | HTMLElement | Window | Document +export type Selectable = string | NodeListOf | Element[] | Element | Window | Document -export type IterableElements = Array +export type IterableElements = Array export default ($: Signalize): void => { - $.select = (selector: string, root?: string | HTMLElement): T | null => { - root = root ?? $.config.root - if (typeof root === 'string') { - const rootSelector = root; - root = root.querySelector(rootSelector) as HTMLElement; - - if (root === null) { - throw new Error(`Signalize: Root element "${rootSelector}" not found.`); - } + $.select = (selector: string, root: string | Element | Document = $.root): T | null => { + const rootEl = typeof root === 'string' ? $.root.querySelector(root) : root; + + if (rootEl === null) { + throw new Error('Signalize: root for select cannot be null.'); } - return root.querySelector(selector); + return rootEl.querySelector(selector); } - $.selectAll = (selector: string, root?: string | HTMLElement): NodeListOf => { - root = root ?? $.config.root; - if (typeof root === 'string') { - const rootSelector = root; - root = root.querySelector(rootSelector) as HTMLElement; + $.selectAll = (selector: string, root: string | Element | Document = $.root): NodeListOf => { + const rootEl = typeof root === 'string' ? $.root.querySelector(root) : root; - if (root === null) { - throw new Error(`Signalize: Root element "${rootSelector}" not found.`); - } + if (rootEl === null) { + throw new Error('Signalize: root for selectAll cannot be null.'); } - return root.querySelectorAll(selector); + return rootEl.querySelectorAll(selector); } $.selectorToIterable = (target: Selectable, normalizeDocument = false): IterableElements => { let elements: IterableElements; if (typeof target === 'string') { - elements = [...$.selectAll(target)]; + elements = [...$.selectAll(target)]; } else { const targetIsDocument = target instanceof Document; - if (target instanceof HTMLElement || targetIsDocument || target instanceof Window) { + if (target instanceof Element || targetIsDocument || target instanceof Window) { elements = [targetIsDocument && normalizeDocument ? target.documentElement : target] } else { elements = target instanceof Array || target instanceof NodeList ? [...target] : [target]; diff --git a/packages/signalizejs/src/plugins/signal.ts b/packages/signalizejs/src/plugins/signal.ts index 94dfd02..6d6bb18 100755 --- a/packages/signalizejs/src/plugins/signal.ts +++ b/packages/signalizejs/src/plugins/signal.ts @@ -2,7 +2,7 @@ import type Signalize from '..'; declare module '..' { interface Signalize { - Signal: typeof Signal + Signal: Signal signal: (defaultValue: T) => Signal } } @@ -22,100 +22,118 @@ interface SignalWatcherArguments { oldValue?: T } -type Unwatch = () => void +type SignalUnwatch = () => void -export class Signal extends Function { - value: T; - watchers: Record> = { - beforeSet: new Set(), - afterSet: new Set(), - onGet: new Set() - }; +interface SignalWatchers extends Record> { + beforeSet: Set + afterSet: Set + onGet: Set +} - setTimeout = undefined; +export interface Signal { + value: T + watchers: SignalWatchers + get: () => T + set: (newValue: T) => void + watch: (listener: BeforeSetSignalWatcher | AfterSetSignalWatcher, options?: SignalWatcherOptions) => SignalUnwatch + toString: () => string + valueOf: () => T + toJSON: () => T - constructor (defaultValue: T) { - super() - this.value = defaultValue; - return new Proxy(this, { - apply: () => { - return this.get() - } - }) - } +} - get = (): T => { - for (const watcher of this.watchers.onGet) { - watcher({ newValue: this.value, oldValue: this.value }); +export default ($: Signalize): void => { + class SignalStructure extends Function implements Signal { + value: T; + watchers: SignalWatchers = { + beforeSet: new Set(), + afterSet: new Set(), + onGet: new Set() + }; + + #setTimeout!: number; + + constructor (defaultValue: T) { + super() + this.value = defaultValue; + return new Proxy(this, { + apply: () => { + return this.get() + } + }) } - return this.value; - } + get = (): T => { + for (const watcher of this.watchers.onGet) { + watcher({ newValue: this.value, oldValue: this.value }); + } - set = (newValue: T): void => { - clearTimeout(this.setTimeout); - this.setTimeout = setTimeout(() => { - const oldValue = this.value; + return this.value; + } - if (['string', 'number'].includes(typeof newValue) && newValue === oldValue) { - return; - } + set = (newValue: T): void => { + clearTimeout(this.#setTimeout); + this.#setTimeout = setTimeout(() => { + const oldValue = this.value; + + if (['string', 'number'].includes(typeof newValue) && newValue === oldValue) { + return; + } - let settable = true; - for (const watcher of this.watchers.beforeSet) { - const watcherData = watcher({ newValue, oldValue }); - if (typeof watcherData !== 'undefined') { - settable = watcherData.settable ?? settable; - newValue = watcherData.value; + let settable = true; + for (const watcher of this.watchers.beforeSet) { + const watcherData = watcher({ newValue, oldValue }); + if (typeof watcherData !== 'undefined') { + settable = watcherData.settable ?? settable; + newValue = watcherData.value; + } + if (!settable) { + break; + } } + if (!settable) { - break; + return; } - } - if (!settable) { - return; - } + this.value = newValue; + for (const watcher of this.watchers.afterSet) { + watcher({ newValue, oldValue }) + } + }); + } - this.value = newValue; - for (const watcher of this.watchers.afterSet) { - watcher({ newValue, oldValue }) + watch = (listener: BeforeSetSignalWatcher | AfterSetSignalWatcher, options: SignalWatcherOptions = {}): SignalUnwatch => { + const immediate = options.immediate ?? false; + const execution = options.execution ?? 'afterSet'; + + if (immediate) { + const watcherData = listener({ newValue: this.value }); + if (typeof watcherData !== 'undefined' && execution === 'beforeSet' && (watcherData.settable ?? true)) { + this.value = watcherData.value; + } } - }); - } - watch = (listener: BeforeSetSignalWatcher | AfterSetSignalWatcher, options: SignalWatcherOptions = {}): Unwatch => { - const immediate = options.immediate ?? false; - const execution = options.execution ?? 'afterSet'; + this.watchers[execution].add(listener); - if (immediate) { - const watcherData = listener({ newValue: this.value }); - if (typeof watcherData !== 'undefined' && execution === 'beforeSet' && (watcherData.settable ?? true)) { - this.value = watcherData.value; + return () => { + this.watchers[execution].delete(listener); } } - this.watchers[execution].add(listener); - - return () => { - this.watchers[execution].delete(listener); + toString = (): string => { + return String(this.get()) } - } - toString = (): string => { - return String(this.get()) - } - - toJSON = (): T => { - return this.get(); - } + toJSON = (): T => { + return this.get(); + } - valueOf = (): T => { - return this.get(); + valueOf = (): T => { + return this.get(); + } } -} -export default ($: Signalize): void => { - $.Signal = Signal; - $.signal = (defaultValue: T): Signal => new Signal(defaultValue); + $.Signal = SignalStructure; + $.signal = (defaultValue: T): Signal => new SignalStructure(defaultValue); } diff --git a/packages/signalizejs/src/plugins/task.ts b/packages/signalizejs/src/plugins/task.ts index aa30438..ab2fbef 100755 --- a/packages/signalizejs/src/plugins/task.ts +++ b/packages/signalizejs/src/plugins/task.ts @@ -24,7 +24,9 @@ export default ($: Signalize): void => { const tasks: CallableFunction[] = []; - const yieldToMain = (): Promise => new Promise((resolve) => window.setTimeout(resolve, 0)); + const yieldToMain = async (): Promise => { + await new Promise((resolve) => window.setTimeout(resolve, 0)); + } let processing = false;