@excom/nucleus-kit 0.1.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.
Files changed (34) hide show
  1. package/.rush/temp/chunked-rush-logs/nucleus-kit.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/nucleus-kit.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/nucleus-kit.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/basic.css +2 -0
  15. package/config/rig.json +6 -0
  16. package/custom-elements.css +18 -0
  17. package/index.ts +27 -0
  18. package/nucleus-kit.progressive.ts +107 -0
  19. package/package.json +68 -0
  20. package/rush-logs/nucleus-kit.apply-exports.cache.log +1 -0
  21. package/rush-logs/nucleus-kit.apply-exports.log +1 -0
  22. package/rush-logs/nucleus-kit.build_docs.cache.log +1 -0
  23. package/rush-logs/nucleus-kit.build_docs.log +1 -0
  24. package/rush-logs/nucleus-kit.build_package-metas.cache.log +1 -0
  25. package/rush-logs/nucleus-kit.build_package-metas.log +1 -0
  26. package/support/dist-docs/docs/breaking_changes.md +2 -0
  27. package/support/dist-docs/docs/readme.md +119 -0
  28. package/support/docs/BREAKING_CHANGES.md +2 -0
  29. package/support/docs/README.md +119 -0
  30. package/support/package-meta.json +36 -0
  31. package/support/tests/loop-guard.test.ts +92 -0
  32. package/support/tests/nucleus-kit.test.ts +19 -0
  33. package/support/tests/progressive.test.ts +129 -0
  34. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 33.996419999999944
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 36.887054000000035
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 89.94967499999984
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "../../packages/nucleus-kit": "../../packages/nucleus-kit:/A06Wgiyi4bzWWfjrcfyvxiVdBtW/mKsf/tz52eP+1g=:"
3
+ }
package/basic.css ADDED
@@ -0,0 +1,2 @@
1
+ @import "@excom/valence/basic.css";
2
+ @import "./custom-elements.css";
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
6
+
@@ -0,0 +1,18 @@
1
+ /* Nucleus Kit element CSS. */
2
+ @import "@excom/content-carousel/index.css";
3
+ @import "@excom/content-drawer/index.css";
4
+ @import "@excom/content-tabs/index.css";
5
+ /* @import "@excom/provider-fetch/index.css"; */
6
+ @import "@excom/data-table/index.css";
7
+ @import "@excom/dialog-anchor/index.css";
8
+ @import "@excom/event-handler/index.css";
9
+ @import "@excom/gesture-handler/index.css";
10
+ @import "@excom/include-content/index.css";
11
+ /* @import "@excom/mapbox-view/index.css"; */
12
+ @import "@excom/quark-sheet/index.css";
13
+ @import "@excom/scroll-into-view/index.css";
14
+ @import "@excom/spa-route/index.css";
15
+ /* @import "@excom/super-form/index.css"; */
16
+ /* @import "@excom/super-img/index.css"; */
17
+ @import "@excom/super-input/index.css";
18
+ /* @import "@excom/detect-features/index.css"; */
package/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ export * from "@excom/content-carousel";
2
+ export * from "@excom/content-drawer";
3
+ export * from "@excom/content-tabs";
4
+ export * from "@excom/data-table";
5
+ export * from "@excom/detect-browser";
6
+ export * from "@excom/detect-features";
7
+ export * from "@excom/detect-media";
8
+ export * from "@excom/dialog-anchor";
9
+ export * from "@excom/dismiss-watcher";
10
+ export * from "@excom/dom-observer";
11
+ export * from "@excom/event-handler";
12
+ export * from "@excom/gesture-handler";
13
+ export * from "@excom/include-content";
14
+ export * from "@excom/network-status";
15
+ export * from "@excom/neutron";
16
+ export * from "@excom/provider-fetch";
17
+ export * from "@excom/provider-geolocation";
18
+ export * from "@excom/provider-orientation";
19
+ export * from "@excom/provider-storage";
20
+ export * from "@excom/quark";
21
+ export * from "@excom/quark-sheet";
22
+ export * from "@excom/scroll-into-view";
23
+ export * from "@excom/service-worker";
24
+ export * from "@excom/spa-route";
25
+ export * from "@excom/super-form";
26
+ export * from "@excom/super-input";
27
+ export * from "@excom/web-authn";
@@ -0,0 +1,107 @@
1
+ /**
2
+ * `nucleus-kit.progressive.min.js`: lazy Nucleus Kit. Watches for Nucleus Kit
3
+ * element tags (initial scan + `MutationObserver`) and imports that package on
4
+ * first sight. Shared chunks (`neutron`, `kit-utils`, `quark`, bases) live
5
+ * under `dist/progressive/` and load once.
6
+ *
7
+ * Vs the all-in `index.umd.min.js`: one extra round-trip before upgrade
8
+ * (style the pre-upgrade state yourself), and ESM only.
9
+ */
10
+ type Loader = () => Promise<unknown>;
11
+
12
+ const contentCarousel: Loader = () => import("@excom/content-carousel");
13
+ const contentDrawer: Loader = () => import("@excom/content-drawer");
14
+ const contentTabs: Loader = () => import("@excom/content-tabs");
15
+ const dataTable: Loader = () => import("@excom/data-table");
16
+ const spaRoute: Loader = () => import("@excom/spa-route");
17
+
18
+ /** Element tag → the package that defines it. */
19
+ export const PROGRESSIVE_LOADERS: Readonly<Record<string, Loader>> = {
20
+ "content-carousel": contentCarousel,
21
+ "content-carousel-slide": contentCarousel,
22
+ "content-drawer": contentDrawer,
23
+ "content-tabs": contentTabs,
24
+ "content-tabs-body": contentTabs,
25
+ "content-tabs-header": contentTabs,
26
+ "data-table": dataTable,
27
+ "data-th": dataTable,
28
+ "detect-browser": () => import("@excom/detect-browser"),
29
+ "detect-features": () => import("@excom/detect-features"),
30
+ "detect-media": () => import("@excom/detect-media"),
31
+ "dialog-anchor": () => import("@excom/dialog-anchor"),
32
+ "dismiss-watcher": () => import("@excom/dismiss-watcher"),
33
+ "dom-observer": () => import("@excom/dom-observer"),
34
+ "event-handler": () => import("@excom/event-handler"),
35
+ "gesture-handler": () => import("@excom/gesture-handler"),
36
+ "include-content": () => import("@excom/include-content"),
37
+ "network-status": () => import("@excom/network-status"),
38
+ "provider-fetch": () => import("@excom/provider-fetch"),
39
+ "provider-geolocation": () => import("@excom/provider-geolocation"),
40
+ "provider-orientation": () => import("@excom/provider-orientation"),
41
+ "provider-storage": () => import("@excom/provider-storage"),
42
+ "quark-sheet": () => import("@excom/quark-sheet"),
43
+ "scroll-into-view": () => import("@excom/scroll-into-view"),
44
+ "service-worker": () => import("@excom/service-worker"),
45
+ "spa-a": spaRoute,
46
+ "spa-manager": spaRoute,
47
+ "spa-route": spaRoute,
48
+ "super-form": () => import("@excom/super-form"),
49
+ "super-input": () => import("@excom/super-input"),
50
+ "web-authn": () => import("@excom/web-authn"),
51
+ };
52
+
53
+ /** Every tag the progressive entry can load. */
54
+ export const PROGRESSIVE_TAGS: readonly string[] =
55
+ Object.keys(PROGRESSIVE_LOADERS);
56
+
57
+ const SELECTOR = PROGRESSIVE_TAGS.join(",");
58
+ const pending = new Map<Loader, Promise<unknown>>();
59
+
60
+ /**
61
+ * Load (once) the package defining `tag`. Resolves when its module has
62
+ * evaluated, i.e. the element is defined; `undefined` for tags Nucleus Kit
63
+ * does not know. A failed import is logged and retried on the next call.
64
+ */
65
+ export const loadElement = (tag: string): Promise<unknown> | undefined => {
66
+ const loader = PROGRESSIVE_LOADERS[tag];
67
+ if (!loader) return undefined;
68
+ let promise = pending.get(loader);
69
+ if (!promise) {
70
+ promise = loader().catch((error) => {
71
+ pending.delete(loader);
72
+ console.error(`[nucleus-kit] failed to load <${tag}>`, error);
73
+ throw error;
74
+ });
75
+ pending.set(loader, promise);
76
+ }
77
+ return promise;
78
+ };
79
+
80
+ const scan = (node: Node) => {
81
+ if (node instanceof Element && node.localName in PROGRESSIVE_LOADERS) {
82
+ loadElement(node.localName);
83
+ }
84
+ if ("querySelectorAll" in node) {
85
+ (node as ParentNode)
86
+ .querySelectorAll(SELECTOR)
87
+ .forEach((el) => loadElement(el.localName));
88
+ }
89
+ };
90
+
91
+ /**
92
+ * Load the Nucleus Kit elements as their tags appear under `root` (an initial
93
+ * scan, then every inserted subtree). Returns a disposer. The entry calls this
94
+ * on `document` at import time; call it yourself for a shadow root.
95
+ */
96
+ export const observeElements = (
97
+ root: Document | Element | ShadowRoot
98
+ ): (() => void) => {
99
+ scan(root);
100
+ const observer = new MutationObserver((records) => {
101
+ for (const record of records) record.addedNodes.forEach(scan);
102
+ });
103
+ observer.observe(root, { childList: true, subtree: true });
104
+ return () => observer.disconnect();
105
+ };
106
+
107
+ observeElements(document);
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@excom/nucleus-kit",
3
+ "version": "0.1.0",
4
+ "description": "The whole Nucleus Stack in one package — elements, Quark, Valence.css",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@excom/content-carousel": "^0.1.0",
12
+ "@excom/content-drawer": "^0.1.0",
13
+ "@excom/content-tabs": "^0.1.0",
14
+ "@excom/data-table": "^0.1.0",
15
+ "@excom/detect-browser": "^0.1.0",
16
+ "@excom/detect-features": "^0.1.0",
17
+ "@excom/detect-media": "^0.1.0",
18
+ "@excom/dismiss-watcher": "^0.1.0",
19
+ "@excom/dom-observer": "^0.1.0",
20
+ "@excom/event-handler": "^0.1.0",
21
+ "@excom/include-content": "^0.1.0",
22
+ "@excom/gesture-handler": "^0.1.0",
23
+ "@excom/dialog-anchor": "^0.1.0",
24
+ "@excom/network-status": "^0.1.0",
25
+ "@excom/neutron": "^0.1.0",
26
+ "@excom/provider-fetch": "^0.1.0",
27
+ "@excom/provider-orientation": "^0.1.0",
28
+ "@excom/provider-geolocation": "^0.1.0",
29
+ "@excom/provider-storage": "^0.1.0",
30
+ "@excom/quark": "^0.1.0",
31
+ "@excom/quark-sheet": "^0.1.0",
32
+ "@excom/scroll-into-view": "^0.1.0",
33
+ "@excom/service-worker": "^0.1.0",
34
+ "@excom/spa-route": "^0.1.0",
35
+ "@excom/super-form": "^0.1.0",
36
+ "@excom/super-input": "^0.1.0",
37
+ "@excom/valence": "^0.1.0",
38
+ "@excom/web-authn": "^0.1.0"
39
+ },
40
+ "peerDependencies": {},
41
+ "devDependencies": {
42
+ "@excom/heft-rig": "^0.1.0"
43
+ },
44
+ "repository": {
45
+ "url": "excom-dev/nucleus",
46
+ "directory": "packages/nucleus-kit"
47
+ },
48
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/nucleus-kit/support/docs/README.md",
49
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
50
+ "keywords": [
51
+ "nucleus-kit"
52
+ ],
53
+ "excom": {
54
+ "packageType": "library",
55
+ "sideEffectImport": true
56
+ },
57
+ "scripts": {
58
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
59
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
60
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
61
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
62
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
63
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
64
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
65
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
66
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1,2 @@
1
+ - Renamed web-authn attrs
2
+ - Command-style events became native commands (2026-09-13): `provider-fetch-trigger` → `--fetch`; `super-form-trigger` / `web-authn-trigger` → `--submit`; `content-drawer-open` / `-close` / `-toggle` → `--open` / `--close` / `--toggle` (per-open props from the invoker's `data-*`, not `detail`); `content-carousel-next` / `-back` → `--next` / `--back`; `include-content-reload` / `spa-route-reload` / `quark-sheet-reload` → `--reload`; `provider-geolocation-request` / `provider-orientation-request` → `--request`; `data-table-export` → `--export` (options as `data-file-type` / `data-file-name` / `data-full` on the invoker); `data-table-filter` removed — write `filter-value` / `filter-casing`. Invoke with `<button command="--verb" commandfor="id">`, `<event-handler command-name="--verb" target-ref="…">`, `<dismiss-watcher command-name="--close">`, or `new CommandEvent()`.
@@ -0,0 +1,119 @@
1
+ # Nucleus Kit
2
+
3
+ The whole Nucleus Stack in one package — every element, provider, Quark, and Valence.css behind a single import.
4
+
5
+ ## Features
6
+
7
+ - **One JS import** Registers every Nucleus Kit element
8
+ - **One CSS import** Valence.css theme + shared element styles (`basic.css`)
9
+ - **App-ready** Routing, sheets, forms, drawers, providers, and more
10
+ - **À la carte** Every package is published on its own; if you only use a few elements, install just those
11
+
12
+ ## Installation
13
+
14
+ <include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
15
+
16
+ ## Usage
17
+
18
+ ```js
19
+ import "@excom/nucleus-kit";
20
+ ```
21
+
22
+ ```css
23
+ @import "@excom/nucleus-kit/basic.css";
24
+ ```
25
+
26
+ That loads Valence.css (`basic` theme) plus element CSS and registers the
27
+ packages below.
28
+
29
+ ### Progressive bundle (experimental)
30
+
31
+ `nucleus-kit.progressive.min.js` registers nothing up front. It watches the
32
+ document for Nucleus Kit element tags and imports each element's package the
33
+ first time its tag appears (initial scan, then every inserted subtree), so a
34
+ page pays only for the elements it uses. Packages shared by several elements
35
+ (`neutron`, `kit-utils`, `quark`, the element bases) are separate chunks
36
+ under `dist/progressive/`, fetched once. The entry is 3 kB; a page using
37
+ `quark-sheet` and `content-drawer` loads ~55 kB gzip less than the all-in
38
+ build.
39
+
40
+ ```html
41
+ <script type="module" src="/node_modules/@excom/nucleus-kit/nucleus-kit.progressive.min.js"></script>
42
+ ```
43
+
44
+ Trade-offs: elements upgrade one network round-trip later (style the
45
+ pre-upgrade state with `:not(:defined)`), it is ES modules only, and elements
46
+ inside a shadow root need `observeElements(shadowRoot)` from the same module.
47
+ The all-in `index.umd.min.js` and the ESM `index.js` are unchanged.
48
+
49
+ ### À la carte
50
+
51
+ Nucleus Kit is a convenience, not a requirement. If you find you are not using
52
+ most of its elements, install the packages you do use individually and drop it:
53
+
54
+ ```sh
55
+ npm install @excom/quark-sheet @excom/provider-fetch @excom/content-drawer
56
+ ```
57
+
58
+ ```js
59
+ import "@excom/quark-sheet";
60
+ import "@excom/provider-fetch";
61
+ import "@excom/content-drawer";
62
+ ```
63
+
64
+ Each package is self-contained — same elements, same versions, same CSS hooks —
65
+ and its README documents the slim install. Valence.css is `@excom/valence`.
66
+
67
+ ### What's included
68
+
69
+ **Core**
70
+
71
+ - [`neutron`](/nucleus/packages/neutron) — custom element factory
72
+ - [`quark`](/nucleus/packages/quark) / [`quark-sheet`](/nucleus/packages/quark-sheet) — DOM
73
+ orchestration
74
+ - [`valence`](/nucleus/packages/valence) — Valence.css, semantic CSS (via `basic.css`)
75
+
76
+ **Layout / chrome**
77
+
78
+ - [`content-carousel`](/nucleus/packages/content-carousel)
79
+ - [`content-drawer`](/nucleus/packages/content-drawer)
80
+ - [`content-tabs`](/nucleus/packages/content-tabs)
81
+ - [`dialog-anchor`](/nucleus/packages/dialog-anchor)
82
+ - [`dismiss-watcher`](/nucleus/packages/dismiss-watcher)
83
+ - [`data-table`](/nucleus/packages/data-table)
84
+
85
+ **Content / routing**
86
+
87
+ - [`include-content`](/nucleus/packages/include-content)
88
+ - [`spa-route`](/nucleus/packages/spa-route)
89
+ - [`scroll-into-view`](/nucleus/packages/scroll-into-view)
90
+
91
+ **Forms / auth**
92
+
93
+ - [`super-form`](/nucleus/packages/super-form)
94
+ - [`super-input`](/nucleus/packages/super-input)
95
+ - [`web-authn`](/nucleus/packages/web-authn)
96
+
97
+ **Providers**
98
+
99
+ - [`provider-fetch`](/nucleus/packages/provider-fetch)
100
+ - [`provider-geolocation`](/nucleus/packages/provider-geolocation)
101
+ - [`provider-orientation`](/nucleus/packages/provider-orientation)
102
+ - [`provider-storage`](/nucleus/packages/provider-storage)
103
+
104
+ **Platform**
105
+
106
+ - [`detect-browser`](/nucleus/packages/detect-browser)
107
+ - [`detect-features`](/nucleus/packages/detect-features)
108
+ - [`detect-media`](/nucleus/packages/detect-media)
109
+ - [`dom-observer`](/nucleus/packages/dom-observer)
110
+ - [`event-handler`](/nucleus/packages/event-handler)
111
+ - [`gesture-handler`](/nucleus/packages/gesture-handler)
112
+ - [`network-status`](/nucleus/packages/network-status)
113
+ - [`service-worker`](/nucleus/packages/service-worker)
114
+
115
+ ### Not in Nucleus Kit
116
+
117
+ Install separately when needed: `mapbox-view`, `super-img`, element bases
118
+ (`abortable-element`, `fetchable-element`, …), and editor tooling such as
119
+ [`nucleus-quark-highlighter`](/nucleus/packages/nucleus-quark-highlighter).
@@ -0,0 +1,2 @@
1
+ - Renamed web-authn attrs
2
+ - Command-style events became native commands (2026-09-13): `provider-fetch-trigger` → `--fetch`; `super-form-trigger` / `web-authn-trigger` → `--submit`; `content-drawer-open` / `-close` / `-toggle` → `--open` / `--close` / `--toggle` (per-open props from the invoker's `data-*`, not `detail`); `content-carousel-next` / `-back` → `--next` / `--back`; `include-content-reload` / `spa-route-reload` / `quark-sheet-reload` → `--reload`; `provider-geolocation-request` / `provider-orientation-request` → `--request`; `data-table-export` → `--export` (options as `data-file-type` / `data-file-name` / `data-full` on the invoker); `data-table-filter` removed — write `filter-value` / `filter-casing`. Invoke with `<button command="--verb" commandfor="id">`, `<event-handler command-name="--verb" target-ref="…">`, `<dismiss-watcher command-name="--close">`, or `new CommandEvent()`.
@@ -0,0 +1,119 @@
1
+ # Nucleus Kit
2
+
3
+ The whole Nucleus Stack in one package — every element, provider, Quark, and Valence.css behind a single import.
4
+
5
+ ## Features
6
+
7
+ - **One JS import** Registers every Nucleus Kit element
8
+ - **One CSS import** Valence.css theme + shared element styles (`basic.css`)
9
+ - **App-ready** Routing, sheets, forms, drawers, providers, and more
10
+ - **À la carte** Every package is published on its own; if you only use a few elements, install just those
11
+
12
+ ## Installation
13
+
14
+ <include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
15
+
16
+ ## Usage
17
+
18
+ ```js
19
+ import "@excom/nucleus-kit";
20
+ ```
21
+
22
+ ```css
23
+ @import "@excom/nucleus-kit/basic.css";
24
+ ```
25
+
26
+ That loads Valence.css (`basic` theme) plus element CSS and registers the
27
+ packages below.
28
+
29
+ ### Progressive bundle (experimental)
30
+
31
+ `nucleus-kit.progressive.min.js` registers nothing up front. It watches the
32
+ document for Nucleus Kit element tags and imports each element's package the
33
+ first time its tag appears (initial scan, then every inserted subtree), so a
34
+ page pays only for the elements it uses. Packages shared by several elements
35
+ (`neutron`, `kit-utils`, `quark`, the element bases) are separate chunks
36
+ under `dist/progressive/`, fetched once. The entry is 3 kB; a page using
37
+ `quark-sheet` and `content-drawer` loads ~55 kB gzip less than the all-in
38
+ build.
39
+
40
+ ```html
41
+ <script type="module" src="/node_modules/@excom/nucleus-kit/nucleus-kit.progressive.min.js"></script>
42
+ ```
43
+
44
+ Trade-offs: elements upgrade one network round-trip later (style the
45
+ pre-upgrade state with `:not(:defined)`), it is ES modules only, and elements
46
+ inside a shadow root need `observeElements(shadowRoot)` from the same module.
47
+ The all-in `index.umd.min.js` and the ESM `index.js` are unchanged.
48
+
49
+ ### À la carte
50
+
51
+ Nucleus Kit is a convenience, not a requirement. If you find you are not using
52
+ most of its elements, install the packages you do use individually and drop it:
53
+
54
+ ```sh
55
+ npm install @excom/quark-sheet @excom/provider-fetch @excom/content-drawer
56
+ ```
57
+
58
+ ```js
59
+ import "@excom/quark-sheet";
60
+ import "@excom/provider-fetch";
61
+ import "@excom/content-drawer";
62
+ ```
63
+
64
+ Each package is self-contained — same elements, same versions, same CSS hooks —
65
+ and its README documents the slim install. Valence.css is `@excom/valence`.
66
+
67
+ ### What's included
68
+
69
+ **Core**
70
+
71
+ - [`neutron`](/nucleus/packages/neutron) — custom element factory
72
+ - [`quark`](/nucleus/packages/quark) / [`quark-sheet`](/nucleus/packages/quark-sheet) — DOM
73
+ orchestration
74
+ - [`valence`](/nucleus/packages/valence) — Valence.css, semantic CSS (via `basic.css`)
75
+
76
+ **Layout / chrome**
77
+
78
+ - [`content-carousel`](/nucleus/packages/content-carousel)
79
+ - [`content-drawer`](/nucleus/packages/content-drawer)
80
+ - [`content-tabs`](/nucleus/packages/content-tabs)
81
+ - [`dialog-anchor`](/nucleus/packages/dialog-anchor)
82
+ - [`dismiss-watcher`](/nucleus/packages/dismiss-watcher)
83
+ - [`data-table`](/nucleus/packages/data-table)
84
+
85
+ **Content / routing**
86
+
87
+ - [`include-content`](/nucleus/packages/include-content)
88
+ - [`spa-route`](/nucleus/packages/spa-route)
89
+ - [`scroll-into-view`](/nucleus/packages/scroll-into-view)
90
+
91
+ **Forms / auth**
92
+
93
+ - [`super-form`](/nucleus/packages/super-form)
94
+ - [`super-input`](/nucleus/packages/super-input)
95
+ - [`web-authn`](/nucleus/packages/web-authn)
96
+
97
+ **Providers**
98
+
99
+ - [`provider-fetch`](/nucleus/packages/provider-fetch)
100
+ - [`provider-geolocation`](/nucleus/packages/provider-geolocation)
101
+ - [`provider-orientation`](/nucleus/packages/provider-orientation)
102
+ - [`provider-storage`](/nucleus/packages/provider-storage)
103
+
104
+ **Platform**
105
+
106
+ - [`detect-browser`](/nucleus/packages/detect-browser)
107
+ - [`detect-features`](/nucleus/packages/detect-features)
108
+ - [`detect-media`](/nucleus/packages/detect-media)
109
+ - [`dom-observer`](/nucleus/packages/dom-observer)
110
+ - [`event-handler`](/nucleus/packages/event-handler)
111
+ - [`gesture-handler`](/nucleus/packages/gesture-handler)
112
+ - [`network-status`](/nucleus/packages/network-status)
113
+ - [`service-worker`](/nucleus/packages/service-worker)
114
+
115
+ ### Not in Nucleus Kit
116
+
117
+ Install separately when needed: `mapbox-view`, `super-img`, element bases
118
+ (`abortable-element`, `fetchable-element`, …), and editor tooling such as
119
+ [`nucleus-quark-highlighter`](/nucleus/packages/nucleus-quark-highlighter).
@@ -0,0 +1,36 @@
1
+ {
2
+ "shortName": "nucleus-kit",
3
+ "package": {
4
+ "name": "@excom/nucleus-kit",
5
+ "version": "0.1.0",
6
+ "description": "The whole Nucleus Stack in one package — elements, Quark, Valence.css",
7
+ "peerDependencies": {},
8
+ "excom": {
9
+ "packageType": "library",
10
+ "sideEffectImport": true
11
+ }
12
+ },
13
+ "demos": {},
14
+ "readme": "<h1 id=\"md-nucleus-kit\">Nucleus Kit</h1>\n<p>The whole Nucleus Stack in one package — every element, provider, Quark, and Valence.css behind a single import.</p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>One JS import</strong> Registers every Nucleus Kit element</li>\n<li><strong>One CSS import</strong> Valence.css theme + shared element styles (<code>basic.css</code>)</li>\n<li><strong>App-ready</strong> Routing, sheets, forms, drawers, providers, and more</li>\n<li><strong>À la carte</strong> Every package is published on its own; if you only use a few elements, install just those</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<include-content data-language=\"js\"><template>import \"@excom/nucleus-kit\";</template></include-content>\n<include-content data-language=\"css\"><template>@import \"@excom/nucleus-kit/basic.css\";</template></include-content>\n<p>That loads Valence.css (<code>basic</code> theme) plus element CSS and registers the\npackages below.</p>\n<h3 id=\"md-progressive-bundle-experimental\">Progressive bundle (experimental)</h3>\n<p><code>nucleus-kit.progressive.min.js</code> registers nothing up front. It watches the\ndocument for Nucleus Kit element tags and imports each element&#39;s package the\nfirst time its tag appears (initial scan, then every inserted subtree), so a\npage pays only for the elements it uses. Packages shared by several elements\n(<code>neutron</code>, <code>kit-utils</code>, <code>quark</code>, the element bases) are separate chunks\nunder <code>dist/progressive/</code>, fetched once. The entry is 3 kB; a page using\n<code>quark-sheet</code> and <code>content-drawer</code> loads ~55 kB gzip less than the all-in\nbuild.</p>\n<include-content data-language=\"html\"><template>&lt;script type=\"module\" src=\"/node_modules/@excom/nucleus-kit/nucleus-kit.progressive.min.js\"&gt;&lt;/script&gt;</template></include-content>\n<p>Trade-offs: elements upgrade one network round-trip later (style the\npre-upgrade state with <code>:not(:defined)</code>), it is ES modules only, and elements\ninside a shadow root need <code>observeElements(shadowRoot)</code> from the same module.\nThe all-in <code>index.umd.min.js</code> and the ESM <code>index.js</code> are unchanged.</p>\n<h3 id=\"md-la-carte\">À la carte</h3>\n<p>Nucleus Kit is a convenience, not a requirement. If you find you are not using\nmost of its elements, install the packages you do use individually and drop it:</p>\n<include-content data-language=\"sh\"><template>npm install @excom/quark-sheet @excom/provider-fetch @excom/content-drawer</template></include-content>\n<include-content data-language=\"js\"><template>import \"@excom/quark-sheet\";\nimport \"@excom/provider-fetch\";\nimport \"@excom/content-drawer\";</template></include-content>\n<p>Each package is self-contained — same elements, same versions, same CSS hooks —\nand its README documents the slim install. Valence.css is <code>@excom/valence</code>.</p>\n<h3 id=\"md-what-39-s-included\">What&#39;s included</h3>\n<p><strong>Core</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/neutron\" role=\"link\"><code>neutron</code></spa-a> — custom element factory</li>\n<li><spa-a route-href=\"/nucleus/packages/quark\" role=\"link\"><code>quark</code></spa-a> / <spa-a route-href=\"/nucleus/packages/quark-sheet\" role=\"link\"><code>quark-sheet</code></spa-a> — DOM\norchestration</li>\n<li><spa-a route-href=\"/nucleus/packages/valence\" role=\"link\"><code>valence</code></spa-a> — Valence.css, semantic CSS (via <code>basic.css</code>)</li>\n</ul>\n<p><strong>Layout / chrome</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/content-carousel\" role=\"link\"><code>content-carousel</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/content-drawer\" role=\"link\"><code>content-drawer</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/content-tabs\" role=\"link\"><code>content-tabs</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/dialog-anchor\" role=\"link\"><code>dialog-anchor</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/dismiss-watcher\" role=\"link\"><code>dismiss-watcher</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/data-table\" role=\"link\"><code>data-table</code></spa-a></li>\n</ul>\n<p><strong>Content / routing</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/include-content\" role=\"link\"><code>include-content</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/spa-route\" role=\"link\"><code>spa-route</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/scroll-into-view\" role=\"link\"><code>scroll-into-view</code></spa-a></li>\n</ul>\n<p><strong>Forms / auth</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/super-form\" role=\"link\"><code>super-form</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/super-input\" role=\"link\"><code>super-input</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/web-authn\" role=\"link\"><code>web-authn</code></spa-a></li>\n</ul>\n<p><strong>Providers</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/provider-fetch\" role=\"link\"><code>provider-fetch</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/provider-geolocation\" role=\"link\"><code>provider-geolocation</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/provider-orientation\" role=\"link\"><code>provider-orientation</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/provider-storage\" role=\"link\"><code>provider-storage</code></spa-a></li>\n</ul>\n<p><strong>Platform</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/detect-browser\" role=\"link\"><code>detect-browser</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/detect-features\" role=\"link\"><code>detect-features</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/detect-media\" role=\"link\"><code>detect-media</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/dom-observer\" role=\"link\"><code>dom-observer</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/event-handler\" role=\"link\"><code>event-handler</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/gesture-handler\" role=\"link\"><code>gesture-handler</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/network-status\" role=\"link\"><code>network-status</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/service-worker\" role=\"link\"><code>service-worker</code></spa-a></li>\n</ul>\n<h3 id=\"md-not-in-nucleus-kit\">Not in Nucleus Kit</h3>\n<p>Install separately when needed: <code>mapbox-view</code>, <code>super-img</code>, element bases\n(<code>abortable-element</code>, <code>fetchable-element</code>, …), and editor tooling such as\n<spa-a route-href=\"/nucleus/packages/nucleus-quark-highlighter\" role=\"link\"><code>nucleus-quark-highlighter</code></spa-a>.</p>\n",
15
+ "docs": {
16
+ "breaking_changes": "<ul>\n<li>Renamed web-authn attrs</li>\n<li>Command-style events became native commands (2026-09-13): <code>provider-fetch-trigger</code> → <code>--fetch</code>; <code>super-form-trigger</code> / <code>web-authn-trigger</code> → <code>--submit</code>; <code>content-drawer-open</code> / <code>-close</code> / <code>-toggle</code> → <code>--open</code> / <code>--close</code> / <code>--toggle</code> (per-open props from the invoker&#39;s <code>data-*</code>, not <code>detail</code>); <code>content-carousel-next</code> / <code>-back</code> → <code>--next</code> / <code>--back</code>; <code>include-content-reload</code> / <code>spa-route-reload</code> / <code>quark-sheet-reload</code> → <code>--reload</code>; <code>provider-geolocation-request</code> / <code>provider-orientation-request</code> → <code>--request</code>; <code>data-table-export</code> → <code>--export</code> (options as <code>data-file-type</code> / <code>data-file-name</code> / <code>data-full</code> on the invoker); <code>data-table-filter</code> removed — write <code>filter-value</code> / <code>filter-casing</code>. Invoke with <code>&lt;button command=&quot;--verb&quot; commandfor=&quot;id&quot;&gt;</code>, <code>&lt;event-handler command-name=&quot;--verb&quot; target-ref=&quot;…&quot;&gt;</code>, <code>&lt;dismiss-watcher command-name=&quot;--close&quot;&gt;</code>, or <code>new CommandEvent()</code>.</li>\n</ul>\n",
17
+ "readme": "<h1 id=\"md-nucleus-kit\">Nucleus Kit</h1>\n<p>The whole Nucleus Stack in one package — every element, provider, Quark, and Valence.css behind a single import.</p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>One JS import</strong> Registers every Nucleus Kit element</li>\n<li><strong>One CSS import</strong> Valence.css theme + shared element styles (<code>basic.css</code>)</li>\n<li><strong>App-ready</strong> Routing, sheets, forms, drawers, providers, and more</li>\n<li><strong>À la carte</strong> Every package is published on its own; if you only use a few elements, install just those</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<include-content data-language=\"js\"><template>import \"@excom/nucleus-kit\";</template></include-content>\n<include-content data-language=\"css\"><template>@import \"@excom/nucleus-kit/basic.css\";</template></include-content>\n<p>That loads Valence.css (<code>basic</code> theme) plus element CSS and registers the\npackages below.</p>\n<h3 id=\"md-progressive-bundle-experimental\">Progressive bundle (experimental)</h3>\n<p><code>nucleus-kit.progressive.min.js</code> registers nothing up front. It watches the\ndocument for Nucleus Kit element tags and imports each element&#39;s package the\nfirst time its tag appears (initial scan, then every inserted subtree), so a\npage pays only for the elements it uses. Packages shared by several elements\n(<code>neutron</code>, <code>kit-utils</code>, <code>quark</code>, the element bases) are separate chunks\nunder <code>dist/progressive/</code>, fetched once. The entry is 3 kB; a page using\n<code>quark-sheet</code> and <code>content-drawer</code> loads ~55 kB gzip less than the all-in\nbuild.</p>\n<include-content data-language=\"html\"><template>&lt;script type=\"module\" src=\"/node_modules/@excom/nucleus-kit/nucleus-kit.progressive.min.js\"&gt;&lt;/script&gt;</template></include-content>\n<p>Trade-offs: elements upgrade one network round-trip later (style the\npre-upgrade state with <code>:not(:defined)</code>), it is ES modules only, and elements\ninside a shadow root need <code>observeElements(shadowRoot)</code> from the same module.\nThe all-in <code>index.umd.min.js</code> and the ESM <code>index.js</code> are unchanged.</p>\n<h3 id=\"md-la-carte\">À la carte</h3>\n<p>Nucleus Kit is a convenience, not a requirement. If you find you are not using\nmost of its elements, install the packages you do use individually and drop it:</p>\n<include-content data-language=\"sh\"><template>npm install @excom/quark-sheet @excom/provider-fetch @excom/content-drawer</template></include-content>\n<include-content data-language=\"js\"><template>import \"@excom/quark-sheet\";\nimport \"@excom/provider-fetch\";\nimport \"@excom/content-drawer\";</template></include-content>\n<p>Each package is self-contained — same elements, same versions, same CSS hooks —\nand its README documents the slim install. Valence.css is <code>@excom/valence</code>.</p>\n<h3 id=\"md-what-39-s-included\">What&#39;s included</h3>\n<p><strong>Core</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/neutron\" role=\"link\"><code>neutron</code></spa-a> — custom element factory</li>\n<li><spa-a route-href=\"/nucleus/packages/quark\" role=\"link\"><code>quark</code></spa-a> / <spa-a route-href=\"/nucleus/packages/quark-sheet\" role=\"link\"><code>quark-sheet</code></spa-a> — DOM\norchestration</li>\n<li><spa-a route-href=\"/nucleus/packages/valence\" role=\"link\"><code>valence</code></spa-a> — Valence.css, semantic CSS (via <code>basic.css</code>)</li>\n</ul>\n<p><strong>Layout / chrome</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/content-carousel\" role=\"link\"><code>content-carousel</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/content-drawer\" role=\"link\"><code>content-drawer</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/content-tabs\" role=\"link\"><code>content-tabs</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/dialog-anchor\" role=\"link\"><code>dialog-anchor</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/dismiss-watcher\" role=\"link\"><code>dismiss-watcher</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/data-table\" role=\"link\"><code>data-table</code></spa-a></li>\n</ul>\n<p><strong>Content / routing</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/include-content\" role=\"link\"><code>include-content</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/spa-route\" role=\"link\"><code>spa-route</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/scroll-into-view\" role=\"link\"><code>scroll-into-view</code></spa-a></li>\n</ul>\n<p><strong>Forms / auth</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/super-form\" role=\"link\"><code>super-form</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/super-input\" role=\"link\"><code>super-input</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/web-authn\" role=\"link\"><code>web-authn</code></spa-a></li>\n</ul>\n<p><strong>Providers</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/provider-fetch\" role=\"link\"><code>provider-fetch</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/provider-geolocation\" role=\"link\"><code>provider-geolocation</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/provider-orientation\" role=\"link\"><code>provider-orientation</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/provider-storage\" role=\"link\"><code>provider-storage</code></spa-a></li>\n</ul>\n<p><strong>Platform</strong></p>\n<ul>\n<li><spa-a route-href=\"/nucleus/packages/detect-browser\" role=\"link\"><code>detect-browser</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/detect-features\" role=\"link\"><code>detect-features</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/detect-media\" role=\"link\"><code>detect-media</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/dom-observer\" role=\"link\"><code>dom-observer</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/event-handler\" role=\"link\"><code>event-handler</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/gesture-handler\" role=\"link\"><code>gesture-handler</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/network-status\" role=\"link\"><code>network-status</code></spa-a></li>\n<li><spa-a route-href=\"/nucleus/packages/service-worker\" role=\"link\"><code>service-worker</code></spa-a></li>\n</ul>\n<h3 id=\"md-not-in-nucleus-kit\">Not in Nucleus Kit</h3>\n<p>Install separately when needed: <code>mapbox-view</code>, <code>super-img</code>, element bases\n(<code>abortable-element</code>, <code>fetchable-element</code>, …), and editor tooling such as\n<spa-a route-href=\"/nucleus/packages/nucleus-quark-highlighter\" role=\"link\"><code>nucleus-quark-highlighter</code></spa-a>.</p>\n"
18
+ },
19
+ "installation": {
20
+ "name": "@excom/nucleus-kit",
21
+ "shortName": "nucleus-kit",
22
+ "version": "0.1.0",
23
+ "description": "The whole Nucleus Stack in one package — elements, Quark, Valence.css",
24
+ "packageType": "library",
25
+ "install": {
26
+ "npm": "npm install @excom/nucleus-kit"
27
+ },
28
+ "imports": {
29
+ "js": "import \"@excom/nucleus-kit\";",
30
+ "css": "@import \"@excom/nucleus-kit/basic.css\";"
31
+ },
32
+ "peerDependencies": []
33
+ },
34
+ "elementApis": [],
35
+ "exportedFiles": {}
36
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Cross-package loop: an event-emitting Adapter (`dom-observer`) and a Quark
3
+ * `@on` block that writes the attribute the Adapter observes. Every hop is
4
+ * asynchronous (MutationObserver → event → deferred paint), so only the
5
+ * shared loop guard's address-carried depth can see it as one chain.
6
+ */
7
+ import "../../index";
8
+ import {
9
+ afterEach,
10
+ beforeEach,
11
+ describe,
12
+ expect,
13
+ fixture,
14
+ it,
15
+ wait,
16
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
17
+ import { LoopGuard, type LoopGuardTrip } from "@excom/kit-utils";
18
+
19
+ describe("nucleus-kit: loop guard across elements and Quark", () => {
20
+ let trips: LoopGuardTrip[];
21
+ let off: () => void;
22
+
23
+ beforeEach(() => {
24
+ LoopGuard.reset();
25
+ LoopGuard.configure({ limit: 6, log: () => {} });
26
+ trips = [];
27
+ off = LoopGuard.onTrip((trip) => trips.push(trip));
28
+ });
29
+
30
+ afterEach(() => {
31
+ off();
32
+ LoopGuard.reset();
33
+ document.body.innerHTML = "";
34
+ });
35
+
36
+ const settle = async (done: () => boolean, ticks = 400) => {
37
+ for (let i = 0; i < ticks && !done(); i++) await wait(0);
38
+ };
39
+
40
+ it("cuts an attribute → event → @on block → attribute cycle", async () => {
41
+ const root = fixture<HTMLElement>(
42
+ `<section>
43
+ <quark-sheet>
44
+ dom-observer {
45
+ @on dom-observer-change {
46
+ #box { data-n: attr("data-n") + "x"; }
47
+ }
48
+ }
49
+ </quark-sheet>
50
+ <dom-observer target-ref="#box"><div id="box" data-n=""></div></dom-observer>
51
+ </section>`
52
+ );
53
+ const box = root.querySelector("#box")!;
54
+ await wait(0);
55
+ await wait(0);
56
+ // kick the cycle from outside: one mutation → change event → block
57
+ // writes `data-n` → another mutation → …
58
+ box.setAttribute("data-n", "start");
59
+ await settle(() => trips.length > 0);
60
+ expect(trips.length).toBeGreaterThanOrEqual(1);
61
+ expect(trips[0]).toMatchObject({ kind: "depth", target: box, name: "data-n" });
62
+ const value = box.getAttribute("data-n")!;
63
+ expect(value.length).toBeLessThanOrEqual("start".length + LoopGuard.limit);
64
+ // dead: nothing changes any more
65
+ for (let i = 0; i < 10; i++) await wait(0);
66
+ expect(box.getAttribute("data-n")).toBe(value);
67
+ });
68
+
69
+ it("does not trip when each change is an external one", async () => {
70
+ const root = fixture<HTMLElement>(
71
+ `<section>
72
+ <quark-sheet>
73
+ dom-observer {
74
+ @on dom-observer-change {
75
+ #box { data-seen: attr("data-n"); }
76
+ }
77
+ }
78
+ </quark-sheet>
79
+ <dom-observer target-ref="#box"><div id="box" data-n=""></div></dom-observer>
80
+ </section>`
81
+ );
82
+ const box = root.querySelector("#box")!;
83
+ await wait(0);
84
+ await wait(0);
85
+ for (let i = 1; i <= 10; i++) {
86
+ box.setAttribute("data-n", String(i));
87
+ await settle(() => box.getAttribute("data-seen") === String(i), 40);
88
+ }
89
+ expect(box.getAttribute("data-seen")).toBe("10");
90
+ expect(trips).toEqual([]);
91
+ });
92
+ });
@@ -0,0 +1,19 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/node_modules/vitest";
8
+
9
+ describe("nucleus-kit", () => {
10
+ afterEach(() => {
11
+ document.body.innerHTML = "";
12
+ });
13
+
14
+ it("registers a few shared elements", () => {
15
+ expect(customElements.get("content-drawer")).toBeTruthy();
16
+ expect(customElements.get("detect-browser")).toBeTruthy();
17
+ expect(customElements.get("dom-observer")).toBeTruthy();
18
+ });
19
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The progressive entry: nothing registered up front, each package imported
3
+ * the first time one of its tags appears in the document.
4
+ */
5
+ import {
6
+ loadElement,
7
+ observeElements,
8
+ PROGRESSIVE_LOADERS,
9
+ PROGRESSIVE_TAGS,
10
+ } from "../../nucleus-kit.progressive";
11
+ import {
12
+ afterEach,
13
+ describe,
14
+ expect,
15
+ it,
16
+ vi,
17
+ wait,
18
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
19
+
20
+ const settle = async () => {
21
+ await wait(0);
22
+ await wait(0);
23
+ };
24
+
25
+ describe("nucleus-kit progressive entry", () => {
26
+ afterEach(() => {
27
+ document.body.innerHTML = "";
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ it("covers every tag Nucleus Kit defines", () => {
32
+ expect([...PROGRESSIVE_TAGS].sort()).toEqual(
33
+ [
34
+ "content-carousel",
35
+ "content-carousel-slide",
36
+ "content-drawer",
37
+ "content-tabs",
38
+ "content-tabs-body",
39
+ "content-tabs-header",
40
+ "data-table",
41
+ "data-th",
42
+ "detect-browser",
43
+ "detect-features",
44
+ "detect-media",
45
+ "dialog-anchor",
46
+ "dismiss-watcher",
47
+ "dom-observer",
48
+ "event-handler",
49
+ "gesture-handler",
50
+ "include-content",
51
+ "network-status",
52
+ "provider-fetch",
53
+ "provider-geolocation",
54
+ "provider-orientation",
55
+ "provider-storage",
56
+ "quark-sheet",
57
+ "scroll-into-view",
58
+ "service-worker",
59
+ "spa-a",
60
+ "spa-manager",
61
+ "spa-route",
62
+ "super-form",
63
+ "super-input",
64
+ "web-authn",
65
+ ].sort()
66
+ );
67
+ // sub-adapters share their parent's loader so a family is one import
68
+ expect(PROGRESSIVE_LOADERS["content-tabs-header"]).toBe(PROGRESSIVE_LOADERS["content-tabs"]);
69
+ expect(PROGRESSIVE_LOADERS["spa-a"]).toBe(PROGRESSIVE_LOADERS["spa-route"]);
70
+ });
71
+
72
+ it("defines every tag it maps", async () => {
73
+ await Promise.all(PROGRESSIVE_TAGS.map((tag) => loadElement(tag)));
74
+ for (const tag of PROGRESSIVE_TAGS) {
75
+ expect(customElements.get(tag), tag).toBeDefined();
76
+ }
77
+ });
78
+
79
+ it("loads a package once on demand and defines its elements", async () => {
80
+ const first = loadElement("content-drawer");
81
+ expect(loadElement("content-drawer")).toBe(first);
82
+ await first;
83
+ expect(customElements.get("content-drawer")).toBeDefined();
84
+ expect(loadElement("not-a-kit-tag")).toBeUndefined();
85
+ });
86
+
87
+ it("loads elements inserted into the document, including nested ones", async () => {
88
+ document.body.innerHTML = `<div>text <section><super-form></super-form></section></div>`;
89
+ document.body.appendChild(document.createTextNode("loose text"));
90
+ await settle();
91
+ await loadElement("super-form");
92
+ expect(customElements.get("super-form")).toBeDefined();
93
+
94
+ const el = document.createElement("dialog-anchor");
95
+ document.body.appendChild(el);
96
+ await settle();
97
+ await loadElement("dialog-anchor");
98
+ expect(customElements.get("dialog-anchor")).toBeDefined();
99
+ });
100
+
101
+ it("observes a shadow root on request and stops when disposed", async () => {
102
+ const host = document.createElement("div");
103
+ const shadow = host.attachShadow({ mode: "open" });
104
+ shadow.innerHTML = `<network-status></network-status>`;
105
+ document.body.appendChild(host);
106
+ const stop = observeElements(shadow);
107
+ await loadElement("network-status");
108
+ expect(customElements.get("network-status")).toBeDefined();
109
+ stop();
110
+ const spy = vi.spyOn(PROGRESSIVE_LOADERS as Record<string, () => Promise<unknown>>, "scroll-into-view");
111
+ shadow.appendChild(document.createElement("scroll-into-view"));
112
+ await settle();
113
+ expect(spy).not.toHaveBeenCalled();
114
+ });
115
+
116
+ it("logs a failed import and retries on the next call", async () => {
117
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
118
+ let attempts = 0;
119
+ (PROGRESSIVE_LOADERS as Record<string, () => Promise<unknown>>)["x-broken"] = () => {
120
+ attempts++;
121
+ return attempts === 1 ? Promise.reject(new Error("nope")) : Promise.resolve("ok");
122
+ };
123
+ await expect(loadElement("x-broken")).rejects.toThrow("nope");
124
+ expect(error).toHaveBeenCalledWith("[nucleus-kit] failed to load <x-broken>", expect.any(Error));
125
+ await expect(loadElement("x-broken")).resolves.toBe("ok");
126
+ expect(attempts).toBe(2);
127
+ delete (PROGRESSIVE_LOADERS as Record<string, unknown>)["x-broken"];
128
+ });
129
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }