@furystack/shades 15.0.0 → 15.0.2

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 (60) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/esm/compile-route.d.ts +5 -0
  3. package/esm/compile-route.d.ts.map +1 -1
  4. package/esm/compile-route.js +5 -0
  5. package/esm/compile-route.js.map +1 -1
  6. package/esm/components/nested-router.js.map +1 -1
  7. package/esm/css-generator.d.ts +9 -26
  8. package/esm/css-generator.d.ts.map +1 -1
  9. package/esm/css-generator.js +9 -26
  10. package/esm/css-generator.js.map +1 -1
  11. package/esm/css-generator.spec.js.map +1 -1
  12. package/esm/models/css-object.d.ts +3 -5
  13. package/esm/models/css-object.d.ts.map +1 -1
  14. package/esm/models/render-options.d.ts +16 -20
  15. package/esm/models/render-options.d.ts.map +1 -1
  16. package/esm/models/shade-component.d.ts +6 -4
  17. package/esm/models/shade-component.d.ts.map +1 -1
  18. package/esm/models/shade-component.js +3 -3
  19. package/esm/models/shade-component.js.map +1 -1
  20. package/esm/services/route-meta-utils.js.map +1 -1
  21. package/esm/services/screen-service.spec.js.map +1 -1
  22. package/esm/shade-component.d.ts +14 -17
  23. package/esm/shade-component.d.ts.map +1 -1
  24. package/esm/shade-component.js +14 -17
  25. package/esm/shade-component.js.map +1 -1
  26. package/esm/shade.d.ts +17 -14
  27. package/esm/shade.d.ts.map +1 -1
  28. package/esm/shade.js +12 -25
  29. package/esm/shade.js.map +1 -1
  30. package/esm/style-manager.d.ts +8 -22
  31. package/esm/style-manager.d.ts.map +1 -1
  32. package/esm/style-manager.js +8 -22
  33. package/esm/style-manager.js.map +1 -1
  34. package/esm/styled-shade.d.ts +4 -7
  35. package/esm/styled-shade.d.ts.map +1 -1
  36. package/esm/styled-shade.js +4 -4
  37. package/esm/styled-shade.js.map +1 -1
  38. package/esm/view-transition.d.ts +10 -22
  39. package/esm/view-transition.d.ts.map +1 -1
  40. package/esm/view-transition.js +10 -22
  41. package/esm/view-transition.js.map +1 -1
  42. package/esm/vnode.js.map +1 -1
  43. package/esm/vnode.spec.js.map +1 -1
  44. package/package.json +10 -10
  45. package/src/compile-route.ts +5 -0
  46. package/src/components/nested-router.tsx +2 -2
  47. package/src/css-generator.spec.ts +1 -1
  48. package/src/css-generator.ts +9 -26
  49. package/src/models/css-object.ts +3 -5
  50. package/src/models/render-options.ts +17 -20
  51. package/src/models/shade-component.ts +6 -4
  52. package/src/services/route-meta-utils.ts +1 -1
  53. package/src/services/screen-service.spec.ts +2 -2
  54. package/src/shade-component.ts +14 -17
  55. package/src/shade.ts +20 -33
  56. package/src/style-manager.ts +9 -22
  57. package/src/styled-shade.ts +5 -8
  58. package/src/view-transition.ts +10 -22
  59. package/src/vnode.spec.ts +3 -3
  60. package/src/vnode.ts +2 -2
package/src/shade.ts CHANGED
@@ -19,18 +19,18 @@ export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
19
19
  */
20
20
  customElementName: string
21
21
 
22
- /**
23
- * Render hook, this method will be executed on each and every render.
24
- */
25
22
  render: (options: RenderOptions<TProps, TElementBase>) => JSX.Element | string | null
26
23
 
27
24
  /**
28
- * Name of the HTML Element's base class. Needs to be defined if the elementBase is set. E.g.: 'div', 'button', 'input'
25
+ * Tag name of the base built-in element when extending one (e.g. `'a'`,
26
+ * `'button'`, `'input'`). Required when {@link ShadeOptions.elementBase}
27
+ * is set; passed to `customElements.define` as `{ extends }`.
29
28
  */
30
29
  elementBaseName?: string
31
30
 
32
31
  /**
33
- * Base class for the custom element. Defaults to HTMLElement. E.g. HTMLButtonElement
32
+ * Constructor of the built-in element to extend (e.g. `HTMLButtonElement`).
33
+ * Defaults to `HTMLElement`. Pair with {@link ShadeOptions.elementBaseName}.
34
34
  */
35
35
  elementBase?: Constructable<TElementBase>
36
36
 
@@ -61,9 +61,14 @@ export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
61
61
  }
62
62
 
63
63
  /**
64
- * Factory method for creating Shade components
65
- * @param o The options object for component creation
66
- * @returns the JSX element
64
+ * Defines and registers a Shade component as a custom element. Returns a
65
+ * JSX-callable factory `(props, children?) => JSX.Element`. Throws when a
66
+ * component with the same {@link ShadeOptions.customElementName} has
67
+ * already been registered — registration is global and process-wide.
68
+ *
69
+ * The returned factory is the entry point for downstream code. The custom
70
+ * element class itself is installed via `customElements.define` and never
71
+ * directly exposed.
67
72
  */
68
73
  export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
69
74
  o: ShadeOptions<TProps, TElementBase>,
@@ -84,9 +89,6 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
84
89
  class extends (ElementBase as Constructable<HTMLElement>) implements JSX.Element {
85
90
  private _renderCount = 0
86
91
 
87
- /**
88
- * @returns the current render count
89
- */
90
92
  public getRenderCount() {
91
93
  return this._renderCount
92
94
  }
@@ -128,28 +130,15 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
128
130
  await this.resourceManager[Symbol.asyncDispose]()
129
131
  }
130
132
 
131
- /**
132
- * Will be triggered when updating the external props object
133
- */
134
133
  public props!: TProps & { children?: JSX.Element[] } & PartialElement<TElementBase>
135
134
 
136
- /**
137
- * Will be updated when on children change
138
- */
139
135
  public shadeChildren?: ChildrenList
140
136
 
141
- /**
142
- * @param options Options for rendering the component
143
- * @returns the JSX element
144
- */
145
137
  public render = (options: RenderOptions<TProps, TElementBase>) => {
146
138
  this._renderCount++
147
139
  return o.render(options)
148
140
  }
149
141
 
150
- /**
151
- * @returns values for the current render options
152
- */
153
142
  private getRenderOptions = (): RenderOptions<TProps, TElementBase> => {
154
143
  const renderOptions: RenderOptions<TProps, TElementBase> = {
155
144
  props: this.props,
@@ -163,8 +152,8 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
163
152
  const existingRef = this._refs.get(key) as RefObject<T> | undefined
164
153
  if (existingRef) return existingRef
165
154
  const refObject = { current: null } as { current: T | null }
166
- this._refs.set(key, refObject as unknown as RefObject<Element>)
167
- return refObject as RefObject<T>
155
+ this._refs.set(key, refObject)
156
+ return refObject
168
157
  },
169
158
  useObservable: (key, observable, options) => {
170
159
  const onChange = options?.onChange || (() => this.updateComponent())
@@ -308,7 +297,7 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
308
297
  this.removeAttribute(key)
309
298
  }
310
299
  if (this._prevHostProps.style) {
311
- for (const sk of Object.keys(this._prevHostProps.style as Record<string, string>)) {
300
+ for (const sk of Object.keys(this._prevHostProps.style)) {
312
301
  if (sk.startsWith('--')) {
313
302
  this.style.removeProperty(sk)
314
303
  } else {
@@ -464,11 +453,9 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
464
453
  }
465
454
 
466
455
  /**
467
- * Flushes any pending microtask-based component updates.
468
- * Useful in tests to wait for batched renders to complete before asserting DOM state.
469
- *
470
- * Child component updates during reconciliation are performed synchronously, so a single
471
- * `await flushUpdates()` is sufficient to settle the entire component tree after a state change.
472
- * @returns a promise that resolves after the current microtask queue has been processed
456
+ * Awaits the next microtask tick — long enough for `updateComponent`'s
457
+ * batching microtask to drain. A single `await flushUpdates()` settles the
458
+ * entire component tree because child reconciliation is synchronous within
459
+ * the parent's render. Use in tests before asserting on DOM state.
473
460
  */
474
461
  export const flushUpdates = (): Promise<void> => new Promise<void>((resolve) => queueMicrotask(resolve))
@@ -1,19 +1,16 @@
1
1
  import { generateCSS } from './css-generator.js'
2
2
  import type { CSSObject } from './models/css-object.js'
3
+ import type { Shade } from './shade.js'
3
4
 
4
5
  /**
5
- * Singleton that manages component CSS injection.
6
- * Creates and maintains a shared `<style>` element in the document head,
7
- * and tracks registered component styles to avoid duplicates.
6
+ * Owns a shared `<style data-shades-styles>` element in `document.head`
7
+ * and dedupes component-style registration. Internal — consumers use
8
+ * the {@link StyleManager} singleton.
8
9
  */
9
10
  class StyleManagerClass {
10
11
  private styleElement: HTMLStyleElement | null = null
11
12
  private registeredComponents = new Set<string>()
12
13
 
13
- /**
14
- * Gets or creates the shared style element
15
- * @returns The style element for CSS injection
16
- */
17
14
  private getStyleElement(): HTMLStyleElement {
18
15
  if (!this.styleElement) {
19
16
  this.styleElement = document.createElement('style')
@@ -66,26 +63,16 @@ class StyleManagerClass {
66
63
  return false
67
64
  }
68
65
 
69
- /**
70
- * Checks if a component's styles have already been registered
71
- * @param customElementName - The component identifier to check
72
- * @returns True if styles are already registered
73
- */
74
66
  public isRegistered(customElementName: string): boolean {
75
67
  return this.registeredComponents.has(customElementName)
76
68
  }
77
69
 
78
- /**
79
- * Gets all registered component names (for debugging/testing)
80
- * @returns Set of registered component names
81
- */
70
+ /** Snapshot of registered component names. Intended for diagnostics + tests. */
82
71
  public getRegisteredComponents(): ReadonlySet<string> {
83
72
  return this.registeredComponents
84
73
  }
85
74
 
86
- /**
87
- * Clears all registered styles (useful for testing)
88
- */
75
+ /** Removes the shared style element and clears the registration set. Test-only. */
89
76
  public clear(): void {
90
77
  this.registeredComponents.clear()
91
78
  if (this.styleElement) {
@@ -97,8 +84,8 @@ class StyleManagerClass {
97
84
  }
98
85
 
99
86
  /**
100
- * Singleton instance for managing component CSS styles.
101
- * Use this to register component-level styles that support
102
- * pseudo-selectors and nested selectors.
87
+ * Process-wide CSS injection registry for Shade components. The {@link Shade}
88
+ * factory calls `registerComponentStyles` automatically; direct use is rare
89
+ * (e.g. injecting a third-party stylesheet keyed off a component name).
103
90
  */
104
91
  export const StyleManager = new StyleManagerClass()
@@ -1,17 +1,14 @@
1
1
  import type { ChildrenList } from './models/children-list.js'
2
2
 
3
- /**
4
- * Props constraint for styled components - must have an optional style property
5
- */
6
3
  type StyledProps = {
7
4
  style?: Partial<CSSStyleDeclaration>
8
5
  }
9
6
 
10
7
  /**
11
- * Creates a shortcut for a specific custom Shade element with additional styles
12
- * @param element The element instance
13
- * @param styles The additional styles to add
14
- * @returns The updated element
8
+ * Wraps a Shade factory with a baked-in `style` overlay. Baked-in `styles`
9
+ * win over caller-supplied `style` props (the wrapper merges last). Use to
10
+ * specialise an existing Shade (e.g. theme variants) without registering
11
+ * a new custom element.
15
12
  */
16
13
  export const styledShade = <TProps extends StyledProps>(
17
14
  element: (props: TProps, children?: ChildrenList) => JSX.Element,
@@ -25,7 +22,7 @@ export const styledShade = <TProps extends StyledProps>(
25
22
  ...props.style,
26
23
  ...styles,
27
24
  },
28
- } as TProps,
25
+ },
29
26
  childrenList,
30
27
  )
31
28
  }
@@ -8,16 +8,13 @@ export type ViewTransitionConfig = {
8
8
  }
9
9
 
10
10
  /**
11
- * Wraps a DOM update in `document.startViewTransition()` when the View Transition API
12
- * is available and `config` is truthy. Falls back to calling `update()` directly otherwise.
11
+ * Runs `update` inside `document.startViewTransition()` when the API is
12
+ * available and `config` is truthy; otherwise calls `update` synchronously.
13
13
  *
14
- * Returns the `updateCallbackDone` promise when a transition is started, allowing callers
15
- * that need to wait for the DOM update (e.g. to run lifecycle hooks) to `await` the result.
16
- * Returns `undefined` when no transition is used (the update runs synchronously).
17
- *
18
- * @param config - The view transition configuration (boolean or object with types)
19
- * @param update - The synchronous DOM update callback
20
- * @returns A promise that resolves after the update callback completes inside the transition, or `undefined`
14
+ * The returned promise (when a transition starts) resolves once the update
15
+ * callback completes inside the transition — callers that need to chain
16
+ * lifecycle work after the DOM swap can `await` it. Returns `undefined`
17
+ * when no transition is used.
21
18
  */
22
19
  export const maybeViewTransition = (
23
20
  config: boolean | ViewTransitionConfig | undefined,
@@ -33,19 +30,10 @@ export const maybeViewTransition = (
33
30
  }
34
31
 
35
32
  /**
36
- * Keeps a "displayed" copy of `value` that is updated through a view transition
37
- * whenever the value changes and `shouldTransition` returns true.
38
- *
39
- * When the transition is skipped (config is falsy or `shouldTransition` returns false),
40
- * the displayed value is updated synchronously without an animation.
41
- *
42
- * @param useState - The component's `useState` hook
43
- * @param key - Unique state key for caching the displayed value
44
- * @param value - The latest source value (e.g. from `useObservable` or derived from props)
45
- * @param config - View transition configuration forwarded to `maybeViewTransition`
46
- * @param shouldTransition - Predicate that decides whether a value change warrants a transition.
47
- * Defaults to `() => true` (always transition).
48
- * @returns The currently displayed value
33
+ * Decouples a component's "displayed" value from its source. When `value`
34
+ * changes and `shouldTransition(prev, next)` returns true, the swap is
35
+ * routed through {@link maybeViewTransition}; otherwise the displayed
36
+ * value updates synchronously. `shouldTransition` defaults to always-true.
49
37
  */
50
38
  export const transitionedValue = <T>(
51
39
  useState: <S>(key: string, initialValue: S) => [S, (v: S) => void],
package/src/vnode.spec.ts CHANGED
@@ -545,7 +545,7 @@ describe('vnode', () => {
545
545
  ;(fakeShadeEl as unknown as Record<string, unknown>).props = { count: 1 }
546
546
  ;(fakeShadeEl as unknown as Record<string, unknown>).shadeChildren = undefined
547
547
 
548
- const factory = vi.fn(() => fakeShadeEl as unknown as JSX.Element)
548
+ const factory = vi.fn(() => fakeShadeEl)
549
549
 
550
550
  const old: VChild[] = [{ _brand: 'vnode', type: factory, props: { count: 1 }, children: [], _el: fakeShadeEl }]
551
551
  parent.appendChild(fakeShadeEl)
@@ -566,7 +566,7 @@ describe('vnode', () => {
566
566
  ;(fakeShadeEl as unknown as Record<string, unknown>).props = { elevation: 2 }
567
567
  ;(fakeShadeEl as unknown as Record<string, unknown>).shadeChildren = undefined
568
568
 
569
- const factory = vi.fn(() => fakeShadeEl as unknown as JSX.Element)
569
+ const factory = vi.fn(() => fakeShadeEl)
570
570
 
571
571
  const old: VChild[] = [
572
572
  { _brand: 'vnode', type: factory, props: { elevation: 2 }, children: [], _el: fakeShadeEl },
@@ -591,7 +591,7 @@ describe('vnode', () => {
591
591
  ;(fakeShadeEl as unknown as Record<string, unknown>).props = props
592
592
  ;(fakeShadeEl as unknown as Record<string, unknown>).shadeChildren = undefined
593
593
 
594
- const factory = vi.fn(() => fakeShadeEl as unknown as JSX.Element)
594
+ const factory = vi.fn(() => fakeShadeEl)
595
595
 
596
596
  const old: VChild[] = [{ _brand: 'vnode', type: factory, props: { count: 1 }, children: [], _el: fakeShadeEl }]
597
597
  parent.appendChild(fakeShadeEl)
package/src/vnode.ts CHANGED
@@ -197,9 +197,9 @@ export const toVChildArray = (renderResult: unknown): VChild[] => {
197
197
  // Real DOM element (from async code that ran outside renderMode)
198
198
  if (renderResult instanceof DocumentFragment) {
199
199
  return Array.from(renderResult.childNodes).map((node) => ({
200
- _brand: VNODE_BRAND as typeof VNODE_BRAND,
200
+ _brand: VNODE_BRAND,
201
201
  type: EXISTING_NODE,
202
- props: {} as Record<string, unknown>,
202
+ props: {},
203
203
  children: [] as VChild[],
204
204
  _el: node,
205
205
  }))