@webjsdev/core 0.7.21 → 0.7.23
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/README.md +1 -3
- package/dist/webjs-core-browser.js +3 -3
- package/dist/webjs-core-browser.js.map +2 -2
- package/dist/webjs-core-testing.js +10 -10
- package/dist/webjs-core-testing.js.map +2 -2
- package/dist/webjs-core.js +4 -4
- package/dist/webjs-core.js.map +2 -2
- package/index-browser.js +10 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/component.d.ts +72 -13
- package/src/component.js +92 -3
- package/src/render-server.js +6 -2
- package/src/router-client.js +216 -49
- package/src/task.js +1 -3
- package/src/webjs-config.d.ts +45 -1
package/index-browser.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
export { html, isTemplate, MARKER } from './src/html.js';
|
|
26
26
|
export { css, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
27
|
-
export { WebComponent } from './src/component.js';
|
|
27
|
+
export { WebComponent, prop } from './src/component.js';
|
|
28
28
|
export { register, lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
29
29
|
export { render } from './src/render-client.js';
|
|
30
30
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
@@ -38,6 +38,15 @@ export {
|
|
|
38
38
|
stringify, parse,
|
|
39
39
|
serialize, deserialize,
|
|
40
40
|
} from './src/serialize.js';
|
|
41
|
+
// The client router auto-enables on bundle load: `router-client.js` calls
|
|
42
|
+
// `enableClientRouter()` at module end (idempotent). This is INTENTIONAL, so
|
|
43
|
+
// any page that loads `@webjsdev/core` (i.e. ships any component) gets client
|
|
44
|
+
// navigation with no per-app opt-in. A layout no longer needs to
|
|
45
|
+
// `import '@webjsdev/core/client-router'`. The bare side-effect import below
|
|
46
|
+
// pins that contract even if the named re-export is later refactored away;
|
|
47
|
+
// the re-export still also evaluates the module, so the two are belt-and-
|
|
48
|
+
// suspenders, not a double-enable (ESM evaluates once, enable is idempotent).
|
|
49
|
+
import './src/router-client.js';
|
|
41
50
|
export { enableClientRouter, disableClientRouter, navigate, revalidate } from './src/router-client.js';
|
|
42
51
|
export { WebjsFrame } from './src/webjs-frame.js';
|
|
43
52
|
export { WebjsStream, renderStream } from './src/webjs-stream.js';
|
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
export { html, isTemplate, MARKER } from './src/html.js';
|
|
10
10
|
export { css, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
11
|
-
export { WebComponent } from './src/component.js';
|
|
11
|
+
export { WebComponent, prop } from './src/component.js';
|
|
12
12
|
export { register, 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';
|
package/package.json
CHANGED
package/src/component.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type PropertyConstructor<T = unknown> =
|
|
|
22
22
|
| (new (...args: any[]) => T)
|
|
23
23
|
| ((v: any) => T);
|
|
24
24
|
|
|
25
|
-
/** Runtime-level property declaration:
|
|
25
|
+
/** Runtime-level property declaration: one entry in `WebComponent({ … })`. */
|
|
26
26
|
export interface PropertyDeclaration<T = unknown> {
|
|
27
27
|
/** Constructor used for string → value coercion when the attribute changes. */
|
|
28
28
|
type?: PropertyConstructor<T>;
|
|
@@ -52,27 +52,30 @@ export interface ReactiveController {
|
|
|
52
52
|
/**
|
|
53
53
|
* Base class for interactive web components.
|
|
54
54
|
*
|
|
55
|
-
* Declare
|
|
56
|
-
*
|
|
55
|
+
* Declare reactive properties via the `extends WebComponent({ … })`
|
|
56
|
+
* factory. It installs the reactive accessor AND types each field for
|
|
57
|
+
* you (no `declare`, no `static properties`):
|
|
57
58
|
*
|
|
58
|
-
* class StudentCard extends WebComponent {
|
|
59
|
-
* static properties = { student: { type: Object } };
|
|
60
|
-
* declare student: Student;
|
|
59
|
+
* class StudentCard extends WebComponent({ student: prop<Student>(Object) }) {
|
|
61
60
|
* render() { return html`<p>${this.student.name}</p>`; }
|
|
62
61
|
* }
|
|
63
62
|
* StudentCard.register('student-card');
|
|
64
63
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* `Object.
|
|
64
|
+
* A bare constructor type covers the common case (`WebComponent({ count:
|
|
65
|
+
* Number })` gives `this.count: number`); use the `prop()` helper for
|
|
66
|
+
* options (`prop(Number, { reflect: true })`) or to narrow the type
|
|
67
|
+
* (`prop<Student>(Object)`). Set defaults via the `default` option or in
|
|
68
|
+
* the constructor, never a class-field initializer (it runs after super()
|
|
69
|
+
* and clobbers the reactive accessor). A hand-written `static properties`
|
|
70
|
+
* in a class body is no longer supported and throws at construction.
|
|
69
71
|
*
|
|
70
72
|
* See the Editor Setup doc for tooling that gives attribute/tag
|
|
71
73
|
* intelligence inside `html\`…\`` templates.
|
|
72
74
|
*/
|
|
73
|
-
|
|
75
|
+
abstract class WebComponentBase extends HTMLElement {
|
|
74
76
|
static shadow: boolean;
|
|
75
77
|
static hydrate: 'visible' | undefined;
|
|
78
|
+
/** @internal Populated by the `WebComponent({ … })` factory, not by hand. */
|
|
76
79
|
static properties: Record<string, PropertyDeclaration>;
|
|
77
80
|
static styles: CSSResult | CSSResult[] | null;
|
|
78
81
|
static lazy?: boolean;
|
|
@@ -84,8 +87,8 @@ export abstract class WebComponent extends HTMLElement {
|
|
|
84
87
|
* Schedule a re-render. Optionally record a property change so lifecycle
|
|
85
88
|
* hooks can branch on what changed via `changedProperties`.
|
|
86
89
|
*
|
|
87
|
-
* For instance-local state, prefer a reactive property (`
|
|
88
|
-
*
|
|
90
|
+
* For instance-local state, prefer a reactive property (`WebComponent({
|
|
91
|
+
* foo: prop({ state: true }) })`) or a signal from
|
|
89
92
|
* `@webjsdev/core`'s `signal()`. The framework's built-in
|
|
90
93
|
* SignalWatcher wires every signal read in `render()` to a re-render
|
|
91
94
|
* automatically; explicit `requestUpdate()` is rarely needed.
|
|
@@ -129,3 +132,59 @@ export abstract class WebComponent extends HTMLElement {
|
|
|
129
132
|
disconnectedCallback(): void;
|
|
130
133
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
131
134
|
}
|
|
135
|
+
|
|
136
|
+
export type WebComponent = WebComponentBase;
|
|
137
|
+
|
|
138
|
+
export type Infer<C> =
|
|
139
|
+
C extends BooleanConstructor ? boolean :
|
|
140
|
+
C extends NumberConstructor ? number :
|
|
141
|
+
C extends StringConstructor ? string :
|
|
142
|
+
C extends new (...a: any[]) => infer T ? T :
|
|
143
|
+
C extends (v: any) => infer T ? T : unknown;
|
|
144
|
+
|
|
145
|
+
export type InferProps<S> = {
|
|
146
|
+
[K in keyof S]: S[K] extends PropertyDeclaration<infer T> ? T : Infer<S[K]>
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The static surface every `WebComponent` constructor carries (the bare base,
|
|
151
|
+
* a factory-produced base, and a registered subclass alike). Declared ONCE
|
|
152
|
+
* here and referenced from both `WebComponentConstructor` and the factory-call
|
|
153
|
+
* return type below, so adding or changing a static can never let the two
|
|
154
|
+
* positions drift. Mirrors the `static` members on `WebComponentBase`.
|
|
155
|
+
*/
|
|
156
|
+
interface WebComponentStatics {
|
|
157
|
+
shadow: boolean;
|
|
158
|
+
hydrate: 'visible' | undefined;
|
|
159
|
+
properties: Record<string, PropertyDeclaration>;
|
|
160
|
+
styles: CSSResult | CSSResult[] | null;
|
|
161
|
+
lazy?: boolean;
|
|
162
|
+
register(tag: string): void;
|
|
163
|
+
readonly observedAttributes: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface WebComponentConstructor extends WebComponentStatics {
|
|
167
|
+
new (): WebComponentBase;
|
|
168
|
+
prototype: WebComponentBase;
|
|
169
|
+
|
|
170
|
+
<S extends Record<string, any>>(shape: S): WebComponentStatics & {
|
|
171
|
+
new (): WebComponentBase & InferProps<S>;
|
|
172
|
+
prototype: WebComponentBase & InferProps<S>;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export declare const WebComponent: WebComponentConstructor;
|
|
177
|
+
|
|
178
|
+
export declare function prop<C extends PropertyConstructor<any>>(
|
|
179
|
+
type: C,
|
|
180
|
+
opts?: Omit<PropertyDeclaration<Infer<C>>, 'type'>
|
|
181
|
+
): PropertyDeclaration<Infer<C>>;
|
|
182
|
+
|
|
183
|
+
export declare function prop<T>(
|
|
184
|
+
type: PropertyConstructor<any>,
|
|
185
|
+
opts?: Omit<PropertyDeclaration<T>, 'type'>
|
|
186
|
+
): PropertyDeclaration<T>;
|
|
187
|
+
|
|
188
|
+
export declare function prop<T = string>(
|
|
189
|
+
opts?: PropertyDeclaration<T>
|
|
190
|
+
): PropertyDeclaration<T>;
|
package/src/component.js
CHANGED
|
@@ -400,7 +400,18 @@ for (const idl of ARIA_IDL_PROPS) {
|
|
|
400
400
|
// Base class choice: real HTMLElement on the browser, the shim on the server.
|
|
401
401
|
const Base = isBrowser ? HTMLElement : /** @type {any} */ (ServerElement);
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Marker stamped on the anonymous subclass the `WebComponent({...})` factory
|
|
405
|
+
* produces. It lets `_assertFactoryProperties` tell the framework's own
|
|
406
|
+
* factory-generated `static properties` (allowed) apart from a `static
|
|
407
|
+
* properties` a user wrote by hand in a class body (no longer allowed).
|
|
408
|
+
*/
|
|
409
|
+
const FACTORY_PROPS = Symbol('webjs.factoryProps');
|
|
410
|
+
|
|
411
|
+
// Per-class memo so the constructor-time enforcement walk runs once per class.
|
|
412
|
+
const _propsChecked = new WeakSet();
|
|
413
|
+
|
|
414
|
+
class WebComponentBase extends Base {
|
|
404
415
|
/** Whether to use shadow DOM. Default: false (light DOM). @type {boolean} */
|
|
405
416
|
static shadow = false;
|
|
406
417
|
|
|
@@ -531,10 +542,45 @@ export class WebComponent extends Base {
|
|
|
531
542
|
*/
|
|
532
543
|
this._isUpdating = false;
|
|
533
544
|
|
|
545
|
+
// Enforce the declare-free factory DX: a hand-written `static properties`
|
|
546
|
+
// in a class body is a hard error (use `extends WebComponent({ … })`).
|
|
547
|
+
this._assertFactoryProperties();
|
|
548
|
+
|
|
534
549
|
// Install reactive property accessors for `static properties` declarations.
|
|
535
550
|
this._initializeProperties();
|
|
536
551
|
}
|
|
537
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Throw if a class in this instance's constructor chain declares its own
|
|
555
|
+
* `static properties`. Reactive properties must be declared via the
|
|
556
|
+
* `extends WebComponent({ … })` factory, which stamps {@link FACTORY_PROPS}
|
|
557
|
+
* on the subclass it generates; a `static properties` written by hand in a
|
|
558
|
+
* class body carries no such marker and is rejected here (issue #598).
|
|
559
|
+
*
|
|
560
|
+
* The walk stops at {@link WebComponentBase} (whose `static properties = {}`
|
|
561
|
+
* default is internal) and is memoized per class so it runs once.
|
|
562
|
+
* @private
|
|
563
|
+
*/
|
|
564
|
+
_assertFactoryProperties() {
|
|
565
|
+
const Ctor = /** @type {any} */ (this.constructor);
|
|
566
|
+
if (_propsChecked.has(Ctor)) return;
|
|
567
|
+
let C = Ctor;
|
|
568
|
+
while (C && C !== WebComponentBase) {
|
|
569
|
+
if (Object.hasOwn(C, 'properties') && !Object.hasOwn(C, FACTORY_PROPS)) {
|
|
570
|
+
const name = C.name || 'a component';
|
|
571
|
+
throw new Error(
|
|
572
|
+
`${name}: \`static properties\` is no longer supported. Declare reactive ` +
|
|
573
|
+
`properties via the factory instead: \`class ${name} extends WebComponent({ ` +
|
|
574
|
+
`count: Number })\`. Use the \`prop()\` helper for options ` +
|
|
575
|
+
`(\`prop(Number, { reflect: true })\`) and set defaults via the \`default\` ` +
|
|
576
|
+
`option or in the constructor. See https://docs.webjs.com/docs/components.`,
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
C = Object.getPrototypeOf(C);
|
|
580
|
+
}
|
|
581
|
+
_propsChecked.add(Ctor);
|
|
582
|
+
}
|
|
583
|
+
|
|
538
584
|
/**
|
|
539
585
|
* For every key in `static properties`, create a getter/setter pair on
|
|
540
586
|
* the instance that coerces values, runs `hasChanged`, schedules updates,
|
|
@@ -893,8 +939,13 @@ export class WebComponent extends Base {
|
|
|
893
939
|
|
|
894
940
|
const Ctor = /** @type any */ (this.constructor);
|
|
895
941
|
const propName = camelCase(name);
|
|
896
|
-
const
|
|
897
|
-
if (!
|
|
942
|
+
const raw = Ctor.properties && (Ctor.properties[propName] || Ctor.properties[name]);
|
|
943
|
+
if (!raw) return;
|
|
944
|
+
// A declaration is either a full descriptor (`{ type: Number, … }`) or the
|
|
945
|
+
// bare-constructor shorthand the factory accepts (`count: Number`), in which
|
|
946
|
+
// case the value IS the type. Normalise so type-based coercion fires either
|
|
947
|
+
// way (matches `_initializeProperties`).
|
|
948
|
+
const def = typeof raw === 'object' ? raw : { type: raw };
|
|
898
949
|
|
|
899
950
|
let v;
|
|
900
951
|
if (def.converter && def.converter.fromAttribute) {
|
|
@@ -1553,3 +1604,41 @@ function hyphenate(s) {
|
|
|
1553
1604
|
function camelCase(s) {
|
|
1554
1605
|
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
1555
1606
|
}
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Dual-role WebComponent class and factory.
|
|
1610
|
+
* Can be extended directly: `class X extends WebComponent`
|
|
1611
|
+
* Or called as a class factory: `class X extends WebComponent({ count: Number })`
|
|
1612
|
+
*
|
|
1613
|
+
* @param {Record<string, any>} [properties]
|
|
1614
|
+
* @returns {any}
|
|
1615
|
+
*/
|
|
1616
|
+
export function WebComponent(properties) {
|
|
1617
|
+
if (new.target) {
|
|
1618
|
+
return Reflect.construct(WebComponentBase, arguments, new.target);
|
|
1619
|
+
} else {
|
|
1620
|
+
return class extends WebComponentBase {
|
|
1621
|
+
static properties = properties;
|
|
1622
|
+
static [FACTORY_PROPS] = true;
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// Ensure static inheritance and instanceof checks work
|
|
1628
|
+
Object.setPrototypeOf(WebComponent, WebComponentBase);
|
|
1629
|
+
WebComponent.prototype = WebComponentBase.prototype;
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Helper to define properties with custom options.
|
|
1633
|
+
*
|
|
1634
|
+
* @param {any} [type]
|
|
1635
|
+
* @param {any} [opts]
|
|
1636
|
+
* @returns {any}
|
|
1637
|
+
*/
|
|
1638
|
+
export function prop(type, opts = {}) {
|
|
1639
|
+
if (type && typeof type === 'object' && !('call' in type)) {
|
|
1640
|
+
opts = type;
|
|
1641
|
+
type = undefined;
|
|
1642
|
+
}
|
|
1643
|
+
return { ...(type ? { type } : {}), ...opts };
|
|
1644
|
+
}
|
package/src/render-server.js
CHANGED
|
@@ -1052,12 +1052,16 @@ function appendReflectedAttrs(opening, instance, presentAttrNames) {
|
|
|
1052
1052
|
function applyAttrsToInstance(instance, attrs, Cls) {
|
|
1053
1053
|
const props = Cls.properties || {};
|
|
1054
1054
|
for (const [key, raw] of Object.entries(attrs)) {
|
|
1055
|
-
const
|
|
1055
|
+
const rawDef = props[key] || props[camelCase(key)];
|
|
1056
1056
|
const propName = props[key] ? key : camelCase(key);
|
|
1057
|
-
if (!
|
|
1057
|
+
if (!rawDef) {
|
|
1058
1058
|
instance[propName] = raw;
|
|
1059
1059
|
continue;
|
|
1060
1060
|
}
|
|
1061
|
+
// The factory accepts a bare constructor shorthand (`{ expanded: Boolean }`)
|
|
1062
|
+
// alongside the long form (`{ expanded: { type: Boolean } }`); normalize so
|
|
1063
|
+
// the type-based coercion below sees a `{ type }` object either way.
|
|
1064
|
+
const def = typeof rawDef === 'object' ? rawDef : { type: rawDef };
|
|
1061
1065
|
if (def.type === Number) instance[propName] = Number(raw);
|
|
1062
1066
|
else if (def.type === Boolean) instance[propName] = raw !== 'false';
|
|
1063
1067
|
else if (def.type === Object || def.type === Array) {
|