@fluixi/web-components 0.0.0-stage → 1.0.0-alpha.100

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -2
  3. package/dist/context.cjs +162 -0
  4. package/dist/context.d.ts +65 -0
  5. package/dist/context.d.ts.map +1 -0
  6. package/dist/context.js +192 -0
  7. package/dist/context.mjs +141 -0
  8. package/dist/element.cjs +847 -0
  9. package/dist/element.d.ts +213 -0
  10. package/dist/element.d.ts.map +1 -0
  11. package/dist/element.js +681 -0
  12. package/dist/element.mjs +835 -0
  13. package/dist/events.cjs +35 -0
  14. package/dist/events.d.ts +24 -0
  15. package/dist/events.d.ts.map +1 -0
  16. package/dist/events.js +20 -0
  17. package/dist/events.mjs +14 -0
  18. package/dist/focus.cjs +156 -0
  19. package/dist/focus.d.ts +34 -0
  20. package/dist/focus.d.ts.map +1 -0
  21. package/dist/focus.js +185 -0
  22. package/dist/focus.mjs +135 -0
  23. package/dist/form.cjs +145 -0
  24. package/dist/form.d.ts +33 -0
  25. package/dist/form.d.ts.map +1 -0
  26. package/dist/form.js +113 -0
  27. package/dist/form.mjs +125 -0
  28. package/dist/index.cjs +1202 -0
  29. package/dist/index.d.ts +41 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +31 -0
  32. package/dist/index.mjs +1188 -0
  33. package/dist/props.cjs +114 -0
  34. package/dist/props.d.ts +93 -0
  35. package/dist/props.d.ts.map +1 -0
  36. package/dist/props.js +99 -0
  37. package/dist/props.mjs +93 -0
  38. package/dist/registry.cjs +77 -0
  39. package/dist/registry.d.ts +26 -0
  40. package/dist/registry.d.ts.map +1 -0
  41. package/dist/registry.js +91 -0
  42. package/dist/registry.mjs +56 -0
  43. package/dist/styles.cjs +59 -0
  44. package/dist/styles.d.ts +17 -0
  45. package/dist/styles.d.ts.map +1 -0
  46. package/dist/styles.js +53 -0
  47. package/dist/styles.mjs +38 -0
  48. package/dist/targets.cjs +42 -0
  49. package/dist/targets.d.ts +26 -0
  50. package/dist/targets.d.ts.map +1 -0
  51. package/dist/targets.js +35 -0
  52. package/dist/targets.mjs +21 -0
  53. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  54. package/dist/types.cjs +18 -0
  55. package/dist/types.d.ts +78 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +1 -0
  58. package/dist/types.mjs +0 -0
  59. package/package.json +53 -3
package/dist/props.cjs ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/props.ts
21
+ var props_exports = {};
22
+ __export(props_exports, {
23
+ createPropStates: () => createPropStates,
24
+ observedFrom: () => observedFrom,
25
+ prop: () => prop
26
+ });
27
+ module.exports = __toCommonJS(props_exports);
28
+ var import_signal = require("@fluixi/reactive/signal");
29
+ var same = (a, b) => a === b;
30
+ function declare(initial, fromAttribute, toAttribute, options = {}) {
31
+ return {
32
+ initial,
33
+ attribute: options.attribute ?? "",
34
+ reflect: options.reflect ?? false,
35
+ fromAttribute,
36
+ toAttribute,
37
+ equals: options.equals ?? same
38
+ };
39
+ }
40
+ var prop = {
41
+ string: (initial = "", options = {}) => declare(initial, (v) => v ?? initial, (v) => v, options),
42
+ number: (initial = 0, options = {}) => declare(
43
+ initial,
44
+ (v) => {
45
+ const n = v === null || v.trim() === "" ? NaN : Number(v);
46
+ return Number.isNaN(n) ? initial : n;
47
+ },
48
+ (v) => String(v),
49
+ options
50
+ ),
51
+ boolean: (initial = false, options = {}) => declare(initial, (v) => v !== null, (v) => v ? "" : null, options),
52
+ /**
53
+ * JSON in the attribute, the parsed value in the property.
54
+ *
55
+ * Unparseable text keeps the initial value rather than throwing: the attribute is
56
+ * author-supplied and often mid-edit, and an exception in `attributeChangedCallback`
57
+ * leaves the element half-upgraded.
58
+ */
59
+ object: (initial, options = {}) => declare(
60
+ initial,
61
+ (v) => {
62
+ if (v === null) return initial;
63
+ try {
64
+ return JSON.parse(v);
65
+ } catch {
66
+ return initial;
67
+ }
68
+ },
69
+ (v) => v === void 0 ? null : JSON.stringify(v),
70
+ options
71
+ ),
72
+ /**
73
+ * Your own conversion, for a type the built-ins don't cover: a date, a list, a unit.
74
+ *
75
+ * `fromAttribute` only sees a present attribute; removing it goes back to `initial`, so a
76
+ * converter never has to handle `null`. It may return `initial` for text it can't read.
77
+ * `toAttribute` is needed only to reflect, and returning `null` removes the attribute.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * due: prop.custom(new Date(0), {
82
+ * fromAttribute: (v) => new Date(v),
83
+ * toAttribute: (d) => d.toISOString(),
84
+ * }, { equals: (a, b) => a.getTime() === b.getTime() }),
85
+ * ```
86
+ */
87
+ custom: (initial, converter, options = {}) => declare(
88
+ initial,
89
+ (v) => v === null ? initial : converter.fromAttribute(v),
90
+ converter.toAttribute ?? ((v) => v == null ? null : String(v)),
91
+ options
92
+ ),
93
+ /** No attribute at all. For a value only a property assignment can carry. */
94
+ any: (initial, options = {}) => declare(initial, () => initial, () => null, { ...options, attribute: false })
95
+ };
96
+ function createPropStates(schema) {
97
+ const states = {};
98
+ for (const [name, declaration] of Object.entries(schema)) {
99
+ const value = (0, import_signal.signal)(declaration.initial, { equals: declaration.equals, name });
100
+ states[name] = {
101
+ read: value,
102
+ // `.set` reads a function argument as an updater and calls it. A property holding
103
+ // a callback is ordinary for a custom element, so it is wrapped: the value
104
+ // arrives as the return of an updater that ignores the previous one.
105
+ write: (next) => void (typeof next === "function" ? value.set(() => next) : value.set(next)),
106
+ declaration,
107
+ attribute: declaration.attribute === "" ? name.toLowerCase() : declaration.attribute
108
+ };
109
+ }
110
+ return states;
111
+ }
112
+ function observedFrom(schema) {
113
+ return Object.entries(schema).map(([name, d]) => d.attribute === "" ? name.toLowerCase() : d.attribute).filter((a) => a !== false);
114
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Properties, and the attributes that mirror them.
3
+ *
4
+ * A custom element has two ways in. A property assignment (`el.count = 3`) carries a
5
+ * real value and is what a framework binding uses. An attribute (`count="3"`) carries
6
+ * a string and is what HTML and a server render can express. Both have to land on the
7
+ * same signal or the two go out of sync.
8
+ *
9
+ * Each declaration owns the conversion in one place, so the element never guesses.
10
+ * `attribute: false` opts a property out of HTML entirely, which is the right answer
11
+ * for anything a string cannot carry: a function, a Date, a large object.
12
+ */
13
+ import { type Accessor } from '@fluixi/reactive/signal';
14
+ export interface PropDeclaration<T> {
15
+ /** Value before anything sets one. */
16
+ readonly initial: T;
17
+ /**
18
+ * The attribute this mirrors, or false for a property with no HTML form.
19
+ * Defaults to the property name lowercased.
20
+ */
21
+ readonly attribute: string | false;
22
+ /** Write the property back out to the attribute whenever it changes. */
23
+ readonly reflect: boolean;
24
+ /** Attribute string to property value. `null` means the attribute was removed. */
25
+ fromAttribute(value: string | null): T;
26
+ /** Property value to attribute string. `null` removes the attribute. */
27
+ toAttribute(value: T): string | null;
28
+ /** Skip a write that would not change anything, so a reflect loop terminates. */
29
+ equals(a: T, b: T): boolean;
30
+ }
31
+ export type PropsSchema = Record<string, PropDeclaration<never>>;
32
+ /** The reactive accessors an element exposes for its declared properties. */
33
+ export type PropAccessors<S extends Record<string, PropDeclaration<unknown>>> = {
34
+ readonly [K in keyof S]: Accessor<S[K] extends PropDeclaration<infer T> ? T : never>;
35
+ };
36
+ export interface PropOptions<T> {
37
+ attribute?: string | false;
38
+ reflect?: boolean;
39
+ equals?: (a: T, b: T) => boolean;
40
+ }
41
+ /**
42
+ * The built-in conversions.
43
+ *
44
+ * `boolean` follows HTML rather than JSON: presence is true, absence is false, and the
45
+ * value is ignored, so `disabled=""` and `disabled="false"` are both true. Anything
46
+ * else would disagree with every native element on the page.
47
+ */
48
+ export declare const prop: {
49
+ string: (initial?: string, options?: PropOptions<string>) => PropDeclaration<string>;
50
+ number: (initial?: number, options?: PropOptions<number>) => PropDeclaration<number>;
51
+ boolean: (initial?: boolean, options?: PropOptions<boolean>) => PropDeclaration<boolean>;
52
+ /**
53
+ * JSON in the attribute, the parsed value in the property.
54
+ *
55
+ * Unparseable text keeps the initial value rather than throwing: the attribute is
56
+ * author-supplied and often mid-edit, and an exception in `attributeChangedCallback`
57
+ * leaves the element half-upgraded.
58
+ */
59
+ object: <T>(initial: T, options?: PropOptions<T>) => PropDeclaration<T>;
60
+ /**
61
+ * Your own conversion, for a type the built-ins don't cover: a date, a list, a unit.
62
+ *
63
+ * `fromAttribute` only sees a present attribute; removing it goes back to `initial`, so a
64
+ * converter never has to handle `null`. It may return `initial` for text it can't read.
65
+ * `toAttribute` is needed only to reflect, and returning `null` removes the attribute.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * due: prop.custom(new Date(0), {
70
+ * fromAttribute: (v) => new Date(v),
71
+ * toAttribute: (d) => d.toISOString(),
72
+ * }, { equals: (a, b) => a.getTime() === b.getTime() }),
73
+ * ```
74
+ */
75
+ custom: <T>(initial: T, converter: {
76
+ fromAttribute: (value: string) => T;
77
+ toAttribute?: (value: T) => string | null;
78
+ }, options?: PropOptions<T>) => PropDeclaration<T>;
79
+ /** No attribute at all. For a value only a property assignment can carry. */
80
+ any: <T>(initial: T, options?: Omit<PropOptions<T>, "attribute" | "reflect">) => PropDeclaration<T>;
81
+ };
82
+ /** One property's signal plus the declaration that drives its attribute. */
83
+ export interface PropState<T> {
84
+ read: Accessor<T>;
85
+ write(value: T): void;
86
+ declaration: PropDeclaration<T>;
87
+ /** The attribute name, resolved from the property name when it was left blank. */
88
+ attribute: string | false;
89
+ }
90
+ export declare function createPropStates(schema: Record<string, PropDeclaration<unknown>>): Record<string, PropState<unknown>>;
91
+ /** The attribute names an element has to observe, derived from its schema. */
92
+ export declare function observedFrom(schema: Record<string, PropDeclaration<unknown>>): string[];
93
+ //# sourceMappingURL=props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;IACnC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,kFAAkF;IAClF,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC;IACvC,wEAAwE;IACxE,WAAW,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IACrC,iFAAiF;IACjF,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;CAC7B;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;AAEjE,6EAA6E;AAC7E,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,IAAI;IAC9E,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;CACrF,CAAC;AAEF,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;CAClC;AAoBD;;;;;;GAMG;AACH,eAAO,MAAM,IAAI;yCACiB,WAAW,CAAC,MAAM,CAAC;yCAGpB,WAAW,CAAC,MAAM,CAAC;2CAad,WAAW,CAAC,OAAO,CAAC;IAGxD;;;;;;OAMG;aACM,CAAC,WAAW,CAAC,YAAW,WAAW,CAAC,CAAC,CAAC;IAe/C;;;;;;;;;;;;;;OAcG;aACM,CAAC,WACC,CAAC,aACC;QAAE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;QAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;KAAE,YACpF,WAAW,CAAC,CAAC,CAAC;IASzB,6EAA6E;UACvE,CAAC,WAAW,CAAC,YAAW,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAE5E,CAAC;AAEF,4EAA4E;AAC5E,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAClB,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAChC,kFAAkF;IAClF,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,GAC/C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAiBpC;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,GAAG,MAAM,EAAE,CAIvF"}
package/dist/props.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Properties, and the attributes that mirror them.
3
+ *
4
+ * A custom element has two ways in. A property assignment (`el.count = 3`) carries a
5
+ * real value and is what a framework binding uses. An attribute (`count="3"`) carries
6
+ * a string and is what HTML and a server render can express. Both have to land on the
7
+ * same signal or the two go out of sync.
8
+ *
9
+ * Each declaration owns the conversion in one place, so the element never guesses.
10
+ * `attribute: false` opts a property out of HTML entirely, which is the right answer
11
+ * for anything a string cannot carry: a function, a Date, a large object.
12
+ */
13
+ import { signal } from '@fluixi/reactive/signal';
14
+ const same = (a, b) => a === b;
15
+ function declare(initial, fromAttribute, toAttribute, options = {}) {
16
+ return {
17
+ initial,
18
+ attribute: options.attribute ?? '',
19
+ reflect: options.reflect ?? false,
20
+ fromAttribute,
21
+ toAttribute,
22
+ equals: options.equals ?? same,
23
+ };
24
+ }
25
+ /**
26
+ * The built-in conversions.
27
+ *
28
+ * `boolean` follows HTML rather than JSON: presence is true, absence is false, and the
29
+ * value is ignored, so `disabled=""` and `disabled="false"` are both true. Anything
30
+ * else would disagree with every native element on the page.
31
+ */
32
+ export const prop = {
33
+ string: (initial = '', options = {}) => declare(initial, (v) => v ?? initial, (v) => v, options),
34
+ number: (initial = 0, options = {}) => declare(initial, (v) => {
35
+ // Unparseable text keeps the initial value, as `object` does: NaN would pass every
36
+ // type check and then poison arithmetic downstream.
37
+ const n = v === null || v.trim() === '' ? NaN : Number(v);
38
+ return Number.isNaN(n) ? initial : n;
39
+ }, (v) => String(v), options),
40
+ boolean: (initial = false, options = {}) => declare(initial, (v) => v !== null, (v) => (v ? '' : null), options),
41
+ /**
42
+ * JSON in the attribute, the parsed value in the property.
43
+ *
44
+ * Unparseable text keeps the initial value rather than throwing: the attribute is
45
+ * author-supplied and often mid-edit, and an exception in `attributeChangedCallback`
46
+ * leaves the element half-upgraded.
47
+ */
48
+ object: (initial, options = {}) => declare(initial, (v) => {
49
+ if (v === null)
50
+ return initial;
51
+ try {
52
+ return JSON.parse(v);
53
+ }
54
+ catch {
55
+ return initial;
56
+ }
57
+ }, (v) => (v === undefined ? null : JSON.stringify(v)), options),
58
+ /**
59
+ * Your own conversion, for a type the built-ins don't cover: a date, a list, a unit.
60
+ *
61
+ * `fromAttribute` only sees a present attribute; removing it goes back to `initial`, so a
62
+ * converter never has to handle `null`. It may return `initial` for text it can't read.
63
+ * `toAttribute` is needed only to reflect, and returning `null` removes the attribute.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * due: prop.custom(new Date(0), {
68
+ * fromAttribute: (v) => new Date(v),
69
+ * toAttribute: (d) => d.toISOString(),
70
+ * }, { equals: (a, b) => a.getTime() === b.getTime() }),
71
+ * ```
72
+ */
73
+ custom: (initial, converter, options = {}) => declare(initial, (v) => (v === null ? initial : converter.fromAttribute(v)), converter.toAttribute ?? ((v) => (v == null ? null : String(v))), options),
74
+ /** No attribute at all. For a value only a property assignment can carry. */
75
+ any: (initial, options = {}) => declare(initial, () => initial, () => null, { ...options, attribute: false }),
76
+ };
77
+ export function createPropStates(schema) {
78
+ const states = {};
79
+ for (const [name, declaration] of Object.entries(schema)) {
80
+ // Named, so the devtools graph shows `step` rather than an anonymous signal.
81
+ const value = signal(declaration.initial, { equals: declaration.equals, name });
82
+ states[name] = {
83
+ read: value,
84
+ // `.set` reads a function argument as an updater and calls it. A property holding
85
+ // a callback is ordinary for a custom element, so it is wrapped: the value
86
+ // arrives as the return of an updater that ignores the previous one.
87
+ write: (next) => void (typeof next === 'function' ? value.set(() => next) : value.set(next)),
88
+ declaration,
89
+ attribute: declaration.attribute === '' ? name.toLowerCase() : declaration.attribute,
90
+ };
91
+ }
92
+ return states;
93
+ }
94
+ /** The attribute names an element has to observe, derived from its schema. */
95
+ export function observedFrom(schema) {
96
+ return Object.entries(schema)
97
+ .map(([name, d]) => (d.attribute === '' ? name.toLowerCase() : d.attribute))
98
+ .filter((a) => a !== false);
99
+ }
package/dist/props.mjs ADDED
@@ -0,0 +1,93 @@
1
+ // src/props.ts
2
+ import { signal } from "@fluixi/reactive/signal";
3
+ var same = (a, b) => a === b;
4
+ function declare(initial, fromAttribute, toAttribute, options = {}) {
5
+ return {
6
+ initial,
7
+ attribute: options.attribute ?? "",
8
+ reflect: options.reflect ?? false,
9
+ fromAttribute,
10
+ toAttribute,
11
+ equals: options.equals ?? same
12
+ };
13
+ }
14
+ var prop = {
15
+ string: (initial = "", options = {}) => declare(initial, (v) => v ?? initial, (v) => v, options),
16
+ number: (initial = 0, options = {}) => declare(
17
+ initial,
18
+ (v) => {
19
+ const n = v === null || v.trim() === "" ? NaN : Number(v);
20
+ return Number.isNaN(n) ? initial : n;
21
+ },
22
+ (v) => String(v),
23
+ options
24
+ ),
25
+ boolean: (initial = false, options = {}) => declare(initial, (v) => v !== null, (v) => v ? "" : null, options),
26
+ /**
27
+ * JSON in the attribute, the parsed value in the property.
28
+ *
29
+ * Unparseable text keeps the initial value rather than throwing: the attribute is
30
+ * author-supplied and often mid-edit, and an exception in `attributeChangedCallback`
31
+ * leaves the element half-upgraded.
32
+ */
33
+ object: (initial, options = {}) => declare(
34
+ initial,
35
+ (v) => {
36
+ if (v === null) return initial;
37
+ try {
38
+ return JSON.parse(v);
39
+ } catch {
40
+ return initial;
41
+ }
42
+ },
43
+ (v) => v === void 0 ? null : JSON.stringify(v),
44
+ options
45
+ ),
46
+ /**
47
+ * Your own conversion, for a type the built-ins don't cover: a date, a list, a unit.
48
+ *
49
+ * `fromAttribute` only sees a present attribute; removing it goes back to `initial`, so a
50
+ * converter never has to handle `null`. It may return `initial` for text it can't read.
51
+ * `toAttribute` is needed only to reflect, and returning `null` removes the attribute.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * due: prop.custom(new Date(0), {
56
+ * fromAttribute: (v) => new Date(v),
57
+ * toAttribute: (d) => d.toISOString(),
58
+ * }, { equals: (a, b) => a.getTime() === b.getTime() }),
59
+ * ```
60
+ */
61
+ custom: (initial, converter, options = {}) => declare(
62
+ initial,
63
+ (v) => v === null ? initial : converter.fromAttribute(v),
64
+ converter.toAttribute ?? ((v) => v == null ? null : String(v)),
65
+ options
66
+ ),
67
+ /** No attribute at all. For a value only a property assignment can carry. */
68
+ any: (initial, options = {}) => declare(initial, () => initial, () => null, { ...options, attribute: false })
69
+ };
70
+ function createPropStates(schema) {
71
+ const states = {};
72
+ for (const [name, declaration] of Object.entries(schema)) {
73
+ const value = signal(declaration.initial, { equals: declaration.equals, name });
74
+ states[name] = {
75
+ read: value,
76
+ // `.set` reads a function argument as an updater and calls it. A property holding
77
+ // a callback is ordinary for a custom element, so it is wrapped: the value
78
+ // arrives as the return of an updater that ignores the previous one.
79
+ write: (next) => void (typeof next === "function" ? value.set(() => next) : value.set(next)),
80
+ declaration,
81
+ attribute: declaration.attribute === "" ? name.toLowerCase() : declaration.attribute
82
+ };
83
+ }
84
+ return states;
85
+ }
86
+ function observedFrom(schema) {
87
+ return Object.entries(schema).map(([name, d]) => d.attribute === "" ? name.toLowerCase() : d.attribute).filter((a) => a !== false);
88
+ }
89
+ export {
90
+ createPropStates,
91
+ observedFrom,
92
+ prop
93
+ };
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/registry.ts
21
+ var registry_exports = {};
22
+ __export(registry_exports, {
23
+ defineCustomElement: () => defineCustomElement,
24
+ defineWhenUsed: () => defineWhenUsed,
25
+ isDefined: () => isDefined,
26
+ resetRegistryTracking: () => resetRegistryTracking
27
+ });
28
+ module.exports = __toCommonJS(registry_exports);
29
+ var import_dom = require("@fluixi/dom");
30
+ var defined = /* @__PURE__ */ new Map();
31
+ function registry() {
32
+ return typeof customElements === "undefined" ? null : customElements;
33
+ }
34
+ function defineCustomElement(name, constructor, options = {}) {
35
+ if ((0, import_dom.isServer)() && typeof HTMLElement !== "undefined") return constructor;
36
+ const registered = defined.get(name);
37
+ if (registered) return registered;
38
+ const ce = registry();
39
+ if (!ce) return constructor;
40
+ const existing = ce.get(name);
41
+ if (existing) {
42
+ defined.set(name, existing);
43
+ return existing;
44
+ }
45
+ ce.define(name, constructor, options.extends ? { extends: options.extends } : void 0);
46
+ defined.set(name, constructor);
47
+ return constructor;
48
+ }
49
+ function isDefined(name) {
50
+ return defined.has(name) || registry()?.get(name) !== void 0;
51
+ }
52
+ function defineWhenUsed(name, load, options = {}) {
53
+ if ((0, import_dom.isServer)() || typeof document === "undefined") return;
54
+ let started = false;
55
+ const start = async () => {
56
+ if (started) return;
57
+ started = true;
58
+ defineCustomElement(name, await load(), options);
59
+ };
60
+ if (document.querySelector(name)) {
61
+ void start();
62
+ return;
63
+ }
64
+ if (typeof MutationObserver === "undefined") {
65
+ void start();
66
+ return;
67
+ }
68
+ const observer = new MutationObserver(() => {
69
+ if (!document.querySelector(name)) return;
70
+ observer.disconnect();
71
+ void start();
72
+ });
73
+ observer.observe(document.documentElement, { childList: true, subtree: true });
74
+ }
75
+ function resetRegistryTracking() {
76
+ defined.clear();
77
+ }
@@ -0,0 +1,26 @@
1
+ export interface DefineOptions {
2
+ /** Extend a built-in, as `is="..."`. Safari does not implement this. */
3
+ extends?: string;
4
+ }
5
+ /**
6
+ * Register a constructor under a name, at most once.
7
+ *
8
+ * Returns the constructor actually registered, which is the previously registered one
9
+ * when the name is already taken. That matters for a standalone bundle: two copies of
10
+ * the same component on a page should agree on which class the tag upgrades to, and
11
+ * the first to load wins.
12
+ */
13
+ export declare function defineCustomElement(name: string, constructor: CustomElementConstructor, options?: DefineOptions): CustomElementConstructor;
14
+ /** Whether a name has been claimed, by this module or anyone else. */
15
+ export declare function isDefined(name: string): boolean;
16
+ /**
17
+ * Register only once the tag is actually in the document.
18
+ *
19
+ * A component that costs something to load should not cost it on a page that never
20
+ * uses the tag. The observer disconnects after the first match, so the scan is paid
21
+ * for once.
22
+ */
23
+ export declare function defineWhenUsed(name: string, load: () => CustomElementConstructor | Promise<CustomElementConstructor>, options?: DefineOptions): void;
24
+ /** Test seam: forget what this module has registered. The registry itself is permanent. */
25
+ export declare function resetRegistryTracking(): void;
26
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AASD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,wBAAwB,EACrC,OAAO,GAAE,aAAkB,GAC1B,wBAAwB,CAuB1B;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC,EACxE,OAAO,GAAE,aAAkB,GAC1B,IAAI,CA0BN;AAED,2FAA2F;AAC3F,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Registration, which is global and happens once.
3
+ *
4
+ * `customElements.define` throws on a repeated name, and a module that registers on
5
+ * import gets evaluated more than once more often than people expect: two bundles on
6
+ * a page, a dev server round trip, a test file importing the module twice. The throw
7
+ * is a `NotSupportedError` that surfaces far from its cause.
8
+ *
9
+ * Registration is also the one part of this package that cannot run on a server. The
10
+ * guard is `isServer()` from @fluixi/dom rather than a `typeof window` test, because
11
+ * that flag is a realm singleton the server renderer sets. Four copies of a local
12
+ * `isServer` was the alpha.83 SSR bug, and this package does not add a fifth.
13
+ */
14
+ import { isServer } from '@fluixi/dom';
15
+ /** Names this module has defined, so a second call is a no-op rather than a throw. */
16
+ const defined = new Map();
17
+ function registry() {
18
+ return typeof customElements === 'undefined' ? null : customElements;
19
+ }
20
+ /**
21
+ * Register a constructor under a name, at most once.
22
+ *
23
+ * Returns the constructor actually registered, which is the previously registered one
24
+ * when the name is already taken. That matters for a standalone bundle: two copies of
25
+ * the same component on a page should agree on which class the tag upgrades to, and
26
+ * the first to load wins.
27
+ */
28
+ export function defineCustomElement(name, constructor, options = {}) {
29
+ // A server render in a browser-like realm (a jsdom test) must not define for real. On Node
30
+ // the stand-in registry takes it, so the server renderer can find the element by its tag.
31
+ if (isServer() && typeof HTMLElement !== 'undefined')
32
+ return constructor;
33
+ const registered = defined.get(name);
34
+ if (registered)
35
+ return registered;
36
+ const ce = registry();
37
+ // No registry means a non-browser realm that is not the server renderer either,
38
+ // such as a worker. Nothing to register into, and nothing to warn about.
39
+ if (!ce)
40
+ return constructor;
41
+ const existing = ce.get(name);
42
+ if (existing) {
43
+ // Another bundle got here first. Remember it so this module stops asking.
44
+ defined.set(name, existing);
45
+ return existing;
46
+ }
47
+ ce.define(name, constructor, options.extends ? { extends: options.extends } : undefined);
48
+ defined.set(name, constructor);
49
+ return constructor;
50
+ }
51
+ /** Whether a name has been claimed, by this module or anyone else. */
52
+ export function isDefined(name) {
53
+ return defined.has(name) || registry()?.get(name) !== undefined;
54
+ }
55
+ /**
56
+ * Register only once the tag is actually in the document.
57
+ *
58
+ * A component that costs something to load should not cost it on a page that never
59
+ * uses the tag. The observer disconnects after the first match, so the scan is paid
60
+ * for once.
61
+ */
62
+ export function defineWhenUsed(name, load, options = {}) {
63
+ if (isServer() || typeof document === 'undefined')
64
+ return;
65
+ let started = false;
66
+ const start = async () => {
67
+ if (started)
68
+ return;
69
+ started = true;
70
+ defineCustomElement(name, await load(), options);
71
+ };
72
+ if (document.querySelector(name)) {
73
+ void start();
74
+ return;
75
+ }
76
+ if (typeof MutationObserver === 'undefined') {
77
+ void start();
78
+ return;
79
+ }
80
+ const observer = new MutationObserver(() => {
81
+ if (!document.querySelector(name))
82
+ return;
83
+ observer.disconnect();
84
+ void start();
85
+ });
86
+ observer.observe(document.documentElement, { childList: true, subtree: true });
87
+ }
88
+ /** Test seam: forget what this module has registered. The registry itself is permanent. */
89
+ export function resetRegistryTracking() {
90
+ defined.clear();
91
+ }
@@ -0,0 +1,56 @@
1
+ // src/registry.ts
2
+ import { isServer } from "@fluixi/dom";
3
+ var defined = /* @__PURE__ */ new Map();
4
+ function registry() {
5
+ return typeof customElements === "undefined" ? null : customElements;
6
+ }
7
+ function defineCustomElement(name, constructor, options = {}) {
8
+ if (isServer() && typeof HTMLElement !== "undefined") return constructor;
9
+ const registered = defined.get(name);
10
+ if (registered) return registered;
11
+ const ce = registry();
12
+ if (!ce) return constructor;
13
+ const existing = ce.get(name);
14
+ if (existing) {
15
+ defined.set(name, existing);
16
+ return existing;
17
+ }
18
+ ce.define(name, constructor, options.extends ? { extends: options.extends } : void 0);
19
+ defined.set(name, constructor);
20
+ return constructor;
21
+ }
22
+ function isDefined(name) {
23
+ return defined.has(name) || registry()?.get(name) !== void 0;
24
+ }
25
+ function defineWhenUsed(name, load, options = {}) {
26
+ if (isServer() || typeof document === "undefined") return;
27
+ let started = false;
28
+ const start = async () => {
29
+ if (started) return;
30
+ started = true;
31
+ defineCustomElement(name, await load(), options);
32
+ };
33
+ if (document.querySelector(name)) {
34
+ void start();
35
+ return;
36
+ }
37
+ if (typeof MutationObserver === "undefined") {
38
+ void start();
39
+ return;
40
+ }
41
+ const observer = new MutationObserver(() => {
42
+ if (!document.querySelector(name)) return;
43
+ observer.disconnect();
44
+ void start();
45
+ });
46
+ observer.observe(document.documentElement, { childList: true, subtree: true });
47
+ }
48
+ function resetRegistryTracking() {
49
+ defined.clear();
50
+ }
51
+ export {
52
+ defineCustomElement,
53
+ defineWhenUsed,
54
+ isDefined,
55
+ resetRegistryTracking
56
+ };