@rusticarcade/palette 0.3.1 → 0.7.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.
@@ -5,30 +5,15 @@ type StateListener<Shape> = (state: Shape) => void;
5
5
  *
6
6
  * The State class wraps stateful data and provides an API for mutating the data
7
7
  * while listeners subscribe to meaningful updates.
8
- *
9
- * Modify state with setters, mutators, transactions, or proxied updates with
10
- * deep reactivity and array mutation support built in.
11
- *
12
- * @example
13
- *
14
- * ```typescript
15
- * const state = new State({count: 1});
16
- * state.addListener(data => console.log(data))
17
- *
18
- * state.set("count", 2); // Prints "2"
19
- * state.set("count", 2); // Nothing happens, no change
20
- * state.live.count = 3; // Prints "3"
21
- * ```
22
8
  */
23
9
  declare class State<Shape extends object> {
10
+ static isState<T extends object>(value: unknown): value is State<T>;
24
11
  private _data;
25
12
  private _listeners;
26
13
  private _proxy?;
27
14
  private _proxyCache;
28
15
  private _reverseProxyCache;
29
- private _isLocked;
30
- private _hasWarned;
31
- constructor(initialData: Shape, onChange?: StateListener<Shape>);
16
+ constructor(initialData: Shape, listener?: StateListener<Shape>);
32
17
  /**
33
18
  * Lazily create and cache proxies into the state for reactive updates on
34
19
  * nested values.
@@ -58,37 +43,17 @@ declare class State<Shape extends object> {
58
43
  * This is a direct reference to the internal state. It is marked as Readonly
59
44
  * to help prevent accidental changes. Never edit this value directly.
60
45
  */
61
- get current(): Readonly<Shape>;
46
+ get raw(): Readonly<Shape>;
62
47
  /**
63
- * Proxied access to state properties with automatic reconciliation.
64
- *
65
- * Allows direct property access for both reading and writing state values.
48
+ * Live reactive accessor for state data. Deeply nested changes trigger
49
+ * automatic updates, including array, Map, and Set mutations.
66
50
  *
67
- * @example
68
- *
69
- * Deep reactivity with the `.live` accessor
70
- *
71
- * ```typescript
72
- * const state = new State({
73
- * nested: {
74
- * values: [1, 2, 3],
75
- * }
76
- * });
77
- *
78
- * // Deep value assignment triggers state updates
79
- * state.live.nested.values[1] = 5;
80
- *
81
- * // Array mutators are automatically reactive as well
82
- * state.live.nested.values.push(4);
83
- * state.live.nested.values.reverse();
84
- * ```
51
+ * @remarks
52
+ * For deeply nested values, the `.live` accessor may add performance overhead
53
+ * compared to directly accessing the value via `.current`
85
54
  */
86
55
  get live(): Shape;
87
56
  /**
88
- * Indicates if this state is currently locked (true) or not (false)
89
- */
90
- get isLocked(): boolean;
91
- /**
92
57
  * Add a handler function for when this state changes.
93
58
  *
94
59
  * Listeners are invoked in the order they are registered and are passed a
@@ -107,14 +72,6 @@ declare class State<Shape extends object> {
107
72
  *
108
73
  * The value is returned as Readonly to prevent accidental state mutations.
109
74
  * To mutate stateful properties, use {@link set} or {@link patch} instead.
110
- *
111
- * @example
112
- *
113
- * ```typescript
114
- * const state = new State({ count: 1 });
115
- * console.log(state.get("count")); // 1
116
- * ```
117
- *
118
75
  */
119
76
  get: <K extends keyof Shape>(key: K) => Readonly<Shape[K]>;
120
77
  /**
@@ -123,13 +80,6 @@ declare class State<Shape extends object> {
123
80
  *
124
81
  * The object returned from this function can be edited without modifying the
125
82
  * actual internal state.
126
- *
127
- * @example
128
- *
129
- * ```typescript
130
- * const snap = state.snapshot();
131
- * snap.count += 1;
132
- * state.patch(snap);
133
83
  * ```
134
84
  */
135
85
  snapshot: () => Shape;
@@ -146,44 +96,17 @@ declare class State<Shape extends object> {
146
96
  * state.set("count", 2); // State is now { count: 2, color: "red" }
147
97
  * ```
148
98
  */
149
- set: <K extends keyof Shape>(key: K, value: Shape[K]) => void;
99
+ set: <K extends keyof Shape>(key: K, value: Shape[K]) => State<Shape>;
150
100
  /**
151
101
  * Set multiple stateful properties at once, leaving omitted properties
152
102
  * unchanged.
153
- *
154
- * @example
155
- *
156
- * Patch a partial state, updating all listed properties at once
157
- *
158
- * ```typescript
159
- * const state = new State({
160
- * weather: "sunny",
161
- * temperature: 30,
162
- * humidity: 70,
163
- * });
164
- *
165
- * // Leaves `temperature` unchanged
166
- * state.patch({
167
- * weather: "cloudy",
168
- * humidity: 50,
169
- * });
170
- * ```
171
103
  */
172
104
  patch: (patch: Partial<Shape>) => State<Shape>;
173
105
  /**
174
- * Fully replace the current state data and force listeners to receive the
175
- * updated data immediately.
106
+ * Fully replace the current state data and force an update
176
107
  */
177
108
  replace: (state: Shape) => State<Shape>;
178
109
  /**
179
- * Lock this State instance, preventing further external changes
180
- */
181
- lock: () => State<Shape>;
182
- /**
183
- * Unlock this State instance, allowing further external changes
184
- */
185
- unlock: () => State<Shape>;
186
- /**
187
110
  * Apply complex updates to the state using a mutator function.
188
111
  *
189
112
  * The mutator function takes one parameter which is a structuredClone copy of
@@ -191,87 +114,79 @@ declare class State<Shape extends object> {
191
114
  * patched in to the state.
192
115
  */
193
116
  mutate: (mutator: (current: Shape) => Shape) => State<Shape>;
194
- /**
195
- * Perform an async mutation of the state, optionally locking the state during
196
- * the process.
197
- * @param mutator A function to mutate and return the new state
198
- * @param lock If `true`, lock the state until the mutator completes
199
- */
200
- mutateAsync: (mutator: (current: Shape) => Promise<Shape>, lock?: boolean) => Promise<State<Shape>>;
201
- /**
202
- * Perform a transaction-style set of actions defined within a function.
203
- *
204
- * The provided function can do anything, beyond just setting state. Any
205
- * uncaught errors thrown from within the function will cause the transaction
206
- * to fail and the state to automatically roll back to the last valid state.
207
- *
208
- * During a transaction, this state instance will be locked, preventing other
209
- * changes.
210
- *
211
- * Transactions will always result in a state update when successful.
212
- *
213
- * @example
214
- *
215
- * Transactions with locking and rollback support
216
- *
217
- * ```typescript
218
- * const state = new State({count: 1});
219
- *
220
- * state.transaction(async (s) => {
221
- * // `s` is a full State object you can safely manipulate
222
- * s.set("count", 10);
223
- * });
224
- * state.get("count"); // => 10;
225
- *
226
- * // Errors inside the transaction roll back the state
227
- * state.transaction(async (s) => {
228
- * s.set("count", 100);
229
- * throw new Error();
230
- * });
231
- * state.get("count"); // => 10;
232
- */
233
- transaction: (fn: (state: State<Shape>) => void) => boolean;
234
- /**
235
- * Perform a transaction-style set of actions defined within an async function
236
- *
237
- * The provided function can do anything, beyond just setting state. Any
238
- * uncaught errors thrown from within the function will cause the transaction
239
- * to fail and the state to automatically roll back to the last valid state.
240
- *
241
- * During a transaction, this state instance will be locked, preventing other
242
- * changes.
243
- *
244
- * Transactions will always result in a state update when successful.
245
- *
246
- * @example
247
- *
248
- * Transactions with locking and rollback support
249
- *
250
- * ```typescript
251
- * const state = new State({count: 1});
252
- *
253
- * // Awaiting the result of a transaction
254
- * await state.transactionAsync(async (s) => {
255
- * // `s` is a full State object you can safely manipulate
256
- * s.set("count", 10);
257
- * });
258
- * state.get("count"); // => 10;
259
- *
260
- * // Errors inside the transaction roll back the state
261
- * await state.transactionAsync(async (s) => {
262
- * s.set("count", 100);
263
- * throw new Error();
264
- * });
265
- * state.get("count"); // => 10;
266
- *
267
- * // If you forget to await the transaction, its still locked
268
- * state.transactionAsync(async (s) => {
269
- * await waitSeconds(1);
270
- * });
271
- * state.set("count", 1); // Error: State is locked!
272
- * ```
273
- */
274
- transactionAsync: (fn: (state: State<Shape>) => Promise<void>) => Promise<boolean>;
117
+ }
118
+ type ParsedNotation = {
119
+ /** The raw string representation of the parsed notation */
120
+ base: string;
121
+ /** An array of property names forming a path to the notation value */
122
+ path: string[];
123
+ modifiers?: {
124
+ not?: boolean;
125
+ };
126
+ };
127
+ type NotationAccessor = (source: unknown) => unknown;
128
+ declare const enum Directive {
129
+ Each = "::each",
130
+ Key = "::key",
131
+ Tag = "::tag",
132
+ Swap = "::swap",
133
+ If = "::if",
134
+ ElseIf = "::else-if",
135
+ Else = "::else"
136
+ }
137
+ declare const enum UpdateType {
138
+ Conditional = "cond",
139
+ Tag = "tag",
140
+ Attribute = "attr",
141
+ List = "list",
142
+ Swap = "swap"
143
+ }
144
+ type ConditionalRenderSchemeBranch = {
145
+ type: Directive.If | Directive.ElseIf;
146
+ notation: ParsedNotation;
147
+ branchRootRef: string;
148
+ branchTemplateHTML: string;
149
+ } | {
150
+ type: Directive.Else;
151
+ branchRootRef: string;
152
+ branchTemplateHTML: string;
153
+ };
154
+ interface ConditionalRenderScheme {
155
+ type: UpdateType.Conditional;
156
+ branches: ConditionalRenderSchemeBranch[];
157
+ }
158
+ interface AttributeUpdateScheme {
159
+ type: UpdateType.Attribute;
160
+ notation: ParsedNotation;
161
+ attribute: string;
162
+ nodeRef: string;
163
+ }
164
+ interface TagChangeScheme {
165
+ type: UpdateType.Tag;
166
+ notation: ParsedNotation;
167
+ nodeRef: string;
168
+ }
169
+ interface ContentSwapScheme {
170
+ type: UpdateType.Swap;
171
+ notation: ParsedNotation;
172
+ nodeRef: string;
173
+ }
174
+ interface ListRenderScheme {
175
+ type: UpdateType.List;
176
+ notation: ParsedNotation;
177
+ keyNotation: ParsedNotation;
178
+ nodeRef: string;
179
+ listContentHtml: string;
180
+ }
181
+ type AnyScheme = AttributeUpdateScheme | TagChangeScheme | ContentSwapScheme | ListRenderScheme | ConditionalRenderScheme;
182
+ /**
183
+ * A JSON-serializable representation of a {@link Template} instance, including
184
+ * everything needed to recreate the template.
185
+ */
186
+ interface CompiledTemplate {
187
+ html: string;
188
+ schemes: AnyScheme[];
189
+ notations: Record<string, ParsedNotation>;
275
190
  }
276
191
  type TemplateRoot = HTMLElement | ShadowRoot;
277
192
  type TemplateContext = {
@@ -287,20 +202,33 @@ type TemplateContext = {
287
202
  * Leverages a simple syntax to provide accessors to template data from one of
288
203
  * four categories, based on use case:
289
204
  *
290
- * ```
291
- * `@` -> The `attr` namespace (the host element's attributes)
292
- * `$` -> The `state` namespace (the host element's reactive state)
293
- * `*` -> The `data` namespace (the host element's computed data)
294
- * `#` -> The `item` namespace (list item access with ::each)
295
- * ```
205
+ * | Prefix | Namespace |
206
+ * | ------ | ------------------------------------------------ |
207
+ * | `@` | The `attr` namespace (host element attributes) |
208
+ * | `$` | The `state` namespace (component reactive state) |
209
+ * | `*` | The `data` namespace (component computed props) |
210
+ * | `#` | The `item` namespace (list item context) |
296
211
  *
297
212
  * Bind attributes to a value using the `:attribute` syntax:
298
213
  *
299
214
  * ```html
300
215
  * <div :id="$id" :class="@classname"></div>
301
216
  * ```
217
+ *
218
+ * Or use directives with notations for advanced templating:
219
+ *
220
+ * ```html
221
+ * <div ::if="*showList">
222
+ * <ul>
223
+ * <li ::each="$items" ::key="#id">
224
+ * <span ::swap="#content"></span>
225
+ * </li>
226
+ * </ul>
227
+ * </div>
228
+ * ```
302
229
  */
303
230
  declare class Template {
231
+ static isTemplate(value: unknown): value is Template;
304
232
  /**
305
233
  * Properties related to the global Template build cache.
306
234
  *
@@ -347,7 +275,10 @@ declare class Template {
347
275
  private _plansByNotation;
348
276
  /** Value Notations -> Accessor functions */
349
277
  private _notationAccessors;
278
+ private _subtemplates;
350
279
  constructor(template: HTMLTemplateElement);
280
+ get compiled(): CompiledTemplate;
281
+ get notationMap(): Map<string, NotationAccessor>;
351
282
  /**
352
283
  * Create a full copy of this template for use in rendering to another root
353
284
  */
@@ -358,6 +289,7 @@ declare class Template {
358
289
  element: () => HTMLTemplateElement;
359
290
  private _collectUpdatedValues;
360
291
  private _update;
292
+ renderWithValues: (root: TemplateRoot, values: Map<string, unknown>) => void;
361
293
  /**
362
294
  * Render this template into a target host element.
363
295
  */
@@ -365,16 +297,10 @@ declare class Template {
365
297
  }
366
298
  type EventName = keyof GlobalEventHandlersEventMap;
367
299
  type EventHandler<K extends EventName = EventName> = (event: GlobalEventHandlersEventMap[K]) => void;
368
- interface LiveAttributes<State extends object = {}> {
369
- [name: string]: {
370
- onChange?: (this: Component<State>, current: string | null, previous: string | null) => void;
371
- reflect?: (this: Component<State>) => unknown;
372
- };
300
+ declare enum RootMode {
301
+ Closed = "closed",
302
+ Open = "open"
373
303
  }
374
- type ComputedProperties<StateShape extends object = {}> = Record<string, unknown> | ((this: Component<StateShape>) => Record<string, unknown>);
375
- type ComponentStyles = CSSStyleSheet | CSSStyleSheet[] | string;
376
- type AttributeMap = Map<string, string | null>;
377
- type StateInitializer<Shape extends object = {}> = Shape | State<Shape> | ((this: Component<Shape>) => Shape | State<Shape>);
378
304
  /**
379
305
  * The **Component** class extends the base HTMLElement class to provide an
380
306
  * ergonomic way to design and develop Web Components with reactive state,
@@ -382,181 +308,56 @@ type StateInitializer<Shape extends object = {}> = Shape | State<Shape> | ((this
382
308
  */
383
309
  declare abstract class Component<StateShape extends object = {}> extends HTMLElement {
384
310
  /**
385
- * Register a Component with the browser. Optionally provide an overriding
386
- * tag name.
311
+ * Check if a value is a Component instance
387
312
  */
388
- static register(component: CustomElementConstructor | typeof Component, tagName?: string): void;
313
+ static isComponent(value: unknown): value is Component;
389
314
  /**
390
- * The HTML tag name to use for this Component
315
+ * Check if a value is a Component class
391
316
  */
317
+ static isComponentClass(value: unknown): value is typeof Component;
318
+ static readComponentTagName(componentClass: typeof Component): string;
319
+ /** The HTML tag name to use for this Component */
392
320
  static tagName: string;
393
- /**
394
- * An HTML Template element for this element to use.
395
- */
321
+ /** An HTML Template element for this element to use. */
396
322
  static template: HTMLTemplateElement | Template;
397
- /**
398
- * A {@link CSSStyleSheet} array or string to adopt as the component's scoped styles
399
- */
400
- static styles: ComponentStyles;
401
- /**
402
- * An array of html attribute names to observe to trigger updates.
403
- *
404
- * @remarks
405
- * If you use the {@link define} factory helper, this value will be
406
- * automatically merged with a list of any defined {@link liveAttributes}.
407
- */
323
+ /** Styles to encapsulate to this component's root. */
324
+ static style: CSSStyleSheet | CSSStyleSheet[] | string;
325
+ /** Attributes to watch for changes for updates. */
408
326
  static observedAttributes: string[];
409
327
  /**
410
328
  * The mode for this Component's ShadowRoot.
411
329
  *
412
- * - `"open"` allows JS access to the children of this component
413
- * - `"closed"` (default) disallows internal JS access
330
+ * MDN: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode
414
331
  */
415
- static shadowRootMode: "closed" | "open";
332
+ static rootMode: RootMode;
333
+ /** Called immediately before each template update */
334
+ beforeUpdate?(changedAttributes: Record<string, string | null>): void;
335
+ /** Called immediately after each template update */
336
+ afterUpdate?(previousAttributes: Record<string, string | null>): void;
337
+ /** Final lifecycle method called at the end of the unmounting process. */
338
+ finalize?(): void;
416
339
  /**
417
- * Called immediately before each template update
340
+ * Handle errors thrown during lifecycle methods of this component or any
341
+ * unhandled errors from child components.
418
342
  */
419
- protected beforeUpdate?(): void;
420
- /**
421
- * Called immediately after each template update
422
- */
423
- protected afterUpdate?(previousAttributes: AttributeMap): void;
424
- /**
425
- * Called as the final lifecycle method at the end of the unmounting process.
426
- *
427
- * Content has been removed from the DOM and the component is no longer
428
- * considered mounted when this function runs.
429
- *
430
- * Use this for final cleanup tasks and freeing resources.
431
- */
432
- protected finalize?(): void;
433
- /**
434
- * If defined, receives errors caught during the lifecycle methods of this
435
- * component and it's children. If not defined, this component will instead
436
- * throw errors as they are found.
437
- *
438
- * If the error cannot be handled, `onError()` should re-throw it for a higher
439
- * up component to evaluate.
440
- *
441
- * To display an error message while removing an erroring component from the
442
- * DOM, use `this.replaceWith()` and a newly constructed placeholder element.
443
- *
444
- * @example
445
- *
446
- * Recovering from errors or displaying a fallback
447
- *
448
- * ```typescript
449
- * onError(error) {
450
- * // Handle known errors
451
- * if (error.message = "Invalid Input") {
452
- * this.state.input = "";
453
- * return;
454
- * }
455
- *
456
- * // Display a fallback banner
457
- * const banner = document.createElement("div");
458
- * banner.textContent = "An unexpected error done did";
459
- * this.replaceWith(banner);
460
- * }
461
- * ```
462
- */
463
- protected onError?(error: unknown): void;
464
- /**
465
- * The initial value for this Component's reactive internal state, if any.
466
- *
467
- * If `initialState` is defined as a function, the function is evaluated at
468
- * mounting time and the return value is used as the initial state.
469
- */
470
- protected initialState?: StateInitializer<StateShape>;
343
+ onError?(error: unknown): void;
344
+ /** The initial state for this Component */
345
+ initialState?(): State<StateShape> | StateShape;
471
346
  /**
472
347
  * An object or function returning an object which populates the `data` values
473
348
  * in the templating engine. Such values are accessed using the `*` notation.
474
- *
475
- * When a hosted value is accessed, it's value is coerced based on the way it
476
- * is being used. For attributes, values are serialized to a string or null,
477
- * which will set the attribute (string) or remove it (null).
478
- *
479
- * For content swaps, strings, numbers and booleans are rendered as strings
480
- * and set as textContent. null and undefined values render nothing (with a
481
- * comment placeholder), an HTMLElement will be swapped in as a cloned node.
482
- *
483
- * Functions are evaluated at render/update time, and their return value is
484
- * used instead. Functions are passed the current target node accessing the
485
- * property as its first and only argument.
486
- *
487
- * Other types are unsupported.
488
- *
489
- * @example
490
- *
491
- * ```typescript
492
- * host = {
493
- * avatarUrl: "http://...",
494
- * renderTime: () => new Date().toISOString(),
495
- * }
496
- * ```
497
- *
498
- * ```html
499
- * <img :src="*avatarUrl" />
500
- * <span>Rendered at ${"*renderTime"}</span>
501
- * ```
502
349
  */
503
- protected computedProperties?: ComputedProperties<StateShape>;
504
- /**
505
- * Declaration of attribute behaviors during updates.
506
- *
507
- * Each key is the name of an attribute. Values are an object with two
508
- * optional function definitions:
509
- *
510
- * `onChange(newValue, oldValue): void`, a handler function for when the value
511
- * of this attribute has changed. Runs once per update cycle, *before* the
512
- * update occurs.
513
- *
514
- * `reflect(): unknown`, a function which returns a value to serialize and set
515
- * as this attribute *after* an update completes.
516
- *
517
- * @example
518
- *
519
- * ```typescript
520
- * liveAttributes = {
521
- * done: {
522
- * onChange(newValue) {
523
- * this.state.set("done", !!newValue);
524
- * },
525
- * reflect() { return this.state.current.done }
526
- * }
527
- * };
528
- * ```
529
- *
530
- * @remarks
531
- *
532
- * If using the {@link define} helper, the keys of this property are
533
- * automatically merged with any defined {@link observedAttributes}.
534
- *
535
- * If you are directly extending the class, `observedAttributes` must be
536
- * explicitly defined with any attributes that should be monitored, regardless
537
- * of if they're defined here.
538
- */
539
- protected liveAttributes?: LiveAttributes<StateShape>;
540
- private _isComponentMounted;
541
- private _template;
542
- private _root;
543
- private _state?;
544
- private _delegator?;
545
- private _ownListeners;
546
- private _liveAttributeConfigs;
547
- private _cleanupFn?;
548
- private _renderInternals;
350
+ computedProperties?(this: Component<StateShape>, attributes: Record<string, string | null>, state: StateShape): Record<string, unknown>;
351
+ readonly root: ShadowRoot;
352
+ readonly template: Template;
353
+ private _cmp;
549
354
  constructor();
550
355
  /** Produce the data object to pass to the template's render function */
551
356
  private _getRenderContext;
552
357
  private _escalateError;
553
358
  private _reportError;
554
- private _executeAttributeChangeHandlers;
555
- private _reflectLiveAttributes;
556
359
  /** The actual update behavior */
557
- private _performUpdate;
558
- /** Safely request for an update to run on the next frame */
559
- private _scheduleUpdate;
360
+ private _render;
560
361
  private _adoptState;
561
362
  /**
562
363
  * @deprecated Use {@link script} instead
@@ -567,277 +368,176 @@ declare abstract class Component<StateShape extends object = {}> extends HTMLEle
567
368
  */
568
369
  protected disconnectedCallback(): void;
569
370
  /**
570
- * @deprecated Use {@link liveAttributes} instead
371
+ * @deprecated Use {@link beforeUpdate} and {@link afterUpdate} instead
571
372
  */
572
373
  protected attributeChangedCallback(name: string, _: string | null, newValue: string | null): void;
374
+ get isMounted(): boolean;
573
375
  /**
574
- * The ShadowRoot node of this element
376
+ * Access the full {@link State} instance for this Component.
377
+ *
378
+ * @remarks
379
+ * Calling this method on a component which is not using reactive state
380
+ * will result in an error being thrown.
575
381
  */
576
- get root(): ShadowRoot;
382
+ getState(): State<StateShape>;
577
383
  /**
578
- * Directly access and manipulate this Component's reactive state with deep
579
- * reactivity enabled.
580
- *
581
- * @example
582
- *
583
- * ```typescript
584
- * initialState = { values: { count: 1, time: 0 } };
585
- *
586
- * increment() {
587
- * this.state.values.count = 2;
588
- * }
589
- * ```
384
+ * Accessor for live reactive state.
590
385
  *
591
- * @remarks
592
- * This is a shorthand getter for `this.getState().live`
386
+ * For the full state manipulation API, use {@link getState}
593
387
  */
594
388
  get state(): StateShape;
595
389
  /**
596
390
  * Replace the current state with a new value or adopt a whole different State
597
391
  * instance
598
- *
599
- * @example
600
- *
601
- * State replacement
602
- *
603
- * ```typescript
604
- * // Always triggers a render
605
- * this.state = { some: "newState" }
606
- *
607
- * // Adopt a new state instance altogether
608
- * const otherState = new State({some: "newState"});
609
- * this.state = otherState;
610
- *
611
- * // Causes a render
612
- * otherState.set("some", "value");
613
- * ```
614
392
  */
615
393
  set state(newState: StateShape | State<StateShape>);
616
394
  /**
617
- * Access the full {@link State} instance for this Component.
618
- *
619
- * Using this method on a component which is not using reactive state
620
- * will result in an error being thrown.
621
- *
622
- * @example
623
- *
624
- * Retrieve stateful value
625
- *
626
- * ```typescript
627
- * class Example extends Component {
628
- * initialState = { count: 1 };
629
- *
630
- * script() {
631
- * const state = this.getState();
632
- * console.log(`The count is ${state.get("count")}`);
633
- * }
634
- *
635
- * }
636
- * ```
637
- *
638
- * @example
639
- *
640
- * Set a stateful value
641
- *
642
- * ```typescript
643
- * class Example extends Component {
644
- * initialState = { count: 1 };
645
- *
646
- * increment() {
647
- * const state = this.getState();
648
- * const current = state.get("count");
649
- * state.set("count", current + 1);
650
- * }
651
- * }
652
- * ```
653
- */
654
- getState(): State<StateShape>;
655
- /**
656
- * Manually schedule a render to occur
395
+ * Manually schedule a render to occur, optionally providing a callback to
396
+ * invoke after the render completes.
657
397
  *
658
398
  * @remarks
659
- * Calling {@link requestRender} multiple times in the same event loop won't
399
+ * Calling {@link render} multiple times in the same event loop won't
660
400
  * schedule multiple renders. Renders are scheduled to occur on the next
661
401
  * animation frame.
662
402
  */
663
- requestRender(callback?: VoidFunction): void;
403
+ render: () => void;
664
404
  /**
665
- * Create a managed event listener on this Component with an optional query
666
- * filter string to specify which elements to listen to events from.
405
+ * Listen for events on a child element based on the provided query
667
406
  *
668
- * Event handlers set up with `listen()` are automatically cleaned up when
669
- * the component is unmounted.
407
+ * @returns A function which removes the listener when called
670
408
  *
671
- * Handlers are delegated to a single listener per event on the root of the
672
- * Component, which is the closed ShadowRoot by default.
409
+ * @remarks
410
+ * Event listeners created with this method are automatically cleaned up when
411
+ * the component unmounts. You can manually remove the listener during the
412
+ * component's lifecycle by calling the returned function if need be.
413
+ */
414
+ listen<E extends EventName>(target: string, eventName: E, eventHandler: EventHandler<E>): VoidFunction;
415
+ /**
416
+ * Listen for events on a child element based on the provided component's tag
673
417
  *
674
- * @example
418
+ * @returns A function which removes the listener when called
675
419
  *
676
- * Set up an event listener for a click event from a button with ID "submit"
420
+ * @remarks
421
+ * Event listeners created with this method are automatically cleaned up when
422
+ * the component unmounts. You can manually remove the listener during the
423
+ * component's lifecycle by calling the returned function if need be.
424
+ */
425
+ listen<E extends EventName>(target: typeof Component<any>, eventName: E, eventHandler: EventHandler<E>): VoidFunction;
426
+ /**
427
+ * Listen for events on the host element
677
428
  *
678
- * ```typescript
679
- * this.listen("#submit", "click", this.handleClick);
680
- * ```
429
+ * @returns A function which removes the listener when called
681
430
  *
682
- * @example
431
+ * @remarks
432
+ * Event listeners created with this method are automatically cleaned up when
433
+ * the component unmounts. You can manually remove the listener during the
434
+ * component's lifecycle by calling the returned function if need be.
435
+ */
436
+ listen<E extends EventName>(target: typeof this, eventName: E, eventHandler: EventHandler<E>): VoidFunction;
437
+ /**
438
+ * Listen for events on the host element
683
439
  *
684
- * Set up an event listener on the host element itself (`this`).
440
+ * @returns A function which removes the listener when called
685
441
  *
686
- * ```typescript
687
- * // Use ":host" or ":HOST" as the query selector to targe the host element
688
- * this.listen(":host", "animationend", this.handler);
689
- * ```
442
+ * @remarks
443
+ * Event listeners created with this method are automatically cleaned up when
444
+ * the component unmounts. You can manually remove the listener during the
445
+ * component's lifecycle by calling the returned function if need be.
690
446
  */
691
- listen<E extends EventName>(targetDescriptor: string | typeof Component<any> | HTMLElement, eventName: E, eventHandler: EventHandler<E>): void;
692
- stopListening<E extends EventName>(targetDescriptor: string | typeof Component<any> | HTMLElement, eventName: E, eventHandler: EventHandler<E>): void;
693
- dispatchEvent(event: Event): boolean;
447
+ listen<E extends EventName>(target: ":host", eventName: E, eventHandler: EventHandler<E>): VoidFunction;
694
448
  /**
695
- * Dispatch a {@link CustomEvent} with the specified name and detail
449
+ * Publish a {@link CustomEvent} with the provided name and options.
696
450
  *
697
- * @example Defining custom events
451
+ * Default options are:
698
452
  *
699
453
  * ```typescript
700
- * // Define types. This can happen in the same file or elsewhere for common
701
- * // and shared events.
702
- * type MyEvent = CustomEvent<T> // Set `T` to the event detail type
703
- *
704
- * declare global {
705
- * interface GlobalEventHandlersEventMap {
706
- * "my-event-name": MyEvent
707
- * }
454
+ * {
455
+ * bubbles: true,
456
+ * cancelable: true,
457
+ * composed: true,
458
+ * detail: undefined,
708
459
  * }
709
- *
710
- * // Dispatch a custom event
711
- * this.dispatchEvent("my-event-name", T);
712
- *
713
- * // Listen to a custom event
714
- * $el.addEventListener("my-event-name", ...);
715
460
  * ```
716
- */
717
- dispatchEvent<T>(event: string, detail?: T, options?: {
718
- bubbles: boolean;
719
- cancelable: boolean;
720
- composed: boolean;
721
- }): boolean;
722
- querySelector<E extends HTMLElement>(query: string): E | null;
723
- querySelectorAll<E extends HTMLElement>(query: string): NodeListOf<E>;
724
- getElementById<E extends HTMLElement>(id: string): E | null;
725
- requireElementById<E extends HTMLElement>(id: string): E;
726
- setAttribute(name: string, value: string): void;
727
- /**
728
- * Set a value as an attribute, serializing to a string or `null`.
729
461
  *
730
- * null values cause the attribute to be removed from this element
462
+ * @param name The name of the custom event to publish
463
+ * @param options Options to configure the custom event
731
464
  */
732
- setAttribute(name: string, value: unknown): void;
733
- getAttribute(name: string): string | null;
465
+ dispatchEvent<T>(name: string, options: CustomEventInit<T>): boolean;
466
+ dispatchEvent(event: Event): boolean;
734
467
  /**
735
- * Get an attribute as a number, falling back to a specified default.
468
+ * Returns a reference to the HTML Element with the provided ID within the
469
+ * shadow root of this component.
736
470
  *
737
- *
738
- * `null` attributes are returned as `null`, while string attributes are
739
- * parsed using the `Number()` constructor.
740
- *
741
- * Values which evaluate to NaN are returned as `null`.
742
- */
743
- getAttribute(name: string, defaultValue: number): number;
744
- /**
745
- * Get an attribute as a string, falling back to a specified default
471
+ * Throws an error if no element with the provided ID is found
746
472
  */
747
- getAttribute(name: string, defaultValue: string): string;
748
- /**
749
- * Set the value of an attribute on this element if the provided value
750
- * differs from the attribute when serialized
751
- */
752
- reflectAttribute(name: string, value: unknown): void;
473
+ ref<E extends HTMLElement>(id: string): E;
753
474
  /**
754
475
  * Serialize this element to a string
755
476
  */
756
- toString(full?: boolean): string;
477
+ toString(): string;
757
478
  }
758
479
  interface ComponentShorthand<StateShape extends object = {}> {
759
- /**
760
- * An HTML Template element for this element to use.
761
- */
480
+ /** An HTML Template element for this element to use. */
762
481
  template?: HTMLTemplateElement;
763
- /**
764
- * A {@link CSSStyleSheet} array to adopt to the element's shadow DOM
765
- */
766
- styles?: ComponentStyles;
482
+ /** Styles to encapsulate to this component's root. */
483
+ style?: CSSStyleSheet | CSSStyleSheet[] | string;
767
484
  /**
768
485
  * The mode for this Component's ShadowRoot.
769
486
  *
770
- * - `"open"` allows JS access to the children of this component
771
- * - `"closed"` (default) disallows internal JS access
772
- */
773
- shadowRootMode?: "closed" | "open";
774
- /**
775
- * The initial state for this Component's reactive internal state, if any.
487
+ * MDN: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode
776
488
  */
777
- initialState?: StateInitializer<StateShape>;
489
+ rootMode?: RootMode;
490
+ /** Attributes to watch for changes for updates. */
491
+ observedAttributes?: string[];
492
+ /** The initial state for this Component */
493
+ initialState?: State<StateShape> | StateShape | (() => State<StateShape>) | (() => StateShape);
778
494
  /**
779
495
  * An object or function returning an object which populates the `data` values
780
496
  * in the templating engine. Such values are accessed using the `*` notation.
781
497
  */
782
- computedProperties?: ComputedProperties<StateShape>;
783
- /**
784
- * Declaration of attribute behaviors during updates.
785
- */
786
- liveAttributes?: LiveAttributes<StateShape>;
787
- /**
788
- * Initialization scripting for this Component
789
- */
498
+ computedProperties?(this: Component<StateShape>, attributes?: Record<string, string | null>, state?: StateShape): Record<string, unknown>;
499
+ /** Initialization scripting for this Component */
790
500
  script?: (this: Component<StateShape>) => VoidFunction | void;
791
- /**
792
- * Called immediately before each template update
793
- */
794
- beforeUpdate?: (this: Component<StateShape>) => void;
795
- /**
796
- * Called immediately after each template update
797
- */
798
- afterUpdate?: (this: Component<StateShape>, previousAttributes: AttributeMap) => void;
501
+ /** Called immediately before each template update */
502
+ beforeUpdate?: (this: Component<StateShape>, changedAttributes?: Record<string, string | null>) => void;
503
+ /** Called immediately after each template update */
504
+ afterUpdate?: (this: Component<StateShape>, previousAttributes?: Record<string, string | null>) => void;
799
505
  /**
800
506
  * Called as the final lifecycle method at the end of the unmounting process.
801
- *
802
- * Content has been removed from the DOM and the component is no longer
803
- * considered mounted when this function runs.
804
- *
805
- * Use this for final cleanup tasks and freeing resources.
806
507
  */
807
508
  finalize?: (this: Component<StateShape>) => void;
808
509
  /**
809
510
  * If defined, receives errors caught during the lifecycle methods of this
810
511
  * component and it's children. If not defined, this component will instead
811
512
  * throw errors as they are found.
812
- *
813
- * If the error cannot be handled, `onError()` should re-throw it for a higher
814
- * up component to evaluate.
815
- *
816
- * To display an error message while removing an erroring component from the
817
- * DOM, use `this.replaceWith()` and a newly constructed placeholder element.
818
- *
819
- * @example
820
- *
821
- * Recovering from errors or displaying a fallback
822
- *
823
- * ```typescript
824
- * onError(error) {
825
- * // Handle known errors
826
- * if (error.message = "Invalid Input") {
827
- * this.state.input = "";
828
- * return;
829
- * }
830
- *
831
- * // Display a fallback banner
832
- * const banner = document.createElement("div");
833
- * banner.textContent = "An unexpected error done did";
834
- * this.replaceWith(banner);
835
- * }
836
- * ```
837
513
  */
838
514
  onError?: (this: Component<StateShape>, error: unknown) => void;
839
515
  }
840
- declare function define<StateShape extends object = {}>(tagname: string, definition: ComponentShorthand<StateShape>): typeof Component<StateShape>;
516
+ /**
517
+ * Define a Palette Component using the shorthand syntax
518
+ *
519
+ * @example
520
+ *
521
+ * ```typescript
522
+ * define("my-component", {
523
+ * template: html`...`,
524
+ * style: css`...`,
525
+ * initialState: { ... },
526
+ * computedProperties() { ... },
527
+ * script() { ... },
528
+ * // and any other lifecycle methods
529
+ * });
530
+ * ```
531
+ */
532
+ declare function define<StateShape extends object = {}>(tagName: string, definition: ComponentShorthand<StateShape>): typeof Component<StateShape>;
533
+ /**
534
+ * Register a Palette Component as a custom element
535
+ */
536
+ declare function define<Cls extends typeof Component<any>>(definition: Cls): Cls;
537
+ /**
538
+ * Register a Palette Component as a custom element
539
+ */
540
+ declare function define<Cls extends typeof Component<any>>(tagName: string, definition: Cls): Cls;
841
541
  /**
842
542
  * An error originating from within a Palette subsystem.
843
543
  *
@@ -862,10 +562,8 @@ declare class PaletteError extends Error {
862
562
  /**
863
563
  * @returns An array of {@link CSSStyleSheet}s containing the provided CSS
864
564
  */
865
- declare function css(strings: TemplateStringsArray, ...values: unknown[]): CSSStyleSheet;
866
- type ClassValue = string | number | boolean | undefined | null | ClassArray | ClassObject;
867
- type ClassArray = ClassValue[];
868
- type ClassObject = Record<string, any>;
565
+ declare function css(strings: TemplateStringsArray, ...values: (string | typeof Component<any>)[]): CSSStyleSheet;
566
+ type ClassifyInput = string | number | boolean | undefined | null | Record<string, any> | ClassifyInput[];
869
567
  /**
870
568
  * Given flexible input, generate a string suitable for setting as an html class
871
569
  *
@@ -877,7 +575,7 @@ type ClassObject = Record<string, any>;
877
575
  * classify({ enabled: true, disabled: false }) // "enabled"
878
576
  * ```
879
577
  */
880
- declare function classify(...args: ClassValue[]): string;
578
+ declare function classify(...args: ClassifyInput[]): string;
881
579
  /**
882
580
  * Create an {@link HTMLTemplateElement} from the provided HTML string.
883
581
  *
@@ -914,4 +612,4 @@ declare function classify(...args: ClassValue[]): string;
914
612
  * ```
915
613
  */
916
614
  declare function html(strings: TemplateStringsArray, ...values: (`@${string}` | `$${string}` | `#${string}` | `*${string}` | HTMLTemplateElement | typeof Component<any>)[]): HTMLTemplateElement;
917
- export { html, define, css, classify, TemplateRoot, TemplateContext, Template, StateListener, StateInitializer, State, PaletteError, LiveAttributes, EventName, EventHandler, ComputedProperties, ComponentStyles, ComponentShorthand, Component, AttributeMap };
615
+ export { html, define, css, classify, TemplateRoot, TemplateContext, Template, StateListener, State, RootMode, PaletteError, EventName, EventHandler, ComponentShorthand, Component, ClassifyInput };