@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.
- package/bin/anza/anza-linux-arm64 +0 -0
- package/bin/anza/anza-linux-x64 +0 -0
- package/bin/anza/anza-macos-arm64 +0 -0
- package/bin/anza/anza-macos-x64 +0 -0
- package/bin/anza/anza-windows-x64.exe +0 -0
- package/package.json +1 -1
- package/src/core/api/index.js +2 -2
- package/src/core/offline/sync.js +2 -2
- package/src/core/router/cascade.js +23 -18
- package/src/core/router/container.js +6 -2
- package/src/core/router/graph.js +46 -15
- package/src/core/router/index.js +7 -1
- package/src/core/router/intercept.js +397 -100
- package/src/core/router/match.js +30 -0
- package/src/core/router/transitions.js +4 -3
- package/src/core/ui/define/container.js +3 -13
- package/src/core/ui/define/element.js +58 -8
- package/src/core/ui/define/orchestrator.js +8 -4
- package/src/core/ui/defs/dock.js +27 -14
- package/src/core/ui/defs/page.js +75 -29
- package/src/core/ui/defs/spec.js +81 -11
- package/src/elements/data/list/style.css +1 -1
- package/src/elements/data/table/index.js +2 -1
- package/src/elements/data/table/style.css +1 -1
- package/src/elements/feedback/alert/style.css +1 -1
- package/src/styles/base.css +22 -21
- package/src/styles/reset.css +0 -2
- package/src/tokens/index.css +1 -4
- package/src/tokens/primitives/colors.css +9 -64
- package/src/tokens/primitives/motion.css +6 -18
- package/src/tokens/primitives/spacing.css +7 -12
- package/src/tokens/primitives/typography.css +12 -42
- package/src/tokens/registered/colors.css +5 -96
- package/src/tokens/registered/dimensions.css +6 -19
- package/src/tokens/semantic/contrast.css +8 -36
- package/src/tokens/semantic/dark.css +13 -48
- package/src/tokens/semantic/light.css +13 -44
- package/src/tokens/semantic/transitions.css +26 -13
- package/CHANGELOG.md +0 -360
- package/src/tokens/primitives/radius.css +0 -16
- package/src/tokens/primitives/shadow.css +0 -34
- package/src/tokens/primitives/zindex.css +0 -18
- 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
|
-
//
|
|
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 =
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
package/src/core/ui/defs/dock.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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;
|
package/src/core/ui/defs/page.js
CHANGED
|
@@ -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
|
|
5
|
-
*
|
|
6
|
-
* router traverses to reach the render target, and gates the boot sequence
|
|
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. '/
|
|
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
|
|
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
|
-
|
|
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
|
|
53
|
+
// render target and cast params/query (specRegistry is populated by element()).
|
|
46
54
|
spec.via = via;
|
|
47
55
|
spec.container = target;
|
|
48
|
-
|
|
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
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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:
|
|
108
|
+
// Route-scoped guard: registers once and matches any of the declared patterns.
|
|
66
109
|
if (typeof config.guard === 'function') {
|
|
67
|
-
registerGuard(
|
|
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(
|
|
73
|
-
let pattern = null;
|
|
115
|
+
function registerGuard(patterns, fn) {
|
|
74
116
|
const Pattern = typeof URLPattern !== 'undefined' ? URLPattern : null;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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 (
|
|
126
|
+
if (compiled.length > 0) {
|
|
83
127
|
let url = destination?.url;
|
|
84
128
|
try { url = new URL(url, globalThis.location?.href).href; } catch (_) {}
|
|
85
|
-
|
|
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
|
+
|
package/src/core/ui/defs/spec.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
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) {
|
package/src/styles/base.css
CHANGED
|
@@ -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(--
|
|
18
|
-
color: var(--color
|
|
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
|
|
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-
|
|
44
|
+
color: var(--color-interactive);
|
|
46
45
|
text-decoration: none;
|
|
47
|
-
transition:
|
|
46
|
+
transition: opacity var(--duration-fast, 120ms) var(--ease-out, ease-out);
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
a:hover {
|
|
51
|
-
|
|
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-
|
|
57
|
+
color: var(--color-surface-page);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
|
|
61
|
+
/* Custom visual focus-ring */
|
|
62
62
|
:focus-visible {
|
|
63
|
-
outline: 2px solid var(--color-
|
|
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
|
+
|
package/src/styles/reset.css
CHANGED
package/src/tokens/index.css
CHANGED
|
@@ -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
|
+
|