@webjsdev/core 0.7.49 → 0.7.51
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/dist/webjs-core-browser.js +5 -5
- package/dist/webjs-core-browser.js.map +3 -3
- package/dist/webjs-core-testing.js +21 -21
- package/dist/webjs-core-testing.js.map +3 -3
- package/dist/webjs-core.js +7 -7
- package/dist/webjs-core.js.map +3 -3
- package/index-browser.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/attribute-reader.js +178 -0
- package/src/component/lifecycle.js +1481 -0
- package/src/component/reactive.js +171 -0
- package/src/component/server-element.js +248 -0
- package/src/component.d.ts +10 -0
- package/src/component.js +2 -1931
- package/src/html-entities.js +2179 -0
- package/src/render-client/parts.js +2007 -0
- package/src/render-client/reconciler.js +388 -0
- package/src/render-client/template-compiler.js +525 -0
- package/src/render-client.js +1 -2939
- package/src/render-server/attrs.js +160 -0
- package/src/render-server/dsd.js +445 -0
- package/src/render-server/html-scan.js +402 -0
- package/src/render-server/slots.js +213 -0
- package/src/render-server/stream.js +163 -0
- package/src/render-server/template-renderer.js +923 -0
- package/src/render-server/text.js +163 -0
- package/src/render-server.js +6 -2334
- package/src/router-client/anchors.js +44 -0
- package/src/router-client/boundaries.js +221 -0
- package/src/router-client/constants.js +187 -0
- package/src/router-client/diagnostics.js +377 -0
- package/src/router-client/dom-differ.js +709 -0
- package/src/router-client/dom-parse.js +150 -0
- package/src/router-client/events.js +139 -0
- package/src/router-client/fetch-apply.js +387 -0
- package/src/router-client/form-encoder.js +135 -0
- package/src/router-client/frames.js +329 -0
- package/src/router-client/head-merge.js +344 -0
- package/src/router-client/nav-error.js +105 -0
- package/src/router-client/navigator.js +781 -0
- package/src/router-client/prefetch.js +824 -0
- package/src/router-client/scroll.js +259 -0
- package/src/router-client/snapshot-cache.js +97 -0
- package/src/router-client/state.js +92 -0
- package/src/router-client/stream.js +206 -0
- package/src/router-client/swap.js +451 -0
- package/src/router-client/upgrade.js +261 -0
- package/src/router-client/view-transition.js +223 -0
- package/src/router-client.d.ts +10 -1
- package/src/router-client.js +75 -5345
- package/src/slot/assignment.js +304 -0
- package/src/slot/interception.js +558 -0
- package/src/slot/polyfills.js +365 -0
- package/src/slot/project.js +432 -0
- package/src/slot/sensors.js +303 -0
- package/src/slot/state.js +245 -0
- package/src/slot/symbols.js +85 -0
- package/src/slot.js +7 -2199
- package/src/webjs-config.d.ts +13 -2
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { tagOf } from '../registry.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default change detection: strict inequality.
|
|
5
|
+
* @param {unknown} a
|
|
6
|
+
* @param {unknown} b
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function defaultHasChanged(a, b) {
|
|
10
|
+
return a !== b;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `String(v)` that cannot itself throw.
|
|
15
|
+
*
|
|
16
|
+
* A rejection reason is whatever the author rejected with, and not every value
|
|
17
|
+
* converts to a primitive: `Object.create(null)` has no `toString`, and a
|
|
18
|
+
* revoked Proxy throws on any operation. Coercing one in an error-reporting
|
|
19
|
+
* path turned a reportable failure into a second, unreported one.
|
|
20
|
+
*
|
|
21
|
+
* @param {unknown} v
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export function safeString(v) {
|
|
25
|
+
try {
|
|
26
|
+
return String(v);
|
|
27
|
+
} catch {
|
|
28
|
+
return Object.prototype.toString.call(v);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Warn that a function value was dropped rather than reflected (#1169).
|
|
34
|
+
*
|
|
35
|
+
* A silently missing attribute is its own confusion, so say what went and why.
|
|
36
|
+
* The message deliberately does NOT include the value: printing the source is
|
|
37
|
+
* the leak this guard exists to prevent, and a server log is not always a
|
|
38
|
+
* private place.
|
|
39
|
+
*
|
|
40
|
+
* UNCONDITIONAL, matching the `.prop=${fn}` unserializable-value drop in
|
|
41
|
+
* `render-server.js`, which is the sibling path this guard mirrors. Two
|
|
42
|
+
* reasons not to gate it on a dev flag. It fires only on a genuine mistake (a
|
|
43
|
+
* function is never a meaningful attribute value), so there is no volume to
|
|
44
|
+
* suppress, and reflection runs per assignment rather than per frame.
|
|
45
|
+
*
|
|
46
|
+
* More decisively, a `NODE_ENV` gate does not survive the dist build.
|
|
47
|
+
* `scripts/build-framework-dist.js` runs esbuild with `platform: 'browser'`
|
|
48
|
+
* and `minify: true`, which substitutes `process.env.NODE_ENV` with
|
|
49
|
+
* `"production"` and folds the check to a constant. The SSR half of the
|
|
50
|
+
* warning would then be unreachable in every published build, which is how
|
|
51
|
+
* every installed app runs, and SSR is the half that matters most, since that
|
|
52
|
+
* is where the leaked source reached visitors.
|
|
53
|
+
*
|
|
54
|
+
* @param {{ constructor: unknown, tagName?: string }} host
|
|
55
|
+
* @param {string} propName
|
|
56
|
+
* @param {string} attrName
|
|
57
|
+
*/
|
|
58
|
+
export function warnFunctionReflection(host, propName, attrName) {
|
|
59
|
+
if (typeof console === 'undefined' || !console.warn) return;
|
|
60
|
+
const tag = tagOf(/** @type any */ (host.constructor)) || host.tagName?.toLowerCase() || 'unknown';
|
|
61
|
+
console.warn(
|
|
62
|
+
`[webjs] reflect:true property "${propName}" on <${tag}> holds a function `
|
|
63
|
+
+ `(or an array carrying one), which has no HTML attribute representation. `
|
|
64
|
+
+ `Removing "${attrName}" instead of stringifying it (a stringified function `
|
|
65
|
+
+ `writes its source into the page, so a server action's body would ship to `
|
|
66
|
+
+ `the browser). Pass a string, or drop reflect on this property.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Warn that a value with no JSON representation was dropped rather than
|
|
72
|
+
* reflected (#1253).
|
|
73
|
+
*
|
|
74
|
+
* `JSON.stringify` throws for three reasons an app hits in practice: a cycle,
|
|
75
|
+
* a `BigInt`, and an author `toJSON()` that throws. All three mean the same
|
|
76
|
+
* thing here, that there is no string to put in the attribute, so all three
|
|
77
|
+
* get the same answer as a function does.
|
|
78
|
+
*
|
|
79
|
+
* The caught message IS included, unlike `warnFunctionReflection`, which
|
|
80
|
+
* withholds its value on purpose. A function's string form is its source,
|
|
81
|
+
* which is the leak that guard exists to prevent; `JSON.stringify`'s own
|
|
82
|
+
* message names the property path rather than a value, and the sibling
|
|
83
|
+
* `.prop=${val}` SSR drop in `render-server.js` already ends the same way.
|
|
84
|
+
*
|
|
85
|
+
* UNCONDITIONAL, for the reason recorded on `warnFunctionReflection` above: a
|
|
86
|
+
* `NODE_ENV` gate is folded to a constant by the dist build, which would make
|
|
87
|
+
* the SSR half unreachable in every published build, and the SSR half is where
|
|
88
|
+
* the component silently vanishes.
|
|
89
|
+
*
|
|
90
|
+
* @param {{ constructor: unknown, tagName?: string }} host
|
|
91
|
+
* @param {string} propName
|
|
92
|
+
* @param {string} attrName
|
|
93
|
+
* @param {string} [detail] the message `JSON.stringify` threw
|
|
94
|
+
*/
|
|
95
|
+
export function warnUnserializableReflection(host, propName, attrName, detail) {
|
|
96
|
+
if (typeof console === 'undefined' || !console.warn) return;
|
|
97
|
+
const tag = tagOf(/** @type any */ (host.constructor)) || host.tagName?.toLowerCase() || 'unknown';
|
|
98
|
+
console.warn(
|
|
99
|
+
`[webjs] reflect:true property "${propName}" on <${tag}> holds a value `
|
|
100
|
+
+ `JSON.stringify cannot serialize (a cycle, a BigInt, or a throwing `
|
|
101
|
+
+ `toJSON), so it has no HTML attribute representation. Removing `
|
|
102
|
+
+ `"${attrName}" instead. Detail: ${detail}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A minimal base for HTML Custom Elements that mirrors Lit's ergonomics
|
|
108
|
+
* while staying JSDoc-only and no-build.
|
|
109
|
+
*
|
|
110
|
+
* Subclasses declare:
|
|
111
|
+
* - `static properties`: attribute/property declarations with type info,
|
|
112
|
+
* reflection, custom converters, and internal-state mode
|
|
113
|
+
* - `static styles`: CSSResult or array thereof (only meaningful with
|
|
114
|
+
* `static shadow = true`; light-DOM components inherit global CSS)
|
|
115
|
+
* - `static shadow`: set `true` to opt in to shadow DOM (default: `false`
|
|
116
|
+
* → light DOM, so Tailwind / global CSS apply directly)
|
|
117
|
+
* - `render()`: returns a TemplateResult
|
|
118
|
+
*
|
|
119
|
+
* The tag name is not a static field: pass it to `.register('tag-name')`
|
|
120
|
+
* at the bottom of the file. Tag must contain a hyphen (HTML spec).
|
|
121
|
+
*
|
|
122
|
+
* Lifecycle (lit-aligned, called in order during each update cycle):
|
|
123
|
+
* 1. `shouldUpdate(changedProperties)`. Skip update if false.
|
|
124
|
+
* 2. `willUpdate(changedProperties)`. Safe to set properties; folds into this cycle.
|
|
125
|
+
* 3. controllers' `hostUpdate()`
|
|
126
|
+
* 4. `update(changedProperties)`. Default impl calls `render()` + commits.
|
|
127
|
+
* 5. controllers' `hostUpdated()`
|
|
128
|
+
* 6. `firstUpdated(changedProperties)`: once, on the first render only
|
|
129
|
+
* 7. `updated(changedProperties)`: every render commit
|
|
130
|
+
* 8. `updateComplete` promise resolves
|
|
131
|
+
*
|
|
132
|
+
* `changedProperties` is a `Map<string, unknown>` where each entry maps
|
|
133
|
+
* a property name to its previous value.
|
|
134
|
+
*
|
|
135
|
+
* MAINTAINER NOTE. Adding a new overridable lifecycle hook here means
|
|
136
|
+
* the display-only component elision analyser must learn about it too,
|
|
137
|
+
* or it will wrongly elide a component that now does client work. Add
|
|
138
|
+
* the hook name to `CLIENT_LIFECYCLE_HOOKS` in
|
|
139
|
+
* `packages/server/src/component-elision.js`. The guard test at
|
|
140
|
+
* `packages/server/test/elision/lifecycle-coverage.test.js` introspects
|
|
141
|
+
* this prototype and fails until you do.
|
|
142
|
+
*
|
|
143
|
+
* Usage:
|
|
144
|
+
* ```js
|
|
145
|
+
* import { signal } from '@webjsdev/core';
|
|
146
|
+
*
|
|
147
|
+
* const count = signal(0);
|
|
148
|
+
*
|
|
149
|
+
* class MyCounter extends WebComponent {
|
|
150
|
+
* render() {
|
|
151
|
+
* return html`<button @click=${() => count.set(count.get() + 1)}>${count.get()}</button>`;
|
|
152
|
+
* }
|
|
153
|
+
* }
|
|
154
|
+
* MyCounter.register('my-counter');
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Helper to define properties with custom options.
|
|
160
|
+
*
|
|
161
|
+
* @param {any} [type]
|
|
162
|
+
* @param {any} [opts]
|
|
163
|
+
* @returns {any}
|
|
164
|
+
*/
|
|
165
|
+
export function prop(type, opts = {}) {
|
|
166
|
+
if (type && typeof type === 'object' && !('call' in type)) {
|
|
167
|
+
opts = type;
|
|
168
|
+
type = undefined;
|
|
169
|
+
}
|
|
170
|
+
return { ...(type ? { type } : {}), ...opts };
|
|
171
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inert `ElementInternals`-shaped object returned by the server shim's
|
|
3
|
+
* `attachInternals()`. Modeled on `@lit-labs/ssr-dom-shim`'s
|
|
4
|
+
* `ElementInternalsShim` (lit repo, `packages/labs/ssr-dom-shim/src/lib/
|
|
5
|
+
* element-internals.ts`): form-association, validity, and custom-state
|
|
6
|
+
* calls are no-ops at SSR (no form, no constraint validation, no `:state()`
|
|
7
|
+
* matching server-side), so a component that calls `this.attachInternals()`
|
|
8
|
+
* in its constructor renders instead of crashing. The browser runs the real
|
|
9
|
+
* `attachInternals()` on hydration.
|
|
10
|
+
*
|
|
11
|
+
* Deliberate deviation from lit: lit's `checkValidity` / `reportValidity`
|
|
12
|
+
* THROW on the server. WebJs returns `true` instead, to keep SSR
|
|
13
|
+
* progressive-enhancement-safe (a stray validity call in a constructor must
|
|
14
|
+
* not 500 the page); the browser does the real validation.
|
|
15
|
+
* @returns {any}
|
|
16
|
+
*/
|
|
17
|
+
export function makeServerInternals() {
|
|
18
|
+
return {
|
|
19
|
+
states: new Set(),
|
|
20
|
+
shadowRoot: null,
|
|
21
|
+
form: null,
|
|
22
|
+
labels: [],
|
|
23
|
+
role: '',
|
|
24
|
+
willValidate: true,
|
|
25
|
+
validity: /** @type {any} */ ({}),
|
|
26
|
+
validationMessage: '',
|
|
27
|
+
setFormValue() {},
|
|
28
|
+
setValidity() {},
|
|
29
|
+
checkValidity() { return true; },
|
|
30
|
+
reportValidity() { return true; },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Server-side stand-in for `HTMLElement`. The SSR pipeline constructs
|
|
36
|
+
* component instances in Node, where `HTMLElement` does not exist, so the
|
|
37
|
+
* base class is this shim. It is modeled on `@lit-labs/ssr-dom-shim`'s
|
|
38
|
+
* `ElementShim` (lit repo, `packages/labs/ssr-dom-shim/src/index.ts`): the
|
|
39
|
+
* attribute methods (`getAttribute` / `setAttribute` / `hasAttribute` /
|
|
40
|
+
* `removeAttribute` / `toggleAttribute`, the `attributes` getter) are backed
|
|
41
|
+
* by a Map, so lit muscle-memory patterns that read attributes in `render()`
|
|
42
|
+
* or set them while deriving state work server-side; the SSR walker seeds
|
|
43
|
+
* the Map from the element's source attributes and reads it back to surface
|
|
44
|
+
* reflected/added attributes in the output. Event methods are no-ops (no
|
|
45
|
+
* server event loop), and `attachInternals()` returns the inert object
|
|
46
|
+
* above. The genuinely browser-only surface (`querySelector`, layout reads,
|
|
47
|
+
* `attachShadow`, `focus`) is deliberately absent and still throws at SSR,
|
|
48
|
+
* which the `no-browser-globals-in-render` rule and the SSR crash hint flag.
|
|
49
|
+
*
|
|
50
|
+
* Deliberate deviation from lit: this shim lowercases attribute names so
|
|
51
|
+
* `getAttribute('Foo')` after `setAttribute('foo', x)` resolves, matching how
|
|
52
|
+
* a real browser treats HTML attribute names as case-insensitive. lit's shim
|
|
53
|
+
* keys the Map by the raw name (a known fidelity gap in lit-labs).
|
|
54
|
+
*/
|
|
55
|
+
export class ServerElement {
|
|
56
|
+
constructor() {
|
|
57
|
+
/**
|
|
58
|
+
* Backing store for the attribute methods. Keys are lowercased
|
|
59
|
+
* attribute names (HTML attributes are case-insensitive). Seeded by the
|
|
60
|
+
* SSR walker from the element's source attributes.
|
|
61
|
+
* @type {Map<string, string>}
|
|
62
|
+
*/
|
|
63
|
+
this.__ssrAttrs = new Map();
|
|
64
|
+
/** @type {any} */
|
|
65
|
+
this.__internals = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Mirrors `Element.attributes`: an array of `{ name, value }`. */
|
|
69
|
+
get attributes() {
|
|
70
|
+
return [...this.__ssrAttrs].map(([name, value]) => ({ name, value }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @param {string} name */
|
|
74
|
+
getAttribute(name) {
|
|
75
|
+
const v = this.__ssrAttrs.get(String(name).toLowerCase());
|
|
76
|
+
return v === undefined ? null : v;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @param {string} name @param {unknown} value */
|
|
80
|
+
setAttribute(name, value) {
|
|
81
|
+
// Emulate the browser casting all values to string (lit does the same).
|
|
82
|
+
this.__ssrAttrs.set(String(name).toLowerCase(), String(value));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @param {string} name */
|
|
86
|
+
removeAttribute(name) {
|
|
87
|
+
this.__ssrAttrs.delete(String(name).toLowerCase());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @param {string} name */
|
|
91
|
+
hasAttribute(name) {
|
|
92
|
+
return this.__ssrAttrs.has(String(name).toLowerCase());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @param {string} name @param {boolean} [force] */
|
|
96
|
+
toggleAttribute(name, force) {
|
|
97
|
+
// Steps mirror https://dom.spec.whatwg.org/#dom-element-toggleattribute
|
|
98
|
+
const key = String(name).toLowerCase();
|
|
99
|
+
const present = this.__ssrAttrs.has(key);
|
|
100
|
+
const next = force === undefined ? !present : force;
|
|
101
|
+
if (next) {
|
|
102
|
+
this.__ssrAttrs.set(key, '');
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
this.__ssrAttrs.delete(key);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @returns {string[]} */
|
|
110
|
+
getAttributeNames() {
|
|
111
|
+
return [...this.__ssrAttrs.keys()];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Minimal `Element.closest()` for SSR. The SSR walker threads the chain
|
|
116
|
+
* of enclosing custom-element instances into each instance
|
|
117
|
+
* (`__ssrAncestors`); this walks self-then-ancestors and returns the
|
|
118
|
+
* nearest whose tag matches. Only bare tag-name selectors are supported
|
|
119
|
+
* server-side (`closest('ui-tabs')`), which is what compound components
|
|
120
|
+
* need to read parent state for a correct first paint; anything more
|
|
121
|
+
* specific (class, attribute, or descendant selectors) returns null,
|
|
122
|
+
* matching the pre-shim behaviour. The browser runs the real `closest()`
|
|
123
|
+
* on hydration.
|
|
124
|
+
* @param {string} selector
|
|
125
|
+
* @returns {any}
|
|
126
|
+
*/
|
|
127
|
+
closest(selector) {
|
|
128
|
+
const sel = String(selector).trim().toLowerCase();
|
|
129
|
+
// Tag-name selectors only at SSR; bail (null) on anything else.
|
|
130
|
+
if (!/^[a-z][a-z0-9-]*$/.test(sel)) return null;
|
|
131
|
+
if (this.__ssrTag === sel) return this;
|
|
132
|
+
const chain = this.__ssrAncestors;
|
|
133
|
+
if (!Array.isArray(chain)) return null;
|
|
134
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
135
|
+
if (chain[i] && chain[i].__ssrTag === sel) return chain[i];
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `HTMLElement.dataset`: a live view over the element's `data-*`
|
|
142
|
+
* attributes, backed by the SSR attribute Map. Reading / writing
|
|
143
|
+
* `el.dataset.fooBar` maps to the `data-foo-bar` attribute (camelCase
|
|
144
|
+
* to kebab-case), so a `render()` that sets `this.dataset.state = 'on'`
|
|
145
|
+
* surfaces `data-state="on"` in the SSR'd host tag instead of crashing
|
|
146
|
+
* on an undefined `dataset`.
|
|
147
|
+
* @returns {Record<string, string>}
|
|
148
|
+
*/
|
|
149
|
+
get dataset() {
|
|
150
|
+
if (this.__dataset) return this.__dataset;
|
|
151
|
+
const el = this;
|
|
152
|
+
const toAttr = (p) => 'data-' + String(p).replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
|
153
|
+
this.__dataset = new Proxy(/** @type {Record<string,string>} */ ({}), {
|
|
154
|
+
get(_t, prop) {
|
|
155
|
+
if (typeof prop !== 'string') return undefined;
|
|
156
|
+
const v = el.getAttribute(toAttr(prop));
|
|
157
|
+
return v === null ? undefined : v;
|
|
158
|
+
},
|
|
159
|
+
set(_t, prop, value) {
|
|
160
|
+
if (typeof prop === 'string') el.setAttribute(toAttr(prop), value);
|
|
161
|
+
return true;
|
|
162
|
+
},
|
|
163
|
+
has(_t, prop) {
|
|
164
|
+
return typeof prop === 'string' && el.hasAttribute(toAttr(prop));
|
|
165
|
+
},
|
|
166
|
+
deleteProperty(_t, prop) {
|
|
167
|
+
if (typeof prop === 'string') el.removeAttribute(toAttr(prop));
|
|
168
|
+
return true;
|
|
169
|
+
},
|
|
170
|
+
ownKeys() {
|
|
171
|
+
return el.getAttributeNames()
|
|
172
|
+
.filter((n) => n.startsWith('data-'))
|
|
173
|
+
.map((n) => n.slice(5).replace(/-([a-z])/g, (_m, c) => c.toUpperCase()));
|
|
174
|
+
},
|
|
175
|
+
getOwnPropertyDescriptor(_t, prop) {
|
|
176
|
+
if (typeof prop === 'string' && el.hasAttribute(toAttr(prop))) {
|
|
177
|
+
return { enumerable: true, configurable: true, value: el.getAttribute(toAttr(prop)) };
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
return this.__dataset;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// IDL properties that reflect to a content attribute. A render() that
|
|
186
|
+
// mutates these on the host (a light-DOM compound-component pattern, e.g.
|
|
187
|
+
// `this.className = ...`, `this.hidden = !active`) then surfaces the
|
|
188
|
+
// matching attribute in the SSR'd host tag, matching the browser.
|
|
189
|
+
get className() { return this.getAttribute('class') ?? ''; }
|
|
190
|
+
set className(v) { this.setAttribute('class', v); }
|
|
191
|
+
get hidden() { return this.hasAttribute('hidden'); }
|
|
192
|
+
set hidden(v) { this.toggleAttribute('hidden', !!v); }
|
|
193
|
+
get id() { return this.getAttribute('id') ?? ''; }
|
|
194
|
+
set id(v) { this.setAttribute('id', v); }
|
|
195
|
+
get title() { return this.getAttribute('title') ?? ''; }
|
|
196
|
+
set title(v) { this.setAttribute('title', v); }
|
|
197
|
+
get slot() { return this.getAttribute('slot') ?? ''; }
|
|
198
|
+
set slot(v) { this.setAttribute('slot', v); }
|
|
199
|
+
get role() { return this.getAttribute('role'); }
|
|
200
|
+
set role(v) { v == null ? this.removeAttribute('role') : this.setAttribute('role', v); }
|
|
201
|
+
get tabIndex() { const v = this.getAttribute('tabindex'); return v === null ? -1 : (Number.parseInt(v, 10) || 0); }
|
|
202
|
+
set tabIndex(v) { this.setAttribute('tabindex', String(v)); }
|
|
203
|
+
|
|
204
|
+
// No server event loop: listeners never fire at SSR. The no-op keeps a
|
|
205
|
+
// constructor that wires delegated listeners (a common lit pattern) from
|
|
206
|
+
// crashing; the browser re-runs the constructor on hydration where the
|
|
207
|
+
// real HTMLElement methods apply.
|
|
208
|
+
addEventListener() {}
|
|
209
|
+
removeEventListener() {}
|
|
210
|
+
dispatchEvent() { return true; }
|
|
211
|
+
|
|
212
|
+
/** @returns {any} */
|
|
213
|
+
attachInternals() {
|
|
214
|
+
// Match the browser (and lit's shim): a second attach is an error.
|
|
215
|
+
if (this.__internals !== null) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
"Failed to execute 'attachInternals' on 'HTMLElement': " +
|
|
218
|
+
'ElementInternals for the specified element was already attached.',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
this.__internals = makeServerInternals();
|
|
222
|
+
return this.__internals;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ARIAMixin IDL reflections (`el.ariaPressed = 'true'` writes aria-pressed).
|
|
227
|
+
// A render() that sets ARIA state via the IDL properties then surfaces the
|
|
228
|
+
// matching aria-* attribute in the SSR'd host tag, matching the browser. The
|
|
229
|
+
// IDL name maps to the content attribute by lowercasing the part after
|
|
230
|
+
// `aria` and prefixing `aria-` (ariaPressed -> aria-pressed).
|
|
231
|
+
const ARIA_IDL_PROPS = [
|
|
232
|
+
'ariaAtomic', 'ariaAutoComplete', 'ariaBusy', 'ariaChecked', 'ariaColCount',
|
|
233
|
+
'ariaColIndex', 'ariaColSpan', 'ariaCurrent', 'ariaDescription', 'ariaDisabled',
|
|
234
|
+
'ariaExpanded', 'ariaHasPopup', 'ariaHidden', 'ariaInvalid', 'ariaKeyShortcuts',
|
|
235
|
+
'ariaLabel', 'ariaLevel', 'ariaLive', 'ariaModal', 'ariaMultiLine',
|
|
236
|
+
'ariaMultiSelectable', 'ariaOrientation', 'ariaPlaceholder', 'ariaPosInSet',
|
|
237
|
+
'ariaPressed', 'ariaReadOnly', 'ariaRequired', 'ariaRoleDescription',
|
|
238
|
+
'ariaRowCount', 'ariaRowIndex', 'ariaRowSpan', 'ariaSelected', 'ariaSetSize',
|
|
239
|
+
'ariaSort', 'ariaValueMax', 'ariaValueMin', 'ariaValueNow', 'ariaValueText',
|
|
240
|
+
];
|
|
241
|
+
for (const idl of ARIA_IDL_PROPS) {
|
|
242
|
+
const attr = 'aria-' + idl.slice(4).toLowerCase();
|
|
243
|
+
Object.defineProperty(ServerElement.prototype, idl, {
|
|
244
|
+
configurable: true,
|
|
245
|
+
get() { return this.getAttribute(attr); },
|
|
246
|
+
set(v) { v == null ? this.removeAttribute(attr) : this.setAttribute(attr, String(v)); },
|
|
247
|
+
});
|
|
248
|
+
}
|
package/src/component.d.ts
CHANGED
|
@@ -213,3 +213,13 @@ export declare function prop<T>(
|
|
|
213
213
|
export declare function prop<T = string>(
|
|
214
214
|
opts?: PropertyDeclaration<T>
|
|
215
215
|
): PropertyDeclaration<T>;
|
|
216
|
+
|
|
217
|
+
// The shared attribute reader (`resolveAttributeProperty`, #1341) is NOT here,
|
|
218
|
+
// and nothing about it belongs here. It lives in `src/attribute-reader.js`,
|
|
219
|
+
// which has no `exports` entry, because two guards bracket every mapped entry
|
|
220
|
+
// and `./component` is one: #388 forces every runtime named export of
|
|
221
|
+
// `component.js` to be declared in this file, and #1031 forces every
|
|
222
|
+
// declaration reachable from `index.d.ts` (which re-exports this file with
|
|
223
|
+
// `export *`) to exist on `index.js` at runtime. Together they leave no way to
|
|
224
|
+
// add an INTERNAL named export to `component.js`. That module's header carries
|
|
225
|
+
// the full reasoning.
|