@rupertsworld/observable 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rupertsworld/observable",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Observable properties mixin and ObservableElement with attribute reflection",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,6 +9,7 @@
9
9
  "dist"
10
10
  ],
11
11
  "scripts": {
12
+ "prepublishOnly": "npm run build",
12
13
  "build": "tsc -p tsconfig.json",
13
14
  "typecheck": "tsc -p tsconfig.json --noEmit",
14
15
  "test": "vitest run --environment happy-dom",
@@ -1,36 +0,0 @@
1
- import type { EventNames, EventForType } from "@rupertsworld/event-target";
2
- type NativeHTMLElement = InstanceType<typeof globalThis.HTMLElement>;
3
- type ReducedHTMLElement = Omit<NativeHTMLElement, "addEventListener" | "removeEventListener" | "dispatchEvent">;
4
- /** Supported type constructors for property coercion. */
5
- type ObservedType = StringConstructor | NumberConstructor | BooleanConstructor | ObjectConstructor;
6
- /** Configuration for a single observed property. */
7
- export type ObservedPropertyConfig = {
8
- type: ObservedType;
9
- /** If set, syncs this property to/from the named attribute. */
10
- attribute?: string;
11
- };
12
- /** Map of property names to their observation config. */
13
- export type ObservedPropertyMap = Record<string, ObservedPropertyConfig>;
14
- export interface BaseElement<T extends Event = Event> extends ReducedHTMLElement {
15
- addEventListener<K extends EventNames<T>>(type: K, listener: ((ev: EventForType<T, K>) => void) | null, options?: boolean | AddEventListenerOptions): void;
16
- addEventListener<K extends EventNames<T>>(type: K, listener: EventListener | EventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
17
- removeEventListener<K extends EventNames<T>>(type: K, listener: ((ev: EventForType<T, K>) => void) | null, options?: boolean | EventListenerOptions): void;
18
- removeEventListener<K extends EventNames<T>>(type: K, listener: EventListener | EventListenerObject | null, options?: boolean | EventListenerOptions): void;
19
- dispatchEvent(event: T): boolean;
20
- }
21
- /**
22
- * Base class for custom elements with reactive observed properties.
23
- *
24
- * Define `static observedProperties` to automatically sync properties with
25
- * attributes and receive `propertyChangedCallback` notifications.
26
- */
27
- export declare class BaseElement<T extends Event = Event> extends globalThis.HTMLElement {
28
- static observedProperties?: ObservedPropertyMap;
29
- static get observedAttributes(): string[];
30
- constructor();
31
- attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
32
- /** Override to react to observed property changes. */
33
- propertyChangedCallback(_name: string, _oldValue: unknown, _newValue: unknown): void;
34
- }
35
- export {};
36
- //# sourceMappingURL=html-element.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"html-element.d.ts","sourceRoot":"","sources":["../src/html-element.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE3E,KAAK,iBAAiB,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;AAErE,KAAK,kBAAkB,GAAG,IAAI,CAC5B,iBAAiB,EACjB,kBAAkB,GAAG,qBAAqB,GAAG,eAAe,CAC7D,CAAC;AAEF,yDAAyD;AACzD,KAAK,YAAY,GACb,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,CAAC;AAEtB,oDAAoD;AACpD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAoLzE,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,CAAE,SAAQ,kBAAkB;IAC9E,gBAAgB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACtC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IAER,gBAAgB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACtC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,aAAa,GAAG,mBAAmB,GAAG,IAAI,EACpD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IAER,mBAAmB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACzC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IAER,mBAAmB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACzC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,aAAa,GAAG,mBAAmB,GAAG,IAAI,EACpD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IAER,aAAa,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,CAAE,SAAQ,UAAU,CAAC,WAAW;IAC9E,MAAM,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;IAEhD,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAWxC;;IAgBD,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IA0BvF,sDAAsD;IACtD,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI;CACrF"}
@@ -1,190 +0,0 @@
1
- const stateByInstance = new WeakMap();
2
- const setupByConstructor = new WeakSet();
3
- const managedProps = new WeakMap();
4
- const asRecord = (obj) => obj;
5
- function coerceFromAttribute(type, value) {
6
- if (type === Boolean) {
7
- if (value === null)
8
- return false;
9
- return value !== "false";
10
- }
11
- if (value === null)
12
- return null;
13
- if (type === Number)
14
- return Number(value);
15
- if (type === Object)
16
- return JSON.parse(value);
17
- return value;
18
- }
19
- function serializeToAttribute(type, value) {
20
- if (value == null)
21
- return null;
22
- if (type === Boolean)
23
- return value ? "" : "false";
24
- if (type === Object)
25
- return JSON.stringify(value);
26
- return String(value);
27
- }
28
- function configForAttribute(ctor, attr) {
29
- for (const [prop, config] of Object.entries(ctor.observedProperties ?? {})) {
30
- if (config.attribute === attr)
31
- return { prop, config };
32
- }
33
- return null;
34
- }
35
- function getState(instance) {
36
- let state = stateByInstance.get(instance);
37
- if (!state) {
38
- state = {
39
- constructing: true,
40
- syncingFromAttribute: null,
41
- syncingToAttribute: null,
42
- values: new Map(),
43
- };
44
- stateByInstance.set(instance, state);
45
- }
46
- return state;
47
- }
48
- function isManaged(ctor, prop) {
49
- return managedProps.get(ctor)?.has(prop) ?? false;
50
- }
51
- function wrapUserAttributeCallback(ctor) {
52
- const desc = Object.getOwnPropertyDescriptor(ctor.prototype, "attributeChangedCallback");
53
- if (!desc || typeof desc.value !== "function")
54
- return;
55
- const userFn = desc.value;
56
- if (userFn === BaseElement.prototype.attributeChangedCallback)
57
- return;
58
- Object.defineProperty(ctor.prototype, "attributeChangedCallback", {
59
- configurable: true,
60
- writable: true,
61
- value(name, oldVal, newVal) {
62
- BaseElement.prototype.attributeChangedCallback.call(this, name, oldVal, newVal);
63
- userFn.call(this, name, oldVal, newVal);
64
- },
65
- });
66
- }
67
- function setup(ctor) {
68
- wrapUserAttributeCallback(ctor);
69
- const observed = ctor.observedProperties ?? {};
70
- const managed = new Set();
71
- managedProps.set(ctor, managed);
72
- for (const [prop, config] of Object.entries(observed)) {
73
- const existing = Object.getOwnPropertyDescriptor(ctor.prototype, prop);
74
- if (existing?.get || existing?.set) {
75
- throw new Error(`Observed property "${prop}" cannot define a custom getter/setter. ` +
76
- `Use propertyChangedCallback for side effects instead.`);
77
- }
78
- if (existing)
79
- continue;
80
- managed.add(prop);
81
- const attr = config.attribute;
82
- Object.defineProperty(ctor.prototype, prop, {
83
- enumerable: true,
84
- configurable: true,
85
- get() {
86
- return getState(this).values.get(prop);
87
- },
88
- set(next) {
89
- const state = getState(this);
90
- const prev = state.values.get(prop);
91
- if (Object.is(prev, next))
92
- return;
93
- state.values.set(prop, next);
94
- if (attr && state.syncingFromAttribute !== attr && state.syncingToAttribute !== attr) {
95
- const serialized = serializeToAttribute(config.type, next);
96
- state.syncingToAttribute = attr;
97
- try {
98
- serialized === null ? this.removeAttribute(attr) : this.setAttribute(attr, serialized);
99
- }
100
- finally {
101
- state.syncingToAttribute = null;
102
- }
103
- }
104
- if (!state.constructing || state.syncingFromAttribute !== null) {
105
- this.propertyChangedCallback(prop, prev, next);
106
- }
107
- },
108
- });
109
- }
110
- }
111
- function initializeProperties(el, ctor) {
112
- const state = getState(el);
113
- const observed = ctor.observedProperties ?? {};
114
- const rec = asRecord(el);
115
- for (const [prop, config] of Object.entries(observed)) {
116
- if (!isManaged(ctor, prop))
117
- continue;
118
- const hasOwn = Object.prototype.hasOwnProperty.call(el, prop);
119
- const ownValue = hasOwn ? rec[prop] : undefined;
120
- if (hasOwn)
121
- delete rec[prop];
122
- const attr = config.attribute;
123
- if (attr && el.hasAttribute(attr)) {
124
- const coerced = coerceFromAttribute(config.type, el.getAttribute(attr));
125
- state.syncingFromAttribute = attr;
126
- try {
127
- rec[prop] = coerced;
128
- }
129
- finally {
130
- state.syncingFromAttribute = null;
131
- }
132
- continue;
133
- }
134
- if (hasOwn)
135
- rec[prop] = ownValue;
136
- }
137
- state.constructing = false;
138
- }
139
- /**
140
- * Base class for custom elements with reactive observed properties.
141
- *
142
- * Define `static observedProperties` to automatically sync properties with
143
- * attributes and receive `propertyChangedCallback` notifications.
144
- */
145
- export class BaseElement extends globalThis.HTMLElement {
146
- static observedProperties;
147
- static get observedAttributes() {
148
- const ctor = this;
149
- if (!setupByConstructor.has(ctor)) {
150
- setup(ctor);
151
- setupByConstructor.add(ctor);
152
- }
153
- return Object.values(this.observedProperties ?? {})
154
- .map((c) => c.attribute)
155
- .filter((a) => Boolean(a));
156
- }
157
- constructor() {
158
- super();
159
- const ctor = this.constructor;
160
- if (!setupByConstructor.has(ctor)) {
161
- setup(ctor);
162
- setupByConstructor.add(ctor);
163
- }
164
- getState(this);
165
- queueMicrotask(() => initializeProperties(this, ctor));
166
- }
167
- attributeChangedCallback(name, _old, value) {
168
- const ctor = this.constructor;
169
- const found = configForAttribute(ctor, name);
170
- if (!found || !isManaged(ctor, found.prop))
171
- return;
172
- const state = getState(this);
173
- if (state.syncingToAttribute === name)
174
- return;
175
- const coerced = coerceFromAttribute(found.config.type, value);
176
- const rec = asRecord(this);
177
- if (Object.prototype.hasOwnProperty.call(this, found.prop)) {
178
- delete rec[found.prop];
179
- }
180
- state.syncingFromAttribute = name;
181
- try {
182
- rec[found.prop] = coerced;
183
- }
184
- finally {
185
- state.syncingFromAttribute = null;
186
- }
187
- }
188
- /** Override to react to observed property changes. */
189
- propertyChangedCallback(_name, _oldValue, _newValue) { }
190
- }
@@ -1,41 +0,0 @@
1
- type EventNames<U extends Event> = U extends unknown ? U extends {
2
- type: infer T extends string;
3
- } ? T : never : never;
4
- type EventForType<U extends Event, K extends string> = Extract<U, {
5
- type: K;
6
- }>;
7
- type NativeHTMLElement = InstanceType<typeof globalThis.HTMLElement>;
8
- type ReducedHTMLElement = Omit<NativeHTMLElement, "addEventListener" | "removeEventListener" | "dispatchEvent">;
9
- /** Supported type constructors for property coercion. */
10
- type ObservedType = StringConstructor | NumberConstructor | BooleanConstructor | ObjectConstructor;
11
- /** Configuration for a single observed property. */
12
- export type ObservedPropertyConfig = {
13
- type: ObservedType;
14
- /** If set, syncs this property to/from the named attribute. */
15
- attribute?: string;
16
- };
17
- /** Map of property names to their observation config. */
18
- export type ObservedPropertyMap = Record<string, ObservedPropertyConfig>;
19
- export interface BaseElement<T extends Event = Event> extends ReducedHTMLElement {
20
- addEventListener<K extends EventNames<T>>(type: K, listener: ((ev: EventForType<T, K>) => void) | null, options?: boolean | AddEventListenerOptions): void;
21
- addEventListener<K extends EventNames<T>>(type: K, listener: EventListener | EventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
22
- removeEventListener<K extends EventNames<T>>(type: K, listener: ((ev: EventForType<T, K>) => void) | null, options?: boolean | EventListenerOptions): void;
23
- removeEventListener<K extends EventNames<T>>(type: K, listener: EventListener | EventListenerObject | null, options?: boolean | EventListenerOptions): void;
24
- dispatchEvent(event: T): boolean;
25
- }
26
- /**
27
- * Base class for custom elements with reactive observed properties.
28
- *
29
- * Define `static observedProperties` to automatically sync properties with
30
- * attributes and receive `propertyChangedCallback` notifications.
31
- */
32
- export declare class BaseElement<T extends Event = Event> extends globalThis.HTMLElement {
33
- static observedProperties?: ObservedPropertyMap;
34
- static get observedAttributes(): string[];
35
- constructor();
36
- attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
37
- /** Override to react to observed property changes. */
38
- propertyChangedCallback(_name: string, _oldValue: unknown, _newValue: unknown): void;
39
- }
40
- export {};
41
- //# sourceMappingURL=reactive-element.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"reactive-element.d.ts","sourceRoot":"","sources":["../src/reactive-element.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,OAAO,GAChD,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC,SAAS,MAAM,CAAA;CAAE,GACxC,CAAC,GACD,KAAK,GACP,KAAK,CAAC;AAEV,KAAK,YAAY,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,SAAS,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAE/E,KAAK,iBAAiB,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;AAErE,KAAK,kBAAkB,GAAG,IAAI,CAC5B,iBAAiB,EACjB,kBAAkB,GAAG,qBAAqB,GAAG,eAAe,CAC7D,CAAC;AAEF,yDAAyD;AACzD,KAAK,YAAY,GACb,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,CAAC;AAEtB,oDAAoD;AACpD,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAoLzE,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,CAAE,SAAQ,kBAAkB;IAC9E,gBAAgB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACtC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IAER,gBAAgB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACtC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,aAAa,GAAG,mBAAmB,GAAG,IAAI,EACpD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IAER,mBAAmB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACzC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IAER,mBAAmB,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EACzC,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,aAAa,GAAG,mBAAmB,GAAG,IAAI,EACpD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IAER,aAAa,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,CAAE,SAAQ,UAAU,CAAC,WAAW;IAC9E,MAAM,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;IAEhD,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAWxC;;IAgBD,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IA0BvF,sDAAsD;IACtD,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI;CACrF"}
@@ -1,190 +0,0 @@
1
- const stateByInstance = new WeakMap();
2
- const setupByConstructor = new WeakSet();
3
- const managedProps = new WeakMap();
4
- const asRecord = (obj) => obj;
5
- function coerceFromAttribute(type, value) {
6
- if (type === Boolean) {
7
- if (value === null)
8
- return false;
9
- return value !== "false";
10
- }
11
- if (value === null)
12
- return null;
13
- if (type === Number)
14
- return Number(value);
15
- if (type === Object)
16
- return JSON.parse(value);
17
- return value;
18
- }
19
- function serializeToAttribute(type, value) {
20
- if (value == null)
21
- return null;
22
- if (type === Boolean)
23
- return value ? "" : "false";
24
- if (type === Object)
25
- return JSON.stringify(value);
26
- return String(value);
27
- }
28
- function configForAttribute(ctor, attr) {
29
- for (const [prop, config] of Object.entries(ctor.observedProperties ?? {})) {
30
- if (config.attribute === attr)
31
- return { prop, config };
32
- }
33
- return null;
34
- }
35
- function getState(instance) {
36
- let state = stateByInstance.get(instance);
37
- if (!state) {
38
- state = {
39
- constructing: true,
40
- syncingFromAttribute: null,
41
- syncingToAttribute: null,
42
- values: new Map(),
43
- };
44
- stateByInstance.set(instance, state);
45
- }
46
- return state;
47
- }
48
- function isManaged(ctor, prop) {
49
- return managedProps.get(ctor)?.has(prop) ?? false;
50
- }
51
- function wrapUserAttributeCallback(ctor) {
52
- const desc = Object.getOwnPropertyDescriptor(ctor.prototype, "attributeChangedCallback");
53
- if (!desc || typeof desc.value !== "function")
54
- return;
55
- const userFn = desc.value;
56
- if (userFn === BaseElement.prototype.attributeChangedCallback)
57
- return;
58
- Object.defineProperty(ctor.prototype, "attributeChangedCallback", {
59
- configurable: true,
60
- writable: true,
61
- value(name, oldVal, newVal) {
62
- BaseElement.prototype.attributeChangedCallback.call(this, name, oldVal, newVal);
63
- userFn.call(this, name, oldVal, newVal);
64
- },
65
- });
66
- }
67
- function setup(ctor) {
68
- wrapUserAttributeCallback(ctor);
69
- const observed = ctor.observedProperties ?? {};
70
- const managed = new Set();
71
- managedProps.set(ctor, managed);
72
- for (const [prop, config] of Object.entries(observed)) {
73
- const existing = Object.getOwnPropertyDescriptor(ctor.prototype, prop);
74
- if (existing?.get || existing?.set) {
75
- throw new Error(`Observed property "${prop}" cannot define a custom getter/setter. ` +
76
- `Use propertyChangedCallback for side effects instead.`);
77
- }
78
- if (existing)
79
- continue;
80
- managed.add(prop);
81
- const attr = config.attribute;
82
- Object.defineProperty(ctor.prototype, prop, {
83
- enumerable: true,
84
- configurable: true,
85
- get() {
86
- return getState(this).values.get(prop);
87
- },
88
- set(next) {
89
- const state = getState(this);
90
- const prev = state.values.get(prop);
91
- if (Object.is(prev, next))
92
- return;
93
- state.values.set(prop, next);
94
- if (attr && state.syncingFromAttribute !== attr && state.syncingToAttribute !== attr) {
95
- const serialized = serializeToAttribute(config.type, next);
96
- state.syncingToAttribute = attr;
97
- try {
98
- serialized === null ? this.removeAttribute(attr) : this.setAttribute(attr, serialized);
99
- }
100
- finally {
101
- state.syncingToAttribute = null;
102
- }
103
- }
104
- if (!state.constructing || state.syncingFromAttribute !== null) {
105
- this.propertyChangedCallback(prop, prev, next);
106
- }
107
- },
108
- });
109
- }
110
- }
111
- function initializeProperties(el, ctor) {
112
- const state = getState(el);
113
- const observed = ctor.observedProperties ?? {};
114
- const rec = asRecord(el);
115
- for (const [prop, config] of Object.entries(observed)) {
116
- if (!isManaged(ctor, prop))
117
- continue;
118
- const hasOwn = Object.prototype.hasOwnProperty.call(el, prop);
119
- const ownValue = hasOwn ? rec[prop] : undefined;
120
- if (hasOwn)
121
- delete rec[prop];
122
- const attr = config.attribute;
123
- if (attr && el.hasAttribute(attr)) {
124
- const coerced = coerceFromAttribute(config.type, el.getAttribute(attr));
125
- state.syncingFromAttribute = attr;
126
- try {
127
- rec[prop] = coerced;
128
- }
129
- finally {
130
- state.syncingFromAttribute = null;
131
- }
132
- continue;
133
- }
134
- if (hasOwn)
135
- rec[prop] = ownValue;
136
- }
137
- state.constructing = false;
138
- }
139
- /**
140
- * Base class for custom elements with reactive observed properties.
141
- *
142
- * Define `static observedProperties` to automatically sync properties with
143
- * attributes and receive `propertyChangedCallback` notifications.
144
- */
145
- export class BaseElement extends globalThis.HTMLElement {
146
- static observedProperties;
147
- static get observedAttributes() {
148
- const ctor = this;
149
- if (!setupByConstructor.has(ctor)) {
150
- setup(ctor);
151
- setupByConstructor.add(ctor);
152
- }
153
- return Object.values(this.observedProperties ?? {})
154
- .map((c) => c.attribute)
155
- .filter((a) => Boolean(a));
156
- }
157
- constructor() {
158
- super();
159
- const ctor = this.constructor;
160
- if (!setupByConstructor.has(ctor)) {
161
- setup(ctor);
162
- setupByConstructor.add(ctor);
163
- }
164
- getState(this);
165
- queueMicrotask(() => initializeProperties(this, ctor));
166
- }
167
- attributeChangedCallback(name, _old, value) {
168
- const ctor = this.constructor;
169
- const found = configForAttribute(ctor, name);
170
- if (!found || !isManaged(ctor, found.prop))
171
- return;
172
- const state = getState(this);
173
- if (state.syncingToAttribute === name)
174
- return;
175
- const coerced = coerceFromAttribute(found.config.type, value);
176
- const rec = asRecord(this);
177
- if (Object.prototype.hasOwnProperty.call(this, found.prop)) {
178
- delete rec[found.prop];
179
- }
180
- state.syncingFromAttribute = name;
181
- try {
182
- rec[found.prop] = coerced;
183
- }
184
- finally {
185
- state.syncingFromAttribute = null;
186
- }
187
- }
188
- /** Override to react to observed property changes. */
189
- propertyChangedCallback(_name, _oldValue, _newValue) { }
190
- }