@adukiorg/anza 0.4.1 → 0.4.3

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 (43) hide show
  1. package/bin/anza/anza-linux-arm64 +0 -0
  2. package/bin/anza/anza-linux-x64 +0 -0
  3. package/bin/anza/anza-macos-arm64 +0 -0
  4. package/bin/anza/anza-macos-x64 +0 -0
  5. package/bin/anza/anza-windows-x64.exe +0 -0
  6. package/package.json +1 -1
  7. package/src/core/api/index.js +2 -2
  8. package/src/core/offline/sync.js +2 -2
  9. package/src/core/router/cascade.js +23 -18
  10. package/src/core/router/container.js +6 -2
  11. package/src/core/router/graph.js +46 -15
  12. package/src/core/router/index.js +7 -1
  13. package/src/core/router/intercept.js +397 -100
  14. package/src/core/router/match.js +30 -0
  15. package/src/core/router/transitions.js +4 -3
  16. package/src/core/ui/define/container.js +3 -13
  17. package/src/core/ui/define/element.js +58 -8
  18. package/src/core/ui/define/orchestrator.js +8 -4
  19. package/src/core/ui/defs/dock.js +27 -14
  20. package/src/core/ui/defs/page.js +75 -29
  21. package/src/core/ui/defs/spec.js +81 -11
  22. package/src/elements/data/list/style.css +1 -1
  23. package/src/elements/data/table/index.js +2 -1
  24. package/src/elements/data/table/style.css +1 -1
  25. package/src/elements/feedback/alert/style.css +1 -1
  26. package/src/styles/base.css +22 -21
  27. package/src/styles/reset.css +0 -2
  28. package/src/tokens/index.css +1 -4
  29. package/src/tokens/primitives/colors.css +9 -64
  30. package/src/tokens/primitives/motion.css +6 -18
  31. package/src/tokens/primitives/spacing.css +7 -12
  32. package/src/tokens/primitives/typography.css +12 -42
  33. package/src/tokens/registered/colors.css +5 -96
  34. package/src/tokens/registered/dimensions.css +6 -19
  35. package/src/tokens/semantic/contrast.css +8 -36
  36. package/src/tokens/semantic/dark.css +13 -48
  37. package/src/tokens/semantic/light.css +13 -44
  38. package/src/tokens/semantic/transitions.css +26 -13
  39. package/CHANGELOG.md +0 -360
  40. package/src/tokens/primitives/radius.css +0 -16
  41. package/src/tokens/primitives/shadow.css +0 -34
  42. package/src/tokens/primitives/zindex.css +0 -18
  43. package/src/tokens/semantic/components.css +0 -123
@@ -2,7 +2,7 @@ import { BaseElement } from '../base.js';
2
2
  import { scheduleFrame, yieldTask } from '../schedule.js';
3
3
  import { router } from '../../router/index.js';
4
4
  import { specRegistry, internalsMap, initializedMap, pendingUpdatesMap, updateScheduledMap, assetCache } from './state.js';
5
- import { preloadResources } from './utils.js';
5
+ import { preloadResources, createTemplateFragment } from './utils.js';
6
6
  import { createComponentContext } from './proxy.js';
7
7
 
8
8
  // Platform lifecycle method names that must never be overridden by spec.methods.
@@ -63,12 +63,62 @@ export function element(tag, spec, base) {
63
63
  ? new URL(spec.template, base).href
64
64
  : null;
65
65
 
66
- // Initiate resource fetching exactly once per component registration (R-06)
66
+ // Defer resource preloading until first mount or HMR rebind for cold-start performance
67
+ const hasUrls = (styleUrls.length > 0) || (templateUrl !== null);
67
68
  let resolved = null;
68
- let resourcesPromise = preloadResources(tag, styleUrls, templateUrl, spec.template, spec.style).then(res => {
69
- resolved = res;
70
- return res;
71
- });
69
+ let resourcesPromise = null;
70
+
71
+ const getResources = () => {
72
+ if (!resourcesPromise) {
73
+ resourcesPromise = preloadResources(tag, styleUrls, templateUrl, spec.template, spec.style).then(res => {
74
+ resolved = res;
75
+ return res;
76
+ });
77
+ }
78
+ return resourcesPromise;
79
+ };
80
+
81
+ const isLazy = spec.lazy === true && hasUrls;
82
+
83
+ if (!isLazy) {
84
+ if (!hasUrls) {
85
+ // Compile synchronously immediately to support synchronous connectedCallback (unit tests and static components)
86
+ const supportsSheets =
87
+ typeof CSSStyleSheet !== 'undefined' &&
88
+ 'adoptedStyleSheets' in Document.prototype &&
89
+ 'adoptedStyleSheets' in ShadowRoot.prototype;
90
+
91
+ let templateNode = null;
92
+ let stylesheets = [];
93
+ let cssTextAcc = '';
94
+
95
+ if (spec.template) {
96
+ templateNode = createTemplateFragment(spec.template);
97
+ }
98
+ if (spec.style) {
99
+ const inlineStyles = Array.isArray(spec.style) ? spec.style : [spec.style];
100
+ for (const style of inlineStyles) {
101
+ if (supportsSheets) {
102
+ const sheet = new CSSStyleSheet();
103
+ sheet.replaceSync(style);
104
+ stylesheets.push(sheet);
105
+ } else {
106
+ cssTextAcc += style + '\n';
107
+ }
108
+ }
109
+ }
110
+ resolved = {
111
+ templateNode,
112
+ stylesheets,
113
+ cssText: cssTextAcc.trim() ? cssTextAcc : null,
114
+ tagsDescriptor: null
115
+ };
116
+ resourcesPromise = Promise.resolve(resolved);
117
+ } else {
118
+ // Start eager preloading immediately
119
+ getResources();
120
+ }
121
+ }
72
122
 
73
123
  // Handle hot reloading of constructable stylesheets (one global listener per unique styleUrl - R-05)
74
124
  if (styleUrls.length > 0 && typeof window !== 'undefined') {
@@ -175,7 +225,7 @@ export function element(tag, spec, base) {
175
225
  // Wait for resolved resources to compile (synchronously if already cached)
176
226
  let res = resolved;
177
227
  if (!res) {
178
- res = await resourcesPromise;
228
+ res = await getResources();
179
229
  }
180
230
  const { templateNode, stylesheets, cssText, tagsDescriptor } = res;
181
231
 
@@ -269,7 +319,7 @@ export function element(tag, spec, base) {
269
319
  // 3. Clone new template
270
320
  let res = resolved;
271
321
  if (!res) {
272
- res = await resourcesPromise;
322
+ res = await getResources();
273
323
  }
274
324
  const { templateNode, stylesheets, cssText, tagsDescriptor } = res;
275
325
 
@@ -10,7 +10,7 @@ let dispose = null; // One-word module-level disposer variable (RT-11)
10
10
  export function initOrchestrator() {
11
11
  if (typeof window !== 'undefined') {
12
12
  dispose?.();
13
- dispose = router.on('found', async ({ tag, params, query, hash, chain, direction }) => {
13
+ dispose = router.on('found', async ({ tag, params, query, hash, chain, via, container, direction }) => {
14
14
  // Resolve the top-level layout element in the chain
15
15
  const topTag = chain && chain.length > 0 ? chain[0].tag : tag;
16
16
  const topParams = chain && chain.length > 0 ? chain[0].params : params;
@@ -23,9 +23,9 @@ export function initOrchestrator() {
23
23
  const spec = specRegistry.get(topTag.toLowerCase());
24
24
  // Resolve the render target: the last container in the `via` chain, or
25
25
  // the legacy single `container`. Without either there is nothing to mount.
26
- const target = (Array.isArray(spec?.via) && spec.via.length)
26
+ const target = container ?? ((Array.isArray(spec?.via) && spec.via.length)
27
27
  ? spec.via[spec.via.length - 1]
28
- : spec?.container;
28
+ : spec?.container);
29
29
  if (!spec || !target) {
30
30
  console.warn('[Orchestrator] Early return: missing spec or target');
31
31
  return;
@@ -35,7 +35,11 @@ export function initOrchestrator() {
35
35
  // The interceptor's cascade has already ensured the chain is mounted.
36
36
  const containerEl = router.getContainer(target);
37
37
  if (!containerEl) {
38
- console.warn(`Target container "${target}" not found in DOM for element <${topTag}>`);
38
+ console.warn(
39
+ `[Orchestrator] Container "${target}" is not mounted — cannot render <${topTag}>.\n` +
40
+ `Make sure dock('${target}', { parent: '...' }) is declared and imported before this page.\n` +
41
+ `Full via chain expected: ${JSON.stringify(spec?.via ?? [])}`
42
+ );
39
43
  return;
40
44
  }
41
45
 
@@ -24,24 +24,34 @@ const CONTAIN = ':host { contain: layout; display: block; }';
24
24
  * @param {string} [config.tag] - tag name; defaults to `dock-<name>`.
25
25
  * @param {string} [config.parent='body'] - parent dock key in the graph.
26
26
  * @param {object} [config.template] - { html, css, shadow }.
27
+ * @param {string|object} [config.notfound] - fallback rendered when a 404
28
+ * lands in this dock. Pass a raw HTML string or { html } object.
29
+ * The deepest configured dock wins at runtime.
27
30
  * @param {string} [base] - import.meta.url of the caller (file templates).
28
31
  */
29
32
  export function dock(name, config = {}, base) {
30
33
  const tag = config.tag ?? `dock-${name}`;
31
34
  const parent = config.parent ?? 'main';
32
35
 
36
+ // Statically register the layout container in the graph with a null element
37
+ router.registerContainer(name, null, parent, tag);
38
+
33
39
  const spec = translate(config);
34
40
 
35
41
  // Default passthrough template — a dock is a shell around its slotted content.
36
42
  if (spec.template == null) spec.template = '<slot></slot>';
37
43
  // Prepend containment styling to whatever the dock declares.
38
- spec.style = spec.style ? `${CONTAIN}\n${spec.style}` : CONTAIN;
44
+ if (spec.style != null) {
45
+ spec.style = Array.isArray(spec.style) ? [CONTAIN, ...spec.style] : [CONTAIN, spec.style];
46
+ } else {
47
+ spec.style = CONTAIN;
48
+ }
39
49
 
40
50
  // Register in the graph on connect, unregister on disconnect. Wrap any
41
51
  // user-supplied connect/disconnect rather than clobbering them.
42
52
  const userMount = spec.mount;
43
53
  spec.mount = (ctx) => {
44
- router.registerContainer(name, ctx.el, parent);
54
+ router.registerContainer(name, ctx.el, parent, tag);
45
55
  return userMount?.(ctx);
46
56
  };
47
57
  const userUnmount = spec.unmount;
@@ -62,6 +72,13 @@ export function dock(name, config = {}, base) {
62
72
  if (Cls && !Cls.prototype.swapView) {
63
73
  Object.defineProperty(Cls.prototype, 'swapView', { value: swap, configurable: true });
64
74
  }
75
+
76
+ // Store user-supplied notfound template on the class as a static property.
77
+ // intercept.js reads Cls.notfound when rendering 404 fallbacks.
78
+ if (Cls && config.notfound != null) {
79
+ const nf = config.notfound;
80
+ Cls.notfound = typeof nf === 'string' ? nf : (nf?.html ?? null);
81
+ }
65
82
  }
66
83
 
67
84
  /**
@@ -104,26 +121,22 @@ async function swap(el, options = {}) {
104
121
  }
105
122
  }
106
123
 
124
+ // Only use element-scoped view transitions. If the browser doesn't support
125
+ // this.startViewTransition (Chrome < 147), fall back to a direct synchronous
126
+ // swap. Document-level startViewTransition() captures the entire page and
127
+ // causes the sidebar/header to flicker — we never want that.
107
128
  if (typeof this.startViewTransition === 'function') {
108
129
  try {
130
+ // Assign a named view-transition so CSS can target this element's
131
+ // group explicitly, keeping the root group stable.
132
+ this.style.viewTransitionName = 'dock-swap';
109
133
  this._tx = this.startViewTransition(go);
110
134
  await this._tx.finished;
111
135
  } catch (err) {
112
136
  if (err?.name !== 'AbortError') console.warn('[Native UI] dock scoped VT aborted:', err);
113
137
  } finally {
114
138
  this._tx = null;
115
- restore();
116
- }
117
- return;
118
- }
119
-
120
- if (typeof document !== 'undefined' && typeof document.startViewTransition === 'function') {
121
- try {
122
- const vt = document.startViewTransition(go);
123
- await vt.finished;
124
- } catch (err) {
125
- if (err?.name !== 'AbortError') console.warn('[Native UI] dock document VT aborted:', err);
126
- } finally {
139
+ this.style.viewTransitionName = '';
127
140
  restore();
128
141
  }
129
142
  return;
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * src/core/ui/defs/page.js
3
3
  *
4
- * `page(route, config, base)` — a route-bound navigable unit. Maps a URL
5
- * pattern to a custom element, declares the ordered container chain (`via`) the
6
- * router traverses to reach the render target, and gates the boot sequence on
7
- * the element's definition so a hard refresh waits for it.
4
+ * `page(route, config, base)` — a route-bound navigable unit. Maps one or more
5
+ * URL patterns to a custom element, declares the ordered container chain (`via`)
6
+ * the router traverses to reach the render target, and gates the boot sequence
7
+ * on the element's definition so a hard refresh waits for it.
8
+ *
9
+ * Route may now be a single string or an array of strings:
10
+ * page('/blog', config)
11
+ * page(['/blog', '/blog/:slug'], config)
8
12
  *
9
13
  * Source: definations.md §3, tasks.md Phase 6
10
14
  */
@@ -12,17 +16,17 @@
12
16
  import { router } from '../../router/index.js';
13
17
  import { gate } from '../../router/boot.js';
14
18
  import { element } from '../define/element.js';
15
- import { specRegistry } from '../define/state.js';
16
19
  import { translate } from './spec.js';
17
20
 
18
21
  /**
19
- * @param {string} route - URL pattern, e.g. '/profile/:id'.
22
+ * @param {string | string[]} route - URL pattern(s), e.g. '/blog' or ['/blog', '/blog/:slug'].
20
23
  * @param {object} config - page definition.
21
24
  * @param {string} config.tag - custom element tag (must contain a hyphen).
22
25
  * @param {string[]} [config.via] - ordered container chain, root-to-leaf.
23
26
  * @param {string} [config.container] - single container (back-compat for via).
24
- * @param {object} [config.props] - reactive props.
25
- * @param {string[]} [config.query] - query params to map onto props.
27
+ * @param {object} [config.props] - generic reactive props.
28
+ * @param {Array<{name:string, type:Function}>} [config.params] - path param contract.
29
+ * @param {Array<{name:string, type:Function}>} [config.query] - query param contract.
26
30
  * @param {Function} [config.guard] - route-scoped navigation guard.
27
31
  * @param {object} [config.on] - lifecycle hooks (load, connect, disconnect, change).
28
32
  * @param {string} [base] - import.meta.url of the caller (file templates).
@@ -30,10 +34,14 @@ import { translate } from './spec.js';
30
34
  export function page(route, config, base) {
31
35
  const tag = config.tag;
32
36
  if (!tag) {
33
- console.error(`[Native UI] page('${route}') is missing a 'tag'.`);
37
+ const routeStr = Array.isArray(route) ? route.join(', ') : route;
38
+ console.error(`[Native UI] page('${routeStr}') is missing a 'tag'.`);
34
39
  return;
35
40
  }
36
41
 
42
+ // Normalise the route into an array of patterns.
43
+ const routes = Array.isArray(route) ? route : [route];
44
+
37
45
  // Normalise the container chain. The render target is the last container.
38
46
  const via = Array.isArray(config.via) && config.via.length
39
47
  ? config.via
@@ -42,48 +50,86 @@ export function page(route, config, base) {
42
50
 
43
51
  const spec = translate(config, { visual: true });
44
52
  // Carry routing metadata on the spec so the orchestrator can resolve the
45
- // render target and cast query params (specRegistry is populated by element()).
53
+ // render target and cast params/query (specRegistry is populated by element()).
46
54
  spec.via = via;
47
55
  spec.container = target;
48
- if (config.query) spec.query = config.query;
56
+ spec.lazy = true;
57
+
58
+ // Store the typed params/query contract on the spec for use by intercept.js.
59
+ // Each entry: { name: string, cast: 'string' | 'number' }
60
+ if (Array.isArray(config.params)) {
61
+ spec.params = config.params.map(p => ({
62
+ name: p.name,
63
+ cast: p.type === Number ? 'number' : 'string',
64
+ }));
65
+ }
66
+ if (Array.isArray(config.query)) {
67
+ spec.query = config.query.map(q => ({
68
+ name: q.name,
69
+ cast: q.type === Number ? 'number' : 'string',
70
+ }));
71
+ }
49
72
 
50
73
  element(tag, spec, base);
51
74
 
52
- // Register the route. Keep both `via` (full chain) and `container` (target)
53
- // in meta so the interceptor cascade and orchestrator both work.
54
- router.register(route, tag, {
55
- ...config.meta,
56
- via,
57
- container: target
58
- });
75
+ // Register all route patterns under the same tag.
76
+ // Keep both `via` (full chain) and `container` (target) in meta so the
77
+ // interceptor cascade and orchestrator both work.
78
+ for (const pattern of routes) {
79
+ router.register(pattern, tag, {
80
+ ...config.meta,
81
+ via,
82
+ container: target,
83
+ });
84
+ }
85
+
86
+ // Dev-time guard: verify every container in `via` has been registered in
87
+ // the graph by a dock() call. Deferred via queueMicrotask so that all
88
+ // synchronous dock() declarations in the same module bundle run first —
89
+ // this prevents false positives from import order within a single file.
90
+ if (typeof queueMicrotask !== 'undefined') {
91
+ queueMicrotask(() => {
92
+ for (const name of via) {
93
+ if (name !== 'main' && !router.hasContainer(name)) {
94
+ console.error(
95
+ `[Native UI] <${tag}> declares via:'${name}' but dock('${name}', ...) has not been called.\n` +
96
+ `Import the file containing dock('${name}', ...) before this page definition, or add dock('${name}', { parent: '...' }).`
97
+ );
98
+ }
99
+ }
100
+ });
101
+ }
59
102
 
60
103
  // Hold the initial match until this element is defined (hard-refresh safety).
61
104
  if (typeof customElements !== 'undefined') {
62
105
  gate(customElements.whenDefined(tag));
63
106
  }
64
107
 
65
- // Route-scoped guard: only runs when the destination matches this route.
108
+ // Route-scoped guard: registers once and matches any of the declared patterns.
66
109
  if (typeof config.guard === 'function') {
67
- registerGuard(route, config.guard);
110
+ registerGuard(routes, config.guard);
68
111
  }
69
112
  }
70
113
 
71
114
  /** Adds a global guard that delegates to `fn` only for matching destinations. */
72
- function registerGuard(route, fn) {
73
- let pattern = null;
115
+ function registerGuard(patterns, fn) {
74
116
  const Pattern = typeof URLPattern !== 'undefined' ? URLPattern : null;
75
- if (Pattern) {
76
- try {
77
- pattern = route.startsWith('http') ? new Pattern(route) : new Pattern({ pathname: route });
78
- } catch (_) { pattern = null; }
79
- }
117
+ const compiled = Pattern
118
+ ? patterns.map(p => {
119
+ try {
120
+ return p.startsWith('http') ? new Pattern(p) : new Pattern({ pathname: p });
121
+ } catch (_) { return null; }
122
+ }).filter(Boolean)
123
+ : [];
80
124
 
81
125
  router.guard(async (destination, controller) => {
82
- if (pattern) {
126
+ if (compiled.length > 0) {
83
127
  let url = destination?.url;
84
128
  try { url = new URL(url, globalThis.location?.href).href; } catch (_) {}
85
- if (!pattern.test(url)) return null; // not this route — allow
129
+ const matches = compiled.some(pat => pat.test(url));
130
+ if (!matches) return null; // not this route — allow
86
131
  }
87
132
  return fn(destination, controller);
88
133
  });
89
134
  }
135
+
@@ -24,9 +24,20 @@ const HOOKS = new Set(['load', 'connect', 'disconnect', 'change']);
24
24
 
25
25
  /**
26
26
  * Collects the route-derived params currently set on an element, keyed by the
27
- * declared prop names. Used to hand `on.load`/`on.connect` a `{ params }` bag.
27
+ * declared param names from the contract array.
28
+ * Used to hand `on.load`/`on.connect` a `{ params }` bag.
29
+ *
30
+ * @param {HTMLElement} el
31
+ * @param {Array<{name:string, cast:string}>} [paramDecls] - from spec.params
32
+ * @param {object} [props] - legacy props map fallback
28
33
  */
29
- function paramsOf(el, props) {
34
+ function paramsOf(el, paramDecls, props) {
35
+ // New contract: ordered array with first/last getters.
36
+ if (Array.isArray(paramDecls) && paramDecls.length > 0) {
37
+ const arr = paramDecls.map(({ name }) => el[name] ?? null);
38
+ return makeAccessorArray(arr, paramDecls.map(d => d.name));
39
+ }
40
+ // Legacy fallback: collect from props keys.
30
41
  const out = {};
31
42
  if (props) {
32
43
  for (const key of Object.keys(props)) out[key] = el[key];
@@ -34,6 +45,44 @@ function paramsOf(el, props) {
34
45
  return out;
35
46
  }
36
47
 
48
+ /**
49
+ * Collects the route-derived query values currently set on an element.
50
+ * Returns an ordered array with first/last getters.
51
+ *
52
+ * @param {HTMLElement} el
53
+ * @param {Array<{name:string, cast:string}>} [queryDecls]
54
+ */
55
+ function queryOf(el, queryDecls) {
56
+ if (!Array.isArray(queryDecls) || queryDecls.length === 0) return [];
57
+ const arr = queryDecls.map(({ name }) => el[name] ?? null);
58
+ return makeAccessorArray(arr, queryDecls.map(d => d.name));
59
+ }
60
+
61
+ /**
62
+ * Wraps a plain array with non-enumerable `first` and `last` getters and a
63
+ * keyed named-accessor map so callers can use both positional and named access.
64
+ *
65
+ * @param {any[]} values
66
+ * @param {string[]} names
67
+ */
68
+ function makeAccessorArray(values, names) {
69
+ const arr = [...values];
70
+ Object.defineProperties(arr, {
71
+ first: { get() { return arr[0] ?? null; }, enumerable: false },
72
+ last: { get() { return arr[arr.length - 1] ?? null; }, enumerable: false },
73
+ });
74
+ // Named getters: arr.slug === arr[0] if slug was the first param
75
+ names.forEach((name, i) => {
76
+ if (!(name in arr)) {
77
+ Object.defineProperty(arr, name, {
78
+ get() { return arr[i] ?? null; },
79
+ enumerable: false,
80
+ });
81
+ }
82
+ });
83
+ return arr;
84
+ }
85
+
37
86
  /**
38
87
  * Translates a declarative definition config into an `element()` spec.
39
88
  *
@@ -44,20 +93,37 @@ function paramsOf(el, props) {
44
93
  */
45
94
  export function translate(config, opts = {}) {
46
95
  const spec = {};
47
- const tpl = config.template ?? {};
96
+ let html = null;
97
+ let css = null;
98
+ let shadow = 'open';
99
+
100
+ if (config.template != null) {
101
+ if (typeof config.template === 'object') {
102
+ html = config.template.html;
103
+ css = config.template.css;
104
+ shadow = config.template.shadow ?? 'open';
105
+ } else if (typeof config.template === 'string') {
106
+ html = config.template;
107
+ }
108
+ }
109
+
110
+ if (config.style != null) {
111
+ css = config.style;
112
+ }
48
113
 
49
- // Template + style + shadow mode. `element()` takes html/css as separate
50
- // string fields (inline source or a resolvable file path) and a shadow mode.
51
- if (tpl.html != null) spec.template = tpl.html;
52
- if (tpl.css != null) spec.style = tpl.css;
53
- spec.mode = tpl.shadow === 'closed' ? 'closed' : 'open';
54
- if (tpl.shadow === false) {
114
+ if (html != null) spec.template = html;
115
+ if (css != null) spec.style = css;
116
+ spec.mode = shadow === 'closed' ? 'closed' : 'open';
117
+ if (shadow === false) {
55
118
  console.warn('[Native UI] Light DOM (shadow: false) is not supported by the element factory; falling back to open shadow root.');
56
119
  }
57
120
 
58
121
  if (config.props) spec.props = config.props;
59
122
  if (config.form) spec.form = config.form;
60
- if (config.query) spec.query = config.query;
123
+
124
+ // New contract: params and query are typed arrays — handled by page.js and
125
+ // stored on the spec. No action needed here; intercept.js reads spec.params
126
+ // and spec.query directly from specRegistry at navigation time.
61
127
 
62
128
  // Install all `on` entries (helpers + hooks) and explicit `methods` as
63
129
  // instance methods, so a hook body can call `this.<helper>()`.
@@ -74,7 +140,11 @@ export function translate(config, opts = {}) {
74
140
  spec.mount = async (ctx) => {
75
141
  const el = ctx.el;
76
142
  if (on.load) {
77
- await on.load.call(el, { params: paramsOf(el, config.props), ...ctx });
143
+ // Provide the full contract-aware context bag.
144
+ // spec.params / spec.query are set by page.js before element() is called.
145
+ const params = paramsOf(el, spec.params, config.props);
146
+ const query = queryOf(el, spec.query);
147
+ await on.load.call(el, { params, query, raw: ctx.raw ?? null, ...ctx });
78
148
  }
79
149
  if (on.connect) {
80
150
  await on.connect.call(el, ctx);
@@ -27,7 +27,7 @@
27
27
  padding: var(--space-3) var(--space-4);
28
28
  border-bottom: var(--space-px) solid var(--color-border-default);
29
29
  box-sizing: border-box;
30
- transition: background-color var(--duration-fast) var(--ease-out);
30
+ /* transition: background-color var(--duration-fast) var(--ease-out); */
31
31
  }
32
32
 
33
33
  ::slotted(ui-list-item:last-child), ::slotted(div[role="listitem"]:last-child) {
@@ -2,7 +2,8 @@
2
2
  * src/elements/data/table/index.js
3
3
  *
4
4
  * Data Element: <ui-table>
5
- * Responsive data table layout wrapping native standard <table> tags and attaching
5
+ * Responsive data table layout wrapping native standard <div class="table-wrap">
6
+ <table> tags and attaching
6
7
  * complete semantic design token styles using slotted tree CSS.
7
8
  *
8
9
  * Source: doc 04 — Web Components §3, doc 05 — Native UI Primitives §3
@@ -33,7 +33,7 @@
33
33
 
34
34
  ::slotted(tr) {
35
35
  border-bottom: var(--space-px) solid var(--color-border-default);
36
- transition: background-color var(--duration-fast) var(--ease-out);
36
+ /* transition: background-color var(--duration-fast) var(--ease-out); */
37
37
  }
38
38
 
39
39
  ::slotted(tr:last-child) {
@@ -38,7 +38,7 @@ button {
38
38
  display: none;
39
39
  align-items: center;
40
40
  justify-content: center;
41
- transition: opacity var(--duration-fast) var(--ease-out);
41
+ /* transition: opacity var(--duration-fast) var(--ease-out); */
42
42
  }
43
43
 
44
44
  button:hover {
@@ -8,33 +8,32 @@
8
8
 
9
9
  @layer base {
10
10
  :root {
11
- font-family: var(--font-sans);
12
- font-size: var(--font-size-base);
11
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
12
+ font-size: var(--font-size-base, 1rem);
13
13
  color-scheme: light dark;
14
14
  }
15
15
 
16
16
  body {
17
- background-color: var(--color-surface-page);
18
- color: var(--color-content-primary);
17
+ background-color: var(--background);
18
+ color: var(--text-color);
19
19
  min-height: 100vh;
20
- font-weight: var(--font-weight-regular);
21
- line-height: var(--line-height-normal);
22
- transition: background-color 220ms var(--ease-out), color 180ms var(--ease-out);
20
+ font-weight: var(--font-weight-regular, 400);
21
+ line-height: var(--line-height-normal, 1.5);
22
+ /* transition: background-color 220ms var(--ease-out, ease-out), color 180ms var(--ease-out, ease-out); */
23
23
  }
24
24
 
25
25
  /* Typography Defaults */
26
26
  h1, h2, h3, h4, h5, h6 {
27
- font-family: var(--font-sans);
28
- color: var(--color-content-primary);
27
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
28
+ color: var(--text-color);
29
29
  font-weight: var(--font-weight-semibold);
30
- line-height: var(--line-height-tight);
31
- letter-spacing: var(--letter-spacing-tight);
30
+ line-height: var(--line-height-tight, 1.25);
32
31
  }
33
32
 
34
- h1 { font-size: var(--font-size-3xl); margin-bottom: var(--space-4); }
35
- h2 { font-size: var(--font-size-2xl); margin-bottom: var(--space-3); }
36
- h3 { font-size: var(--font-size-xl); margin-bottom: var(--space-2); }
37
- h4 { font-size: var(--font-size-lg); }
33
+ h1 { font-size: var(--font-size-3xl, 2.25rem); margin-bottom: var(--space-4); }
34
+ h2 { font-size: var(--font-size-2xl, 1.875rem); margin-bottom: var(--space-3); }
35
+ h3 { font-size: var(--font-size-xl, 1.5rem); margin-bottom: var(--space-2); }
36
+ h4 { font-size: var(--font-size-lg, 1.25rem); }
38
37
 
39
38
  p {
40
39
  margin-bottom: var(--space-4);
@@ -42,25 +41,26 @@
42
41
  }
43
42
 
44
43
  a {
45
- color: var(--color-content-link);
44
+ color: var(--color-interactive);
46
45
  text-decoration: none;
47
- transition: color var(--duration-fast) var(--ease-out);
46
+ transition: opacity var(--duration-fast, 120ms) var(--ease-out, ease-out);
48
47
  }
49
48
 
50
49
  a:hover {
51
- color: var(--color-interactive-hover);
50
+ opacity: 0.85;
52
51
  text-decoration: underline;
53
52
  }
54
53
 
55
54
  /* Selection highlight styles */
56
55
  ::selection {
57
56
  background-color: var(--color-interactive);
58
- color: var(--color-content-inverse);
57
+ color: var(--color-surface-page);
59
58
  }
60
59
 
61
- /* Custom high-contrast visual focus-ring */
60
+
61
+ /* Custom visual focus-ring */
62
62
  :focus-visible {
63
- outline: 2px solid var(--color-border-focus);
63
+ outline: 2px solid var(--color-interactive);
64
64
  outline-offset: 2px;
65
65
  }
66
66
 
@@ -79,3 +79,4 @@
79
79
  min-height: 100vh;
80
80
  }
81
81
  }
82
+
@@ -11,8 +11,6 @@
11
11
  *::before,
12
12
  *::after {
13
13
  box-sizing: border-box;
14
- margin: 0;
15
- padding: 0;
16
14
  }
17
15
 
18
16
  html,
@@ -11,9 +11,6 @@
11
11
  @import './primitives/spacing.css';
12
12
  @import './primitives/typography.css';
13
13
  @import './primitives/motion.css';
14
- @import './primitives/radius.css';
15
- @import './primitives/shadow.css';
16
- @import './primitives/zindex.css';
17
14
 
18
15
  /* 2. Registered (Houdini Typed Properties for Animation) */
19
16
  @import './registered/colors.css';
@@ -23,5 +20,5 @@
23
20
  @import './semantic/light.css';
24
21
  @import './semantic/dark.css';
25
22
  @import './semantic/contrast.css';
26
- @import './semantic/components.css';
27
23
  @import './semantic/transitions.css';
24
+