@webjsdev/core 0.7.64 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/webjs-core-browser.js +5 -5
- package/dist/webjs-core-browser.js.map +3 -3
- package/dist/webjs-core-testing.js +21 -22
- package/dist/webjs-core-testing.js.map +3 -3
- package/dist/webjs-core.js +6 -7
- package/dist/webjs-core.js.map +3 -3
- package/index-browser.js +6 -2
- package/index.d.ts +7 -3
- package/index.js +6 -2
- package/package.json +1 -1
- package/src/attribute-directives.d.ts +6 -0
- package/src/attribute-directives.js +49 -0
- package/src/attribute-reader.js +37 -3
- package/src/binding-prefixes.js +10 -41
- package/src/component/lifecycle.js +261 -101
- package/src/component/reactive.js +5 -4
- package/src/component.d.ts +11 -5
- package/src/directives.d.ts +2 -0
- package/src/directives.js +2 -0
- package/src/escape.js +12 -2
- package/src/form-action.js +7 -7
- package/src/html.d.ts +4 -0
- package/src/html.js +22 -1
- package/src/optimistic.js +3 -3
- package/src/registry.d.ts +5 -0
- package/src/registry.js +35 -4
- package/src/render-client/adopt-slots.js +245 -0
- package/src/render-client/parts.js +53 -19
- package/src/render-client/reconciler.js +28 -12
- package/src/render-client/template-compiler.js +19 -5
- package/src/render-client.d.ts +2 -1
- package/src/render-server/attrs.js +5 -4
- package/src/render-server/compile.js +433 -0
- package/src/render-server/dsd.js +11 -435
- package/src/render-server/errors.js +84 -0
- package/src/render-server/expand.js +346 -0
- package/src/render-server/html-scan.js +19 -358
- package/src/render-server/served-attrs.js +79 -0
- package/src/render-server/stream.js +11 -13
- package/src/render-server/template-renderer.js +13 -936
- package/src/render-server/walk.js +798 -0
- package/src/router-client/anchors.js +3 -1
- package/src/router-client/boundaries.js +15 -1
- package/src/router-client/constants.js +30 -0
- package/src/router-client/fetch-apply.js +21 -0
- package/src/router-client/frames.js +6 -3
- package/src/router-client/head-merge.js +58 -10
- package/src/router-client/navigator.js +4 -0
- package/src/router-client/owned-styles.js +372 -0
- package/src/router-client/prefetch.js +3 -0
- package/src/router-client/snapshot-cache.js +2 -12
- package/src/router-client/stylesheets.js +191 -0
- package/src/router-client/swap.js +49 -9
- package/src/sentinels.d.ts +2 -0
- package/src/sentinels.js +4 -0
- package/src/slot.js +1 -1
- package/src/webjs-config.d.ts +1 -1
- package/src/render-server/slots.js +0 -213
package/index-browser.js
CHANGED
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
* Keep this list in sync with `./index.js` for everything else.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
export { html, isTemplate, MARKER } from './src/html.js';
|
|
26
|
+
export { html, svg, isTemplate, MARKER } from './src/html.js';
|
|
27
27
|
export { css, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
28
28
|
export { WebComponent, prop } from './src/component.js';
|
|
29
|
-
export {
|
|
29
|
+
export { lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
30
30
|
export { render } from './src/render-client.js';
|
|
31
31
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
32
32
|
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
@@ -100,3 +100,7 @@ export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent }
|
|
|
100
100
|
|
|
101
101
|
// Task controller, also available via '@webjsdev/core/task'
|
|
102
102
|
export { Task, TaskStatus } from './src/task.js';
|
|
103
|
+
|
|
104
|
+
export { nothing, noChange } from './src/sentinels.js';
|
|
105
|
+
|
|
106
|
+
export { classMap, styleMap, ifDefined } from './src/attribute-directives.js';
|
package/index.d.ts
CHANGED
|
@@ -74,13 +74,13 @@ export type {
|
|
|
74
74
|
NonSerializable,
|
|
75
75
|
} from './src/serializable.d.ts';
|
|
76
76
|
|
|
77
|
-
export { html, isTemplate, MARKER } from './src/html.js';
|
|
77
|
+
export { html, svg, isTemplate, MARKER } from './src/html.js';
|
|
78
78
|
// The `html\`\`` return type. A JSDoc typedef in html.js used across the
|
|
79
79
|
// component / route / testing type surfaces; re-exported so an app or the
|
|
80
80
|
// docs/website can annotate a value as `TemplateResult` (#772).
|
|
81
81
|
export type { TemplateResult } from './src/html.js';
|
|
82
82
|
export { css, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
83
|
-
export {
|
|
83
|
+
export { lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
84
84
|
export { renderToString, renderToStream } from './src/render-server.js';
|
|
85
85
|
export { render } from './src/render-client.js';
|
|
86
86
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
@@ -152,10 +152,14 @@ export function optimistic<State>(
|
|
|
152
152
|
): OptimisticState<State, State>;
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
//
|
|
155
|
+
// Imperative signature (signal-based rollback)
|
|
156
156
|
export function optimistic<T, R>(
|
|
157
157
|
signal: { get(): T; set(v: T): void },
|
|
158
158
|
value: T,
|
|
159
159
|
action: () => Promise<R> | R,
|
|
160
160
|
): Promise<R>;
|
|
161
161
|
|
|
162
|
+
|
|
163
|
+
export { nothing, noChange } from './src/sentinels.js';
|
|
164
|
+
|
|
165
|
+
export { classMap, styleMap, ifDefined } from './src/attribute-directives.js';
|
package/index.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* renderer is only reached on the server.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export { html, isTemplate, MARKER } from './src/html.js';
|
|
9
|
+
export { html, svg, isTemplate, MARKER } from './src/html.js';
|
|
10
10
|
export { css, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
11
11
|
export { WebComponent, prop } from './src/component.js';
|
|
12
|
-
export {
|
|
12
|
+
export { lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
13
13
|
export { renderToString, renderToStream } from './src/render-server.js';
|
|
14
14
|
export { render } from './src/render-client.js';
|
|
15
15
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
@@ -68,3 +68,7 @@ export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent }
|
|
|
68
68
|
|
|
69
69
|
// Task controller, also available via '@webjsdev/core/task'
|
|
70
70
|
export { Task, TaskStatus } from './src/task.js';
|
|
71
|
+
|
|
72
|
+
export { nothing, noChange } from './src/sentinels.js';
|
|
73
|
+
|
|
74
|
+
export { classMap, styleMap, ifDefined } from './src/attribute-directives.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The runtime for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Ships the html and css template tags, the WebComponent base class, signals, directives, and the isomorphic renderers.",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { nothing } from './sentinels.js';
|
|
2
|
+
export interface ClassMapDirective { readonly _$webjs: 'class-map'; readonly value: Record<string, unknown>; }
|
|
3
|
+
export interface StyleMapDirective { readonly _$webjs: 'style-map'; readonly value: Record<string, string | number | null | undefined>; }
|
|
4
|
+
export function classMap(classes: Record<string, unknown>): ClassMapDirective;
|
|
5
|
+
export function styleMap(styles: Record<string, string | number | null | undefined>): StyleMapDirective;
|
|
6
|
+
export function ifDefined<T>(value: T): NonNullable<T> | typeof nothing;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { nothing } from './sentinels.js';
|
|
2
|
+
|
|
3
|
+
/** Map class names to whether each should be present. @param {Record<string, unknown>} classes */
|
|
4
|
+
export function classMap(classes) { return { _$webjs: 'class-map', value: classes }; }
|
|
5
|
+
/** Map CSS property names to their values. @param {Record<string, string | number | null | undefined>} styles */
|
|
6
|
+
export function styleMap(styles) { return { _$webjs: 'style-map', value: styles }; }
|
|
7
|
+
/** Remove an attribute when its value is nullish. @template T @param {T} value */
|
|
8
|
+
export function ifDefined(value) { return value ?? nothing; }
|
|
9
|
+
|
|
10
|
+
export function isAttributeMap(value) {
|
|
11
|
+
return value && (value._$webjs === 'class-map' || value._$webjs === 'style-map');
|
|
12
|
+
}
|
|
13
|
+
export function cssName(name) { return name.startsWith('--') ? name : name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()); }
|
|
14
|
+
export function attributeMapText(value, name) {
|
|
15
|
+
const classes = value._$webjs === 'class-map';
|
|
16
|
+
if (name !== (classes ? 'class' : 'style')) throw new Error('classMap/styleMap must be used in a class/style attribute.');
|
|
17
|
+
// Lit deliberately pads even an empty map to separate adjacent static classes.
|
|
18
|
+
return classes
|
|
19
|
+
? ' ' + Object.keys(value.value).filter((key) => value.value[key]).join(' ') + ' '
|
|
20
|
+
: Object.entries(value.value).filter(([, v]) => v != null).map(([key, v]) => `${cssName(key)}:${v};`).join('');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Commit only the map-owned tokens, preserving static and externally added values. */
|
|
24
|
+
export function applyAttributeMap(part, value) {
|
|
25
|
+
if (part.kind !== 'attr' && part.kind !== 'attr-mixed') throw new Error('classMap/styleMap require an attribute binding.');
|
|
26
|
+
const classes = value._$webjs === 'class-map';
|
|
27
|
+
if (part.name !== (classes ? 'class' : 'style')) throw new Error('classMap/styleMap must be used in a class/style attribute.');
|
|
28
|
+
if (part.group && part.group.length !== 1) throw new Error('classMap/styleMap require a single expression.');
|
|
29
|
+
if (part.__mapKind !== value._$webjs) {
|
|
30
|
+
part.el.setAttribute(part.name, (part.statics || []).join(classes ? ' ' : ''));
|
|
31
|
+
part.__mapKind = value._$webjs;
|
|
32
|
+
part.__mapKeys = new Set();
|
|
33
|
+
part.__staticClasses = classes ? new Set(part.el.classList) : new Set();
|
|
34
|
+
}
|
|
35
|
+
const next = new Set();
|
|
36
|
+
for (const [key, v] of Object.entries(value.value)) {
|
|
37
|
+
if (classes) {
|
|
38
|
+
if (v && !part.__staticClasses.has(key)) { part.el.classList.add(key); next.add(key); }
|
|
39
|
+
} else if (v != null) {
|
|
40
|
+
const name = cssName(key); const text = String(v);
|
|
41
|
+
part.el.style.setProperty(name, text.replace(/\s*!important\s*$/, ''), /!important\s*$/.test(text) ? 'important' : '');
|
|
42
|
+
next.add(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const key of part.__mapKeys) if (!next.has(key)) {
|
|
46
|
+
if (classes) part.el.classList.remove(key); else part.el.style.removeProperty(key);
|
|
47
|
+
}
|
|
48
|
+
part.__mapKeys = next;
|
|
49
|
+
}
|
package/src/attribute-reader.js
CHANGED
|
@@ -39,6 +39,20 @@
|
|
|
39
39
|
* JS under a hand-written overlay, so the shared code moves out instead.
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Whether a declared property has an HTML attribute at all. lit's two ways to
|
|
44
|
+
* say no, both honoured: `state: true` (internal state, which lit normalizes
|
|
45
|
+
* to `attribute: false`) and `attribute: false` (a public, property-only
|
|
46
|
+
* input). A property without one is never observed, never read from a source
|
|
47
|
+
* attribute at SSR, and never reflected; a `.prop` binding still reaches it.
|
|
48
|
+
*
|
|
49
|
+
* @param {{ state?: boolean, attribute?: string | false } | null | undefined} decl
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export function isAttributeBacked(decl) {
|
|
53
|
+
return !!decl && decl.state !== true && decl.attribute !== false;
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
/**
|
|
43
57
|
* Read one attribute string into its declared property value.
|
|
44
58
|
*
|
|
@@ -110,7 +124,7 @@ export function readAttributeValue(def, value) {
|
|
|
110
124
|
// The two readers now see the SAME attribute set, so this fallback is not
|
|
111
125
|
// the only thing they agree on (#1341). Both resolve a name through
|
|
112
126
|
// `resolveAttributeProperty` below, which matches the same names
|
|
113
|
-
// `observedAttributes` lists (via `observedAttributeNames`) and nothing else, so it skips a `state: true` prop and ignores a name that
|
|
127
|
+
// `observedAttributes` lists (via `observedAttributeNames`) and nothing else, so it skips a `state: true` or `attribute: false` prop and ignores a name that
|
|
114
128
|
// maps to no attribute-backed property exactly as the observed list does.
|
|
115
129
|
// The SSR caller lowercases its source name first, because the browser
|
|
116
130
|
// lowercases while parsing. And both are handed a value with every
|
|
@@ -130,7 +144,7 @@ function hyphenate(s) {
|
|
|
130
144
|
* Resolve an attribute NAME to the property it feeds, and to that property's
|
|
131
145
|
* normalised declaration.
|
|
132
146
|
*
|
|
133
|
-
* `state: true` props are skipped, because they are absent from
|
|
147
|
+
* `state: true` and `attribute: false` props are skipped, because they are absent from
|
|
134
148
|
* `observedAttributes` and so the browser never delivers their attribute to any
|
|
135
149
|
* reader. lit reaches the same outcome the same way, resolving through the very
|
|
136
150
|
* map `observedAttributes` is built from (`reactive-element.ts`
|
|
@@ -212,11 +226,31 @@ export function observedAttributeNames(Cls) {
|
|
|
212
226
|
* @returns {{ propName: string, def: PropertyDeclaration, primary: string, alias: string | undefined }[]}
|
|
213
227
|
*/
|
|
214
228
|
function attributeBackedProps(Cls) {
|
|
229
|
+
// Memoized per class (#1646): the SSR walk resolves every source attribute
|
|
230
|
+
// of every instance through this list, and rebuilding it (one hyphenate per
|
|
231
|
+
// declared prop) was a measurable share of a component's render. A class's
|
|
232
|
+
// declarations never change after it is defined (a `static properties`
|
|
233
|
+
// block throws at runtime, and the factory fixes the set), so the first
|
|
234
|
+
// walk is the only one.
|
|
235
|
+
if (Cls && typeof Cls === 'function') {
|
|
236
|
+
const hit = BACKED_PROPS.get(Cls);
|
|
237
|
+
if (hit) return hit;
|
|
238
|
+
const built = buildAttributeBackedProps(Cls);
|
|
239
|
+
BACKED_PROPS.set(Cls, built);
|
|
240
|
+
return built;
|
|
241
|
+
}
|
|
242
|
+
return buildAttributeBackedProps(Cls);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** @type {WeakMap<Function, { propName: string, def: any, primary: string, alias: string | undefined }[]>} */
|
|
246
|
+
const BACKED_PROPS = new WeakMap();
|
|
247
|
+
|
|
248
|
+
function buildAttributeBackedProps(Cls) {
|
|
215
249
|
const props = (Cls && Cls.properties) || {};
|
|
216
250
|
const out = [];
|
|
217
251
|
for (const [k, decl] of Object.entries(props)) {
|
|
218
252
|
const d = typeof decl === 'object' && decl !== null ? decl : { type: decl };
|
|
219
|
-
if (d
|
|
253
|
+
if (!isAttributeBacked(d)) continue;
|
|
220
254
|
const primary = d.attribute || hyphenate(k);
|
|
221
255
|
const lower = k.toLowerCase();
|
|
222
256
|
const alias = !d.attribute && lower !== primary ? lower : undefined;
|
package/src/binding-prefixes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nothing, noChange } from './sentinels.js';
|
|
1
2
|
/**
|
|
2
3
|
* The template attribute-binding prefixes the renderers recognise.
|
|
3
4
|
*
|
|
@@ -42,48 +43,16 @@ export function isBindingPrefix(ch) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* attribute index.
|
|
50
|
-
*/
|
|
51
|
-
const BOOLEAN_ATTRIBUTES = new Set([
|
|
52
|
-
'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',
|
|
53
|
-
'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'inert',
|
|
54
|
-
'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate',
|
|
55
|
-
'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected',
|
|
56
|
-
'shadowrootclonable', 'shadowrootdelegatesfocus', 'shadowrootserializable',
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* What a PLAIN attribute hole (`name=${value}`, unquoted, the whole value)
|
|
61
|
-
* writes, or `null` when the attribute is omitted. The one rule both renderers
|
|
62
|
-
* follow, so the server never serves an attribute the client then removes
|
|
63
|
-
* (#1573, #1579):
|
|
64
|
-
*
|
|
65
|
-
* - `null` / `undefined` omit the attribute. This is the "omit" value, so a
|
|
66
|
-
* conditional attribute is `aria-current=${active ? 'page' : null}`.
|
|
67
|
-
* - `false` omits it too, EXCEPT on an `aria-*` attribute, where it writes
|
|
68
|
-
* `"false"`. For a tri-state ARIA attribute (`aria-expanded`,
|
|
69
|
-
* `aria-pressed`, `aria-checked`) `"false"` and absent mean different
|
|
70
|
-
* things, so the value an author passes is the value the user agent gets.
|
|
71
|
-
* - `true` on an HTML boolean attribute (`checked`, `selected`, `disabled`,
|
|
72
|
-
* and the rest) writes the empty value, exactly as `?attr=${true}` does. Anywhere
|
|
73
|
-
* else it is `"true"`.
|
|
74
|
-
* - anything else is `String(value)`.
|
|
75
|
-
*
|
|
76
|
-
* A quoted or mixed value (`title="${x}"`, `class="a ${x}"`) is not a plain
|
|
77
|
-
* hole: the author wrote the attribute statically, so it stays and a nullish
|
|
78
|
-
* piece reads as empty text.
|
|
46
|
+
* Serialize an ordinary whole-value attribute expression for SSR and client
|
|
47
|
+
* commits. Nullish values become empty strings and booleans stringify.
|
|
48
|
+
* `nothing` omits the attribute. `noChange` omits it on initial SSR; client
|
|
49
|
+
* updates handle that sentinel before serialization to retain the prior value.
|
|
79
50
|
*
|
|
80
|
-
* @param {string} name the
|
|
81
|
-
* @param {unknown} value the
|
|
51
|
+
* @param {string} _name attribute name (kept for the shared call signature)
|
|
52
|
+
* @param {unknown} value the expression value (`live()` already unwrapped)
|
|
82
53
|
* @returns {string | null}
|
|
83
54
|
*/
|
|
84
|
-
export function attrHoleValue(
|
|
85
|
-
if (value
|
|
86
|
-
|
|
87
|
-
if (value === true && BOOLEAN_ATTRIBUTES.has(String(name).toLowerCase())) return '';
|
|
88
|
-
return String(value);
|
|
55
|
+
export function attrHoleValue(_name, value) {
|
|
56
|
+
if (value === nothing || value === noChange) return null;
|
|
57
|
+
return String(value ?? '');
|
|
89
58
|
}
|