@adukiorg/anza 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +137 -0
- package/README.md +215 -0
- package/bin/anza.js +63 -0
- package/bin/create.js +150 -0
- package/importmap.json +72 -0
- package/package.json +100 -0
- package/src/core/animations/index.js +55 -0
- package/src/core/animations/play.js +111 -0
- package/src/core/animations/registry.js +54 -0
- package/src/core/animations/scroll.js +50 -0
- package/src/core/animations/tokens.js +58 -0
- package/src/core/animations/usage.md +301 -0
- package/src/core/animations/waapi.js +86 -0
- package/src/core/api/cache.js +120 -0
- package/src/core/api/caches/glob.js +24 -0
- package/src/core/api/caches/index.js +118 -0
- package/src/core/api/events/index.js +75 -0
- package/src/core/api/fetch.js +99 -0
- package/src/core/api/index.js +158 -0
- package/src/core/api/pipeline.js +98 -0
- package/src/core/api/plan.md +209 -0
- package/src/core/api/prefixes/index.js +66 -0
- package/src/core/api/retry.js +69 -0
- package/src/core/api/stream.js +127 -0
- package/src/core/api/upload.js +180 -0
- package/src/core/api/usage.md +206 -0
- package/src/core/events/bus.js +38 -0
- package/src/core/events/delegate.js +79 -0
- package/src/core/events/index.js +26 -0
- package/src/core/events/listen.js +50 -0
- package/src/core/events/missing.md +103 -0
- package/src/core/events/once.js +49 -0
- package/src/core/events/plan.md +177 -0
- package/src/core/events/types/index.js +34 -0
- package/src/core/events/usage.md +107 -0
- package/src/core/offline/bridge.js +51 -0
- package/src/core/offline/clock.js +100 -0
- package/src/core/offline/connectivity.js +116 -0
- package/src/core/offline/index.js +41 -0
- package/src/core/offline/missing.md +89 -0
- package/src/core/offline/plan.md +143 -0
- package/src/core/offline/queue.js +168 -0
- package/src/core/offline/state.js +18 -0
- package/src/core/offline/sync.js +106 -0
- package/src/core/offline/usage.md +273 -0
- package/src/core/platform/guard.js +104 -0
- package/src/core/platform/index.js +42 -0
- package/src/core/platform/missing.md +119 -0
- package/src/core/platform/platform.d.ts +88 -0
- package/src/core/platform/polyfills/anchor.js +79 -0
- package/src/core/platform/polyfills/navigation.js +142 -0
- package/src/core/platform/polyfills/popover.js +142 -0
- package/src/core/platform/polyfills/scheduler.js +194 -0
- package/src/core/platform/polyfills/shadow.js +35 -0
- package/src/core/platform/polyfills/urlpattern.js +119 -0
- package/src/core/platform/supports.js +186 -0
- package/src/core/platform/usage.md +287 -0
- package/src/core/router/cache.js +95 -0
- package/src/core/router/container.js +146 -0
- package/src/core/router/handler.js +52 -0
- package/src/core/router/history.js +120 -0
- package/src/core/router/index.js +158 -0
- package/src/core/router/intercept.js +376 -0
- package/src/core/router/match.js +145 -0
- package/src/core/router/missing.md +716 -0
- package/src/core/router/outlet.js +139 -0
- package/src/core/router/plan.md +370 -0
- package/src/core/router/sync/index.js +16 -0
- package/src/core/router/sync/tab.js +115 -0
- package/src/core/router/sync/transport.js +139 -0
- package/src/core/router/transitions.js +59 -0
- package/src/core/router/usage.md +773 -0
- package/src/core/security/crypto.js +159 -0
- package/src/core/security/index.js +49 -0
- package/src/core/security/missing.md +97 -0
- package/src/core/security/permissions.js +64 -0
- package/src/core/security/sanitize.js +100 -0
- package/src/core/security/usage.md +283 -0
- package/src/core/state/derived.js +117 -0
- package/src/core/state/index.js +23 -0
- package/src/core/state/missing.md +165 -0
- package/src/core/state/persist.js +284 -0
- package/src/core/state/store.js +308 -0
- package/src/core/state/sync.js +46 -0
- package/src/core/state/usage.md +440 -0
- package/src/core/storage/cache.js +83 -0
- package/src/core/storage/idb.js +196 -0
- package/src/core/storage/index.js +373 -0
- package/src/core/storage/lru.js +107 -0
- package/src/core/storage/missing.md +165 -0
- package/src/core/storage/opfs.js +190 -0
- package/src/core/storage/plan.md +69 -0
- package/src/core/storage/quota.js +69 -0
- package/src/core/storage/usage.md +226 -0
- package/src/core/ui/base.js +50 -0
- package/src/core/ui/define/container.js +82 -0
- package/src/core/ui/define/define.js +12 -0
- package/src/core/ui/define/element.js +390 -0
- package/src/core/ui/define/index.js +9 -0
- package/src/core/ui/define/orchestrator.js +105 -0
- package/src/core/ui/define/proxy.js +644 -0
- package/src/core/ui/define/state.js +6 -0
- package/src/core/ui/define/utils.js +134 -0
- package/src/core/ui/implementation.md +170 -0
- package/src/core/ui/index.js +41 -0
- package/src/core/ui/observe.js +117 -0
- package/src/core/ui/plan.md +510 -0
- package/src/core/ui/schedule.js +60 -0
- package/src/core/ui/template.js +37 -0
- package/src/core/ui/transitions.js +37 -0
- package/src/core/ui/ui.types.md +890 -0
- package/src/core/ui/usage.md +1124 -0
- package/src/core/ui/watch.md +346 -0
- package/src/core/workers/broadcast.js +138 -0
- package/src/core/workers/dedicated.js +153 -0
- package/src/core/workers/index.js +169 -0
- package/src/core/workers/locks.js +160 -0
- package/src/core/workers/offscreen.js +166 -0
- package/src/core/workers/plan.md +381 -0
- package/src/core/workers/pool.js +267 -0
- package/src/core/workers/shared.js +137 -0
- package/src/core/workers/usage.md +622 -0
- package/src/elements/base.js +12 -0
- package/src/elements/data/card/index.html +9 -0
- package/src/elements/data/card/index.js +19 -0
- package/src/elements/data/card/index.tags.json +1 -0
- package/src/elements/data/card/style.css +46 -0
- package/src/elements/data/chart/index.html +1 -0
- package/src/elements/data/chart/index.js +143 -0
- package/src/elements/data/chart/index.tags.json +1 -0
- package/src/elements/data/chart/style.css +13 -0
- package/src/elements/data/list/index.html +3 -0
- package/src/elements/data/list/index.js +19 -0
- package/src/elements/data/list/index.tags.json +1 -0
- package/src/elements/data/list/style.css +39 -0
- package/src/elements/data/stat/index.html +9 -0
- package/src/elements/data/stat/index.js +19 -0
- package/src/elements/data/stat/index.tags.json +1 -0
- package/src/elements/data/stat/style.css +50 -0
- package/src/elements/data/table/index.html +1 -0
- package/src/elements/data/table/index.js +16 -0
- package/src/elements/data/table/index.tags.json +1 -0
- package/src/elements/data/table/style.css +50 -0
- package/src/elements/feedback/alert/index.html +11 -0
- package/src/elements/feedback/alert/index.js +28 -0
- package/src/elements/feedback/alert/index.tags.json +1 -0
- package/src/elements/feedback/alert/style.css +75 -0
- package/src/elements/feedback/empty/index.html +13 -0
- package/src/elements/feedback/empty/index.js +34 -0
- package/src/elements/feedback/empty/index.tags.json +1 -0
- package/src/elements/feedback/empty/style.css +45 -0
- package/src/elements/feedback/progress/index.html +7 -0
- package/src/elements/feedback/progress/index.js +46 -0
- package/src/elements/feedback/progress/index.tags.json +1 -0
- package/src/elements/feedback/progress/style.css +36 -0
- package/src/elements/feedback/skeleton/index.html +1 -0
- package/src/elements/feedback/skeleton/index.js +78 -0
- package/src/elements/feedback/skeleton/index.tags.json +1 -0
- package/src/elements/feedback/skeleton/style.css +28 -0
- package/src/elements/feedback/toast/index.html +3 -0
- package/src/elements/feedback/toast/index.js +65 -0
- package/src/elements/feedback/toast/index.tags.json +1 -0
- package/src/elements/feedback/toast/style.css +36 -0
- package/src/elements/forms/checkbox/index.html +7 -0
- package/src/elements/forms/checkbox/index.js +104 -0
- package/src/elements/forms/checkbox/index.tags.json +1 -0
- package/src/elements/forms/checkbox/style.css +86 -0
- package/src/elements/forms/field/index.html +13 -0
- package/src/elements/forms/field/index.js +42 -0
- package/src/elements/forms/field/index.tags.json +1 -0
- package/src/elements/forms/field/style.css +42 -0
- package/src/elements/forms/form/index.html +3 -0
- package/src/elements/forms/form/index.js +122 -0
- package/src/elements/forms/form/index.tags.json +1 -0
- package/src/elements/forms/form/style.css +11 -0
- package/src/elements/forms/input/index.html +4 -0
- package/src/elements/forms/input/index.js +103 -0
- package/src/elements/forms/input/index.tags.json +1 -0
- package/src/elements/forms/input/style.css +39 -0
- package/src/elements/forms/radio/index.html +4 -0
- package/src/elements/forms/radio/index.js +109 -0
- package/src/elements/forms/radio/index.tags.json +1 -0
- package/src/elements/forms/radio/style.css +65 -0
- package/src/elements/forms/select/index.html +9 -0
- package/src/elements/forms/select/index.js +114 -0
- package/src/elements/forms/select/index.tags.json +1 -0
- package/src/elements/forms/select/style.css +95 -0
- package/src/elements/forms/textarea/index.html +4 -0
- package/src/elements/forms/textarea/index.js +115 -0
- package/src/elements/forms/textarea/index.tags.json +1 -0
- package/src/elements/forms/textarea/style.css +46 -0
- package/src/elements/forms/toggle/index.html +4 -0
- package/src/elements/forms/toggle/index.js +89 -0
- package/src/elements/forms/toggle/index.tags.json +1 -0
- package/src/elements/forms/toggle/style.css +63 -0
- package/src/elements/forms/upload/index.html +13 -0
- package/src/elements/forms/upload/index.js +120 -0
- package/src/elements/forms/upload/index.tags.json +1 -0
- package/src/elements/forms/upload/style.css +61 -0
- package/src/elements/index.js +71 -0
- package/src/elements/layout/app/index.html +7 -0
- package/src/elements/layout/app/index.js +16 -0
- package/src/elements/layout/app/index.tags.json +1 -0
- package/src/elements/layout/app/style.css +41 -0
- package/src/elements/layout/grid/index.html +3 -0
- package/src/elements/layout/grid/index.js +41 -0
- package/src/elements/layout/grid/index.tags.json +1 -0
- package/src/elements/layout/grid/style.css +12 -0
- package/src/elements/layout/header/index.html +8 -0
- package/src/elements/layout/header/index.js +16 -0
- package/src/elements/layout/header/index.tags.json +1 -0
- package/src/elements/layout/header/style.css +28 -0
- package/src/elements/layout/scroll/index.html +3 -0
- package/src/elements/layout/scroll/index.js +19 -0
- package/src/elements/layout/scroll/index.tags.json +1 -0
- package/src/elements/layout/scroll/style.css +24 -0
- package/src/elements/layout/sidebar/index.html +3 -0
- package/src/elements/layout/sidebar/index.js +24 -0
- package/src/elements/layout/sidebar/index.tags.json +1 -0
- package/src/elements/layout/sidebar/style.css +30 -0
- package/src/elements/layout/split/index.html +3 -0
- package/src/elements/layout/split/index.js +18 -0
- package/src/elements/layout/split/index.tags.json +1 -0
- package/src/elements/layout/split/style.css +28 -0
- package/src/elements/layout/stack/index.html +3 -0
- package/src/elements/layout/stack/index.js +31 -0
- package/src/elements/layout/stack/index.tags.json +1 -0
- package/src/elements/layout/stack/style.css +15 -0
- package/src/elements/layout/surface/index.html +3 -0
- package/src/elements/layout/surface/index.js +19 -0
- package/src/elements/layout/surface/index.tags.json +1 -0
- package/src/elements/layout/surface/style.css +29 -0
- package/src/elements/navigation/breadcrumb/index.html +5 -0
- package/src/elements/navigation/breadcrumb/index.js +16 -0
- package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
- package/src/elements/navigation/breadcrumb/style.css +36 -0
- package/src/elements/navigation/nav/index.html +3 -0
- package/src/elements/navigation/nav/index.js +24 -0
- package/src/elements/navigation/nav/index.tags.json +1 -0
- package/src/elements/navigation/nav/style.css +38 -0
- package/src/elements/navigation/pagination/index.html +3 -0
- package/src/elements/navigation/pagination/index.js +94 -0
- package/src/elements/navigation/pagination/index.tags.json +1 -0
- package/src/elements/navigation/pagination/style.css +39 -0
- package/src/elements/navigation/steps/index.html +6 -0
- package/src/elements/navigation/steps/index.js +64 -0
- package/src/elements/navigation/steps/index.tags.json +1 -0
- package/src/elements/navigation/steps/style.css +78 -0
- package/src/elements/navigation/tabs/index.html +6 -0
- package/src/elements/navigation/tabs/index.js +132 -0
- package/src/elements/navigation/tabs/index.tags.json +1 -0
- package/src/elements/navigation/tabs/style.css +52 -0
- package/src/elements/overlay/dialog/index.html +5 -0
- package/src/elements/overlay/dialog/index.js +57 -0
- package/src/elements/overlay/dialog/index.tags.json +1 -0
- package/src/elements/overlay/dialog/style.css +31 -0
- package/src/elements/overlay/drawer/index.html +3 -0
- package/src/elements/overlay/drawer/index.js +56 -0
- package/src/elements/overlay/drawer/index.tags.json +1 -0
- package/src/elements/overlay/drawer/style.css +48 -0
- package/src/elements/overlay/menu/index.html +3 -0
- package/src/elements/overlay/menu/index.js +107 -0
- package/src/elements/overlay/menu/index.tags.json +1 -0
- package/src/elements/overlay/menu/style.css +43 -0
- package/src/elements/overlay/popover/index.html +3 -0
- package/src/elements/overlay/popover/index.js +44 -0
- package/src/elements/overlay/popover/index.tags.json +1 -0
- package/src/elements/overlay/popover/style.css +21 -0
- package/src/elements/overlay/sheet/index.html +8 -0
- package/src/elements/overlay/sheet/index.js +105 -0
- package/src/elements/overlay/sheet/index.tags.json +1 -0
- package/src/elements/overlay/sheet/style.css +64 -0
- package/src/elements/overlay/tooltip/index.html +6 -0
- package/src/elements/overlay/tooltip/index.js +16 -0
- package/src/elements/overlay/tooltip/index.tags.json +1 -0
- package/src/elements/overlay/tooltip/style.css +41 -0
- package/src/elements/primitives/avatar/index.html +2 -0
- package/src/elements/primitives/avatar/index.js +79 -0
- package/src/elements/primitives/avatar/index.tags.json +1 -0
- package/src/elements/primitives/avatar/style.css +36 -0
- package/src/elements/primitives/badge/index.html +3 -0
- package/src/elements/primitives/badge/index.js +20 -0
- package/src/elements/primitives/badge/index.tags.json +1 -0
- package/src/elements/primitives/badge/style.css +67 -0
- package/src/elements/primitives/button/index.html +3 -0
- package/src/elements/primitives/button/index.js +61 -0
- package/src/elements/primitives/button/index.tags.json +1 -0
- package/src/elements/primitives/button/style.css +66 -0
- package/src/elements/primitives/divider/index.html +1 -0
- package/src/elements/primitives/divider/index.js +43 -0
- package/src/elements/primitives/divider/index.tags.json +1 -0
- package/src/elements/primitives/divider/style.css +39 -0
- package/src/elements/primitives/icon/index.html +3 -0
- package/src/elements/primitives/icon/index.js +66 -0
- package/src/elements/primitives/icon/index.tags.json +1 -0
- package/src/elements/primitives/icon/style.css +20 -0
- package/src/elements/primitives/link/index.html +3 -0
- package/src/elements/primitives/link/index.js +129 -0
- package/src/elements/primitives/link/index.tags.json +1 -0
- package/src/elements/primitives/link/style.css +40 -0
- package/src/elements/primitives/spinner/index.html +1 -0
- package/src/elements/primitives/spinner/index.js +62 -0
- package/src/elements/primitives/spinner/index.tags.json +1 -0
- package/src/elements/primitives/spinner/style.css +20 -0
- package/src/elements/primitives/text/index.html +1 -0
- package/src/elements/primitives/text/index.js +79 -0
- package/src/elements/primitives/text/index.tags.json +1 -0
- package/src/elements/primitives/text/style.css +25 -0
- package/src/index.js +23 -0
- package/src/styles/base.css +66 -0
- package/src/styles/index.css +10 -0
- package/src/styles/layers.css +9 -0
- package/src/styles/reset.css +66 -0
- package/src/sw/activate.js +51 -0
- package/src/sw/expire.js +47 -0
- package/src/sw/index.js +28 -0
- package/src/sw/install.js +35 -0
- package/src/sw/push.js +58 -0
- package/src/sw/queue.js +60 -0
- package/src/sw/routes.js +71 -0
- package/src/sw/strategies.js +247 -0
- package/src/sw/sync.js +80 -0
- package/src/tokens/index.css +26 -0
- package/src/tokens/primitives/colors.css +54 -0
- package/src/tokens/primitives/motion.css +34 -0
- package/src/tokens/primitives/radius.css +16 -0
- package/src/tokens/primitives/shadow.css +34 -0
- package/src/tokens/primitives/spacing.css +27 -0
- package/src/tokens/primitives/typography.css +46 -0
- package/src/tokens/primitives/zindex.css +18 -0
- package/src/tokens/registered/colors.css +133 -0
- package/src/tokens/registered/dimensions.css +31 -0
- package/src/tokens/semantic/components.css +125 -0
- package/src/tokens/semantic/contrast.css +33 -0
- package/src/tokens/semantic/dark.css +61 -0
- package/src/tokens/semantic/light.css +64 -0
- package/types/core/animations/index.d.ts +52 -0
- package/types/core/api/index.d.ts +68 -0
- package/types/core/events/index.d.ts +50 -0
- package/types/core/offline/index.d.ts +68 -0
- package/types/core/platform/index.d.ts +60 -0
- package/types/core/router/index.d.ts +203 -0
- package/types/core/security/index.d.ts +33 -0
- package/types/core/state/index.d.ts +68 -0
- package/types/core/storage/index.d.ts +40 -0
- package/types/core/ui/index.d.ts +446 -0
- package/types/core/workers/index.d.ts +221 -0
- package/types/elements/index.d.ts +150 -0
- package/types/index.d.ts +18 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/events/index.js
|
|
3
|
+
*
|
|
4
|
+
* Public event architecture entry point.
|
|
5
|
+
* Aggregates EventBus dispatchers, Shadow-DOM composed delegation, single-event awaits,
|
|
6
|
+
* progressive passive listener registrations, and standardized system event names.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 10 — Event Architecture §1, §9
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { bus, EventBus } from './bus.js';
|
|
12
|
+
import { delegate } from './delegate.js';
|
|
13
|
+
import { once } from './once.js';
|
|
14
|
+
import { listen } from './listen.js';
|
|
15
|
+
import { names } from './types/index.js';
|
|
16
|
+
|
|
17
|
+
export const events = {
|
|
18
|
+
emit: (type, detail) => bus.emit(type, detail),
|
|
19
|
+
on: (type, fn, signal) => bus.on(type, fn, signal),
|
|
20
|
+
delegate,
|
|
21
|
+
once,
|
|
22
|
+
listen,
|
|
23
|
+
names
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { bus, EventBus, delegate, once, listen, names };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/events/listen.js
|
|
3
|
+
*
|
|
4
|
+
* High-performance, memory-safe event listener aggregator.
|
|
5
|
+
* Enforces touch/wheel events to passive: true by default to protect INP performance,
|
|
6
|
+
* while seamlessly supporting AbortSignal-gated cleanups and disposer patterns.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 10 — Event Architecture §2, §3, §13
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Attaches a memory-safe event listener to a target.
|
|
13
|
+
* Automatically defaults touch/wheel events to passive: true unless explicitly overridden.
|
|
14
|
+
*
|
|
15
|
+
* @param {EventTarget} target - The target element, window, document, or global bus.
|
|
16
|
+
* @param {string} type - The event name to subscribe to.
|
|
17
|
+
* @param {Function} handler - The listener callback function.
|
|
18
|
+
* @param {Object} [options={}] - Standard addEventListener options.
|
|
19
|
+
* @returns {Function} A disposer function that unregisters the listener.
|
|
20
|
+
*/
|
|
21
|
+
export function listen(target, type, handler, options = {}) {
|
|
22
|
+
const signal = options.signal;
|
|
23
|
+
if (signal?.aborted) return () => {};
|
|
24
|
+
|
|
25
|
+
// Automatically default touch and wheel events to passive: true to prevent scroll blocking
|
|
26
|
+
const isPassiveTarget = ['touchstart', 'touchmove', 'wheel', 'mousewheel'].includes(type);
|
|
27
|
+
const listenerOpts = {
|
|
28
|
+
passive: isPassiveTarget ? true : undefined,
|
|
29
|
+
...options
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const listener = (event) => {
|
|
33
|
+
handler(event);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
target.addEventListener(type, listener, listenerOpts);
|
|
37
|
+
|
|
38
|
+
const dispose = () => {
|
|
39
|
+
target.removeEventListener(type, listener, listenerOpts);
|
|
40
|
+
if (signal) {
|
|
41
|
+
signal.removeEventListener('abort', dispose);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (signal) {
|
|
46
|
+
signal.addEventListener('abort', dispose, { once: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return dispose;
|
|
50
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Event Missing Support
|
|
2
|
+
|
|
3
|
+
This document tracks remaining support, runtime gaps, type mismatches, and state/storage synchronization improvements for `src/core/events`. Implemented behavior belongs in `usage.md`; unsupported, under-tested, or performance-gated behavior belongs here until it is built and verified.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 0. Toolchain and Naming Rules
|
|
8
|
+
|
|
9
|
+
Heavy compilation, static analysis, and code generation belong in `tools/`, not in browser runtime modules.
|
|
10
|
+
|
|
11
|
+
Current toolchain event requirements:
|
|
12
|
+
- Statically verify event name strings inside `dispatchEvent` to ensure they are registered in the names registry.
|
|
13
|
+
- Detect event listeners registered on `window` or `document` that do not supply an `AbortSignal`.
|
|
14
|
+
|
|
15
|
+
Naming rules for event support:
|
|
16
|
+
- Prefer one-word files: `bus.js`, `delegate.js`, `once.js`, `listen.js`, `index.js`.
|
|
17
|
+
- Plural folders: `tests/`, `types/`.
|
|
18
|
+
- Single-word methods: `on`, `emit`, `delegate`, `once`, `listen`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Critical Runtime and Type Gaps
|
|
23
|
+
|
|
24
|
+
### 1.1. Native EventTarget Base Composition for EventBus
|
|
25
|
+
- **Status**: Runtime gap & design code smell.
|
|
26
|
+
- **Files**:
|
|
27
|
+
- `src/core/events/bus.js`
|
|
28
|
+
- **Problem**:
|
|
29
|
+
The `EventBus` class is implemented as a custom subscriber registry utilizing a private Map. The architectural specifications state that the event bus must compose the browser's native `EventTarget` interface. By making `EventBus` extend `EventTarget`, we inherit native performance optimizations and DOM-standard listener registration, while maintaining backward-compatible helper wrappers.
|
|
30
|
+
- **Expected Support**:
|
|
31
|
+
- Refactor `EventBus` in `bus.js` to extend `EventTarget`.
|
|
32
|
+
- Compose standard `EventTarget` capabilities, implementing `on()` and `emit()` as simple, clean wrappers on top of `addEventListener()` and `dispatchEvent()`.
|
|
33
|
+
|
|
34
|
+
### 1.2. Missing TypeScript Declarations
|
|
35
|
+
- **Status**: Type mismatch.
|
|
36
|
+
- **Files**:
|
|
37
|
+
- `types/core/events/index.d.ts`
|
|
38
|
+
- **Problem**:
|
|
39
|
+
The TypeScript definitions for events do not export the `listen` function or the `names` namespace registry, even though both are public exports in `src/core/events/index.js`. Additionally, the declaration of the `EventBus` class does not denote its native `EventTarget` inheritance.
|
|
40
|
+
- **Expected Support**:
|
|
41
|
+
- Declare and export `listen` and `names` inside `types/core/events/index.d.ts`.
|
|
42
|
+
- Update `EventBus` type definition to extend `EventTarget`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. State & Storage Synchronization Gaps
|
|
47
|
+
|
|
48
|
+
### 2.1. Missing Connectivity Broadcasts on the Event Bus
|
|
49
|
+
- **Status**: Synchronization gap.
|
|
50
|
+
- **Files**:
|
|
51
|
+
- `src/core/offline/connectivity.js`
|
|
52
|
+
- **Problem**:
|
|
53
|
+
When network connectivity transitions between online and offline states, the connectivity monitor correctly updates the reactive state store but does not broadcast matching events to the global Event Bus. Consumers listening on the bus for `connectivity:online` and `connectivity:offline` are never notified.
|
|
54
|
+
- **Expected Support**:
|
|
55
|
+
- Synchronize connectivity updates by importing the global `bus` and dispatching `connectivity:online` or `connectivity:offline` events using standard naming constants.
|
|
56
|
+
|
|
57
|
+
### 2.2. Offline Sync Queue Broadcasts
|
|
58
|
+
- **Status**: Synchronization gap.
|
|
59
|
+
- **Files**:
|
|
60
|
+
- `src/core/offline/sync.js`
|
|
61
|
+
- **Problem**:
|
|
62
|
+
Sync queue completion, replays, or failures are handled silently. Parent application views cannot react to queue status changes.
|
|
63
|
+
- **Expected Support**:
|
|
64
|
+
- Emit synchronization telemetry events on the Event Bus when tasks are successfully replayed or if they fail permanently.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 3. Test Coverage Gaps
|
|
69
|
+
|
|
70
|
+
### 3.1. Single Event Awaiter Verification
|
|
71
|
+
- **Status**: Test gap.
|
|
72
|
+
- **Files**:
|
|
73
|
+
- `tests/core/events/once.test.js` [NEW]
|
|
74
|
+
- **Needed Coverage**:
|
|
75
|
+
- Test that `once()` resolves immediately upon event firing.
|
|
76
|
+
- Test that `once()` cleans up the registered listener after firing.
|
|
77
|
+
- Test that `once()` rejects with an AbortError when its signal is aborted.
|
|
78
|
+
|
|
79
|
+
### 3.2. Listener Aggregation Edge Cases
|
|
80
|
+
- **Status**: Test gap.
|
|
81
|
+
- **Files**:
|
|
82
|
+
- `tests/core/events/listen.test.js`
|
|
83
|
+
- **Needed Coverage**:
|
|
84
|
+
- Verify that options like `once: true` or `capture: true` are correctly passed through to the native target.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. Suggested Implementation Order
|
|
89
|
+
|
|
90
|
+
1. Refactor `EventBus` to extend `EventTarget` inside `src/core/events/bus.js`.
|
|
91
|
+
2. Add connectivity event emission inside `src/core/offline/connectivity.js`.
|
|
92
|
+
3. Create the type declarations for `listen` and `names` in `types/core/events/index.d.ts`.
|
|
93
|
+
4. Add the unit test suite `tests/core/events/once.test.js`.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 5. Done Criteria
|
|
98
|
+
|
|
99
|
+
This missing-support list is complete when:
|
|
100
|
+
- `EventBus` extends `EventTarget` and passes all existing/new test cases.
|
|
101
|
+
- Connectivity status transitions emit corresponding events on the `bus`.
|
|
102
|
+
- All public exports of the events module are fully typed.
|
|
103
|
+
- `once()` and `listen()` are thoroughly covered by tests.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/events/once.js
|
|
3
|
+
*
|
|
4
|
+
* Promise-wrapped single-event listener.
|
|
5
|
+
* Dynamically resolves a promise upon event completion, utilizing native browser cleanup properties.
|
|
6
|
+
*
|
|
7
|
+
* Source: doc 10 — Event Architecture §2, §3
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Awaits a single event occurrence on a given target, resolving with the Event object.
|
|
12
|
+
*/
|
|
13
|
+
export function once(target, type, options = {}) {
|
|
14
|
+
const { signal, ...listenerOpts } = options;
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
if (signal?.aborted) {
|
|
18
|
+
return reject(new Error('Operation aborted'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let onAbort;
|
|
22
|
+
|
|
23
|
+
const cleanup = () => {
|
|
24
|
+
if (signal && onAbort) {
|
|
25
|
+
signal.removeEventListener('abort', onAbort);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const callback = (event) => {
|
|
30
|
+
cleanup();
|
|
31
|
+
resolve(event);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Modern browsers support signal natively inside addEventListener options
|
|
35
|
+
target.addEventListener(type, callback, {
|
|
36
|
+
once: true,
|
|
37
|
+
...listenerOpts,
|
|
38
|
+
signal
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (signal) {
|
|
42
|
+
onAbort = () => {
|
|
43
|
+
cleanup();
|
|
44
|
+
reject(new Error('Operation aborted'));
|
|
45
|
+
};
|
|
46
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Native-First Event Architecture Spec & Plan
|
|
2
|
+
|
|
3
|
+
This document establishes the comprehensive architectural plan, technical specifications, and system guidelines for the Native-First Event Architecture inside `src/core/events/`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Architectural Philosophy & Principles
|
|
8
|
+
|
|
9
|
+
The event system in this platform relies on the browser's high-performance native event-dispatch loop rather than recreating arbitrary JavaScript Pub/Sub emitters. We treat standard `EventTarget`, `Event`, and `CustomEvent` APIs as primary scheduling primitives:
|
|
10
|
+
|
|
11
|
+
1. **Zero Library Bloat:** We leverage native rendering engine loops. Subscribing to, firing, and propagating events has zero impact on bundle size.
|
|
12
|
+
2. **Memory Safety by Design:** No event handler should persist past its lifecycle. Every component uses a dedicated, centralized `AbortController` bound to the connection state (`disconnectedCallback`).
|
|
13
|
+
3. **Encapsulated & Composed Boundaries:** Communication boundaries respect the Shadow DOM layout, utilizing retargeting, flat tree propagation, and `composedPath` traversal for seamless cross-shadow event routing.
|
|
14
|
+
4. **Unidirectional Event Flow:**
|
|
15
|
+
* **Upward Flow:** Custom events bubbled from child to ancestor (`{ bubbles: true, composed: true }`).
|
|
16
|
+
* **Downward Flow:** Direct property/method invocations from parent to child.
|
|
17
|
+
* **Decoupled System Flow:** Global Event Bus singleton (`bus.js`) reserved exclusively for out-of-band cross-cutting system telemetry (e.g., Auth, Connectivity, SW).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. Review of Current Architecture
|
|
22
|
+
|
|
23
|
+
The `src/core/events/` module is divided into four highly focused primitives:
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
graph TD
|
|
27
|
+
A[index.js] --> B[bus.js]
|
|
28
|
+
A --> C[delegate.js]
|
|
29
|
+
A --> D[once.js]
|
|
30
|
+
|
|
31
|
+
subgraph Core Modules
|
|
32
|
+
B
|
|
33
|
+
C
|
|
34
|
+
D
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### A. The Global Event Bus (`bus.js`)
|
|
39
|
+
|
|
40
|
+
* **Purpose:** A decoupled central hub for system-wide notifications that do not map naturally to a DOM lineage.
|
|
41
|
+
* **Mechanism:** Leverages an optimized internal `Map<string, Set<{ fn }>>`.
|
|
42
|
+
* **Key Feature:** Highly performant execution with snapshotting (`[...set]`) to avoid runtime list mutations during event dispatch.
|
|
43
|
+
* **Cleanup:** Integrates `AbortSignal` listeners to automatically execute the `dispose()` callback when component lifecycles cancel.
|
|
44
|
+
|
|
45
|
+
### B. Event Delegation (`delegate.js`)
|
|
46
|
+
|
|
47
|
+
* **Purpose:** Attaches a single root listener to dynamically handle events from dynamic descendants, minimizing handler creation and memory consumption.
|
|
48
|
+
* **Boundary Traversal:** Traverses through Web Component boundaries by evaluating `event.composedPath()`, checking if descendants match specified queries via `.matches(selector)`.
|
|
49
|
+
* **Cleanup:** Disposes of listeners seamlessly on signal abort.
|
|
50
|
+
|
|
51
|
+
### C. Single Event Awaiter (`once.js`)
|
|
52
|
+
|
|
53
|
+
* **Purpose:** Wraps standard event listener triggers in a Promise, allowing asynchronous waiting for one-off user or network events.
|
|
54
|
+
* **Mechanism:** Passes `{ once: true }` down to the browser's `addEventListener` and handles clean early rejection if the abort signal triggers first.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 3. Composed Event Routing (Shadow DOM Integration)
|
|
59
|
+
|
|
60
|
+
To traverse the Shadow DOM encapsulation without breaking modular boundaries, our event system implements specific propagation combinations:
|
|
61
|
+
|
|
62
|
+
| Propagation Mode | Configuration | Boundaries Crossed | Target Behavior | Recommended Use Case |
|
|
63
|
+
|---|---|---|---|---|
|
|
64
|
+
| **Fully Encapsulated** | `bubbles: false`, `composed: false` | None | Stays on target element | Internal component state ticks |
|
|
65
|
+
| **Shadow-Local** | `bubbles: true`, `composed: false` | None (Stops at Shadow Root) | Normal bubbling | Internal compound component parts |
|
|
66
|
+
| **Composed Bubbling** | `bubbles: true`, `composed: true` | All boundaries | Retargeted to Shadow Host | Standard child-to-parent callbacks |
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
[Document Root]
|
|
70
|
+
▲
|
|
71
|
+
│ (Bubbles & Composed)
|
|
72
|
+
[Shadow Host (Component)] ◄─── (Retargeted target appears here to Light DOM)
|
|
73
|
+
┌───┴───┐
|
|
74
|
+
│ Shadow│ (Bubbles inside local tree)
|
|
75
|
+
│ Root │
|
|
76
|
+
└───┬───┘
|
|
77
|
+
▲
|
|
78
|
+
│ (Originates)
|
|
79
|
+
[Shadow Target Element]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Safe composedPath Traversal
|
|
83
|
+
|
|
84
|
+
Standard event delegation utilizing `event.target` fails across Shadow DOM limits because of browser-enforced retargeting. `delegate.js` safely bypasses this restriction by reading `event.composedPath()`.
|
|
85
|
+
|
|
86
|
+
* **Rule:** Always inspect `composedPath()[0]` when you need to resolve the exact physical leaf node that triggered the interaction.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 4. Unidirectional Data Flow & Communication Contract
|
|
91
|
+
|
|
92
|
+
We enforce highly strict API paths to guarantee components maintain unidirectional, predictable updates:
|
|
93
|
+
|
|
94
|
+
```mermaid
|
|
95
|
+
sequenceDiagram
|
|
96
|
+
participant Parent as Parent Component
|
|
97
|
+
participant Child as Child Component (Shadow DOM)
|
|
98
|
+
|
|
99
|
+
rect rgb(20, 20, 30)
|
|
100
|
+
Note over Parent, Child: 1. Downward Communication
|
|
101
|
+
Parent->>Child: Direct Property Assignment (child.items = data)
|
|
102
|
+
Parent->>Child: Imperative Method Call (child.focus())
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
rect rgb(30, 20, 20)
|
|
106
|
+
Note over Parent, Child: 2. Upward Communication
|
|
107
|
+
Child-->>Parent: CustomEvent("domain:action", { bubbles, composed })
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
* **No Event Sinks Downward:** Never dispatch an event downward on a child element if setting a direct property or method accomplishes the same task.
|
|
112
|
+
* **No Diagonal Bus Calls:** Avoid using the global Event Bus (`bus.js`) to coordinate between nearby siblings. Let their common ancestor mediate their states.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 5. Memory Safety & Garbage Collection (GC) Boundary
|
|
117
|
+
|
|
118
|
+
Event listeners keep their host components alive in memory because closures bind `this` strongly. The following strategies must be strictly applied:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
┌─────────────────────────────────┐
|
|
122
|
+
│ Garbage Collector │
|
|
123
|
+
└────────────────┬────────────────┘
|
|
124
|
+
│ (Checks for reference paths)
|
|
125
|
+
▼
|
|
126
|
+
┌──────────────────┐ ┌──────────────┐ ┌──────────────────┐
|
|
127
|
+
│ Component Target │ ◄──── │ AbortSignal │ ◄──── │ Event Listener │
|
|
128
|
+
└──────────────────┘ └──────────────┘ └──────────────────┘
|
|
129
|
+
(Eligible for GC) (Aborted = Detached) (Strong Ref broken)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
1. **Deterministic Teardown:** Ensure *every* single event listener attached outside the element's local DOM (e.g. on `window`, `document`, or `bus`) is attached with `signal: this.#controller.signal`.
|
|
133
|
+
2. **WeakMap for Auxiliary Metadata:** When maintaining auxiliary state outside the element class scope, store references in a `WeakMap`. This allows components to be GC'd cleanly when removed from the DOM:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
const elementStates = new WeakMap();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
3. **Avoid Dynamic Lambda Closures:** Prefer class methods or statically bound private arrow fields. Generating anonymous arrow functions inside rendering paths causes unnecessary garbage allocations:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// AVOID:
|
|
143
|
+
this.addEventListener('click', (e) => this.handle(e));
|
|
144
|
+
|
|
145
|
+
// PREFER:
|
|
146
|
+
this.addEventListener('click', this.handle, { signal });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 6. Actionable Implementation & Optimization Enhancements
|
|
152
|
+
|
|
153
|
+
To make our current system even more robust, we will implement three core enhancements:
|
|
154
|
+
|
|
155
|
+
### 1. Optimize `delegate.js` (Fast Path Selector Matching)
|
|
156
|
+
|
|
157
|
+
* **Status:** Current implementation iterates through the entire composed path.
|
|
158
|
+
* **Enhancement:** Cache selector match results or stop iteration immediately when traversing past the designated boundaries.
|
|
159
|
+
|
|
160
|
+
### 2. Standardized Namespace Registry for System Events
|
|
161
|
+
|
|
162
|
+
* **Status:** Events can be dispatched on the bus using freeform strings.
|
|
163
|
+
* **Enhancement:** Maintain a central, typed list of valid events in `plan.md` and export standard constants to prevent typos (e.g., `events.NAMES.AUTH_SIGNED_IN`).
|
|
164
|
+
|
|
165
|
+
### 3. Progressive Passive Hooks
|
|
166
|
+
|
|
167
|
+
* **Status:** Native scroll listeners default to passive, but custom handlers do not.
|
|
168
|
+
* **Enhancement:** Automatically default touch and wheel event listeners inside components to `{ passive: true }` to enforce Interaction to Next Paint (INP) excellence.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 7. Verification & Telemetry Performance Testing
|
|
173
|
+
|
|
174
|
+
We verify the event architecture through several layers:
|
|
175
|
+
|
|
176
|
+
* **Automated Unit Tests:** Assert composition properties, event delegation boundary crossing, and AbortSignal detaching.
|
|
177
|
+
* **INP Measurement:** Utilize the `PerformanceObserver` with `type: 'event'` to guarantee that no event handler blocks the main thread for > 50ms.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/events/types/index.js
|
|
3
|
+
*
|
|
4
|
+
* Centralized System Event Name constants registry.
|
|
5
|
+
* Maps standard, cross-cutting system telemetry events to prevent typos.
|
|
6
|
+
*
|
|
7
|
+
* Source: doc 10 — Event Architecture §10, §11
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const names = {
|
|
11
|
+
// Authentication Lifecycle Events
|
|
12
|
+
auth: {
|
|
13
|
+
signedin: 'auth:signedin',
|
|
14
|
+
signedout: 'auth:signedout',
|
|
15
|
+
refreshed: 'auth:refreshed'
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// Network Connectivity Lifecycle Events
|
|
19
|
+
connectivity: {
|
|
20
|
+
online: 'connectivity:online',
|
|
21
|
+
offline: 'connectivity:offline'
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// User Preference Configuration Changes
|
|
25
|
+
preference: {
|
|
26
|
+
changed: 'preference:changed'
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Service Worker Status and Communication Channels
|
|
30
|
+
sw: {
|
|
31
|
+
updated: 'sw:updated',
|
|
32
|
+
message: 'sw:message'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Native Events Usage Guide
|
|
2
|
+
|
|
3
|
+
The events layer is a thin, memory-safe surface over the platform: a global event
|
|
4
|
+
bus built on `EventTarget`, Shadow-DOM-aware delegation, a promise-based single
|
|
5
|
+
awaiter, a progressive passive listener, and a registry of standard system event
|
|
6
|
+
names. Every subscription supports `AbortSignal` cleanup.
|
|
7
|
+
|
|
8
|
+
Import from the events entry point:
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import { events } from '@adukiorg/anza/events';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Named exports are also available:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { bus, EventBus, delegate, once, listen, names } from '@adukiorg/anza/events';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 1. Choosing an API
|
|
21
|
+
|
|
22
|
+
| Need | Use |
|
|
23
|
+
|---|---|
|
|
24
|
+
| App-wide pub/sub | `events.emit`, `events.on` (or `bus`) |
|
|
25
|
+
| Delegate DOM events | `events.delegate` |
|
|
26
|
+
| Await a single event | `events.once` |
|
|
27
|
+
| Add one passive-safe listener | `events.listen` |
|
|
28
|
+
| Standard event name constants | `events.names` |
|
|
29
|
+
|
|
30
|
+
## 2. Global bus
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
const ctrl = new AbortController();
|
|
34
|
+
|
|
35
|
+
// Subscribe (auto-cleans up when the signal aborts); returns a disposer too.
|
|
36
|
+
const off = events.on('user:purchased', (event) => {
|
|
37
|
+
const { itemId, price } = event.detail;
|
|
38
|
+
}, ctrl.signal);
|
|
39
|
+
|
|
40
|
+
events.emit('user:purchased', { itemId: 42, price: 9.99 });
|
|
41
|
+
|
|
42
|
+
off(); // manual teardown
|
|
43
|
+
ctrl.abort(); // or signal-based teardown
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`bus` is an `EventBus extends EventTarget`, so `bus.addEventListener` /
|
|
47
|
+
`bus.dispatchEvent` work too; `bus.on` / `bus.emit` are convenience wrappers.
|
|
48
|
+
|
|
49
|
+
## 3. Standard names
|
|
50
|
+
|
|
51
|
+
Use the `names` registry instead of raw strings to avoid typos. Connectivity
|
|
52
|
+
events are emitted automatically by the offline connectivity monitor.
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
import { events } from '@adukiorg/anza/events';
|
|
56
|
+
|
|
57
|
+
events.on(events.names.connectivity.offline, () => showOfflineBanner());
|
|
58
|
+
events.on(events.names.connectivity.online, () => hideOfflineBanner());
|
|
59
|
+
events.on(events.names.auth.signedout, () => redirectToLogin());
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Available groups: `auth` (`signedin`, `signedout`, `refreshed`),
|
|
63
|
+
`connectivity` (`online`, `offline`), `preference` (`changed`),
|
|
64
|
+
`sw` (`updated`, `message`).
|
|
65
|
+
|
|
66
|
+
## 4. Delegation
|
|
67
|
+
|
|
68
|
+
`delegate(root, selector, type, handler, options)` attaches a single listener to
|
|
69
|
+
`root` and dispatches to descendants matching `selector`. The handler receives
|
|
70
|
+
`(event, matchedElement)`. Returns a disposer. Crosses Shadow DOM via
|
|
71
|
+
`composedPath()`.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const list = document.querySelector('.users');
|
|
75
|
+
|
|
76
|
+
const dispose = delegate(list, '.delete', 'click', (event, btn) => {
|
|
77
|
+
removeUser(btn.dataset.id);
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 5. Single awaiter
|
|
82
|
+
|
|
83
|
+
`once(target, type, options)` resolves a promise with the next matching event.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
await once(dialog, 'close');
|
|
87
|
+
const e = await once(img, 'load', { signal: ctrl.signal });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 6. Passive-safe listener
|
|
91
|
+
|
|
92
|
+
`listen(target, type, handler, options)` registers a listener and returns a
|
|
93
|
+
disposer. Scroll-class events (`touchstart`, `touchmove`, `wheel`, `mousewheel`)
|
|
94
|
+
default to `{ passive: true }`; pass `{ passive: false }` to call
|
|
95
|
+
`preventDefault()`.
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
const stop = listen(window, 'wheel', () => updateParallax());
|
|
99
|
+
listen(el, 'touchmove', onZoom, { passive: false });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 7. Rules
|
|
103
|
+
|
|
104
|
+
- Pass `ctrl.signal` (or call the returned disposer) so listeners clean up.
|
|
105
|
+
- Prefer `events.names.*` constants over string literals.
|
|
106
|
+
- Use `delegate` for dynamic lists instead of per-item listeners.
|
|
107
|
+
- Dispatch outward from components with `composed: true` custom events.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/offline/bridge.js
|
|
3
|
+
*
|
|
4
|
+
* Service Worker Message Bridge.
|
|
5
|
+
* Establishes a highly concurrent messaging corridor between the main window context
|
|
6
|
+
* and the active Service Worker using native MessageChannel instances for direct response routing.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 13 — Offline and Background §3
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class ServiceWorkerBridge {
|
|
12
|
+
/**
|
|
13
|
+
* Dispatches a message to the active Service Worker controller, returning a promise.
|
|
14
|
+
*/
|
|
15
|
+
send(task, payload = null) {
|
|
16
|
+
if (
|
|
17
|
+
typeof navigator === 'undefined' ||
|
|
18
|
+
!navigator.serviceWorker ||
|
|
19
|
+
!navigator.serviceWorker.controller
|
|
20
|
+
) {
|
|
21
|
+
return Promise.reject(new Error('Active Service Worker controller not found'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const channel = new MessageChannel();
|
|
26
|
+
|
|
27
|
+
channel.port1.onmessage = (event) => {
|
|
28
|
+
channel.port1.close();
|
|
29
|
+
const { success, result, error } = event.data;
|
|
30
|
+
if (success) {
|
|
31
|
+
resolve(result);
|
|
32
|
+
} else {
|
|
33
|
+
reject(new Error(error || 'Service Worker message task failed'));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
channel.port1.onmessageerror = () => {
|
|
38
|
+
channel.port1.close();
|
|
39
|
+
reject(new Error('Deserialization error on Service Worker message channel'));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Direct message post using the target port transferable
|
|
43
|
+
navigator.serviceWorker.controller.postMessage(
|
|
44
|
+
{ task, payload, port: channel.port2 },
|
|
45
|
+
[channel.port2]
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const bridge = new ServiceWorkerBridge();
|