@furystack/shades 11.0.35 → 12.0.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.
- package/CHANGELOG.md +337 -0
- package/README.md +99 -13
- package/esm/compile-route.spec.d.ts +2 -0
- package/esm/compile-route.spec.d.ts.map +1 -0
- package/esm/compile-route.spec.js +34 -0
- package/esm/compile-route.spec.js.map +1 -0
- package/esm/component-factory.spec.js +13 -5
- package/esm/component-factory.spec.js.map +1 -1
- package/esm/components/index.d.ts +4 -1
- package/esm/components/index.d.ts.map +1 -1
- package/esm/components/index.js +4 -1
- package/esm/components/index.js.map +1 -1
- package/esm/components/lazy-load.d.ts +2 -4
- package/esm/components/lazy-load.d.ts.map +1 -1
- package/esm/components/lazy-load.js +40 -24
- package/esm/components/lazy-load.js.map +1 -1
- package/esm/components/lazy-load.spec.js +57 -50
- package/esm/components/lazy-load.spec.js.map +1 -1
- package/esm/components/link-to-route.d.ts +2 -0
- package/esm/components/link-to-route.d.ts.map +1 -1
- package/esm/components/link-to-route.js +3 -2
- package/esm/components/link-to-route.js.map +1 -1
- package/esm/components/link-to-route.spec.js +13 -9
- package/esm/components/link-to-route.spec.js.map +1 -1
- package/esm/components/nested-route-link.d.ts +62 -0
- package/esm/components/nested-route-link.d.ts.map +1 -0
- package/esm/components/nested-route-link.js +66 -0
- package/esm/components/nested-route-link.js.map +1 -0
- package/esm/components/nested-route-link.spec.d.ts +2 -0
- package/esm/components/nested-route-link.spec.d.ts.map +1 -0
- package/esm/components/nested-route-link.spec.js +179 -0
- package/esm/components/nested-route-link.spec.js.map +1 -0
- package/esm/components/nested-route-types.d.ts +37 -0
- package/esm/components/nested-route-types.d.ts.map +1 -0
- package/esm/components/nested-route-types.js +2 -0
- package/esm/components/nested-route-types.js.map +1 -0
- package/esm/components/nested-router.d.ts +103 -0
- package/esm/components/nested-router.d.ts.map +1 -0
- package/esm/components/nested-router.js +178 -0
- package/esm/components/nested-router.js.map +1 -0
- package/esm/components/nested-router.spec.d.ts +2 -0
- package/esm/components/nested-router.spec.d.ts.map +1 -0
- package/esm/components/nested-router.spec.js +659 -0
- package/esm/components/nested-router.spec.js.map +1 -0
- package/esm/components/route-link.d.ts +4 -0
- package/esm/components/route-link.d.ts.map +1 -1
- package/esm/components/route-link.js +9 -10
- package/esm/components/route-link.js.map +1 -1
- package/esm/components/route-link.spec.js +16 -12
- package/esm/components/route-link.spec.js.map +1 -1
- package/esm/components/router.d.ts +20 -2
- package/esm/components/router.d.ts.map +1 -1
- package/esm/components/router.js +3 -0
- package/esm/components/router.js.map +1 -1
- package/esm/components/router.spec.js +75 -74
- package/esm/components/router.spec.js.map +1 -1
- package/esm/css-generator.d.ts +50 -0
- package/esm/css-generator.d.ts.map +1 -0
- package/esm/css-generator.js +107 -0
- package/esm/css-generator.js.map +1 -0
- package/esm/css-generator.spec.d.ts +2 -0
- package/esm/css-generator.spec.d.ts.map +1 -0
- package/esm/css-generator.spec.js +162 -0
- package/esm/css-generator.spec.js.map +1 -0
- package/esm/index.d.ts +2 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -0
- package/esm/index.js.map +1 -1
- package/esm/initialize.d.ts +11 -0
- package/esm/initialize.d.ts.map +1 -1
- package/esm/initialize.js +5 -0
- package/esm/initialize.js.map +1 -1
- package/esm/jsx.d.ts +83 -2
- package/esm/jsx.d.ts.map +1 -1
- package/esm/models/children-list.d.ts +5 -1
- package/esm/models/children-list.d.ts.map +1 -1
- package/esm/models/css-object.d.ts +33 -0
- package/esm/models/css-object.d.ts.map +1 -0
- package/esm/models/css-object.js +2 -0
- package/esm/models/css-object.js.map +1 -0
- package/esm/models/index.d.ts +1 -0
- package/esm/models/index.d.ts.map +1 -1
- package/esm/models/index.js +1 -0
- package/esm/models/index.js.map +1 -1
- package/esm/models/partial-element.d.ts +12 -2
- package/esm/models/partial-element.d.ts.map +1 -1
- package/esm/models/render-options.d.ts +89 -3
- package/esm/models/render-options.d.ts.map +1 -1
- package/esm/models/selection-state.d.ts +4 -0
- package/esm/models/selection-state.d.ts.map +1 -1
- package/esm/services/location-service.d.ts +11 -0
- package/esm/services/location-service.d.ts.map +1 -1
- package/esm/services/location-service.js +11 -0
- package/esm/services/location-service.js.map +1 -1
- package/esm/services/resource-manager.d.ts +24 -0
- package/esm/services/resource-manager.d.ts.map +1 -1
- package/esm/services/resource-manager.js +30 -0
- package/esm/services/resource-manager.js.map +1 -1
- package/esm/services/resource-manager.spec.js +93 -0
- package/esm/services/resource-manager.spec.js.map +1 -1
- package/esm/services/screen-service.d.ts +81 -4
- package/esm/services/screen-service.d.ts.map +1 -1
- package/esm/services/screen-service.js +75 -4
- package/esm/services/screen-service.js.map +1 -1
- package/esm/services/screen-service.spec.js +91 -7
- package/esm/services/screen-service.spec.js.map +1 -1
- package/esm/shade-component.d.ts +17 -4
- package/esm/shade-component.d.ts.map +1 -1
- package/esm/shade-component.js +67 -5
- package/esm/shade-component.js.map +1 -1
- package/esm/shade-host-props-ref.integration.spec.d.ts +2 -0
- package/esm/shade-host-props-ref.integration.spec.d.ts.map +1 -0
- package/esm/shade-host-props-ref.integration.spec.js +381 -0
- package/esm/shade-host-props-ref.integration.spec.js.map +1 -0
- package/esm/shade-resources.integration.spec.js +208 -39
- package/esm/shade-resources.integration.spec.js.map +1 -1
- package/esm/shade.d.ts +34 -15
- package/esm/shade.d.ts.map +1 -1
- package/esm/shade.js +180 -33
- package/esm/shade.js.map +1 -1
- package/esm/shade.spec.d.ts +2 -0
- package/esm/shade.spec.d.ts.map +1 -0
- package/esm/shade.spec.js +198 -0
- package/esm/shade.spec.js.map +1 -0
- package/esm/shades.integration.spec.js +135 -72
- package/esm/shades.integration.spec.js.map +1 -1
- package/esm/style-manager.d.ts +65 -0
- package/esm/style-manager.d.ts.map +1 -0
- package/esm/style-manager.js +95 -0
- package/esm/style-manager.js.map +1 -0
- package/esm/style-manager.spec.d.ts +2 -0
- package/esm/style-manager.spec.d.ts.map +1 -0
- package/esm/style-manager.spec.js +179 -0
- package/esm/style-manager.spec.js.map +1 -0
- package/esm/styled-element.spec.d.ts +2 -0
- package/esm/styled-element.spec.d.ts.map +1 -0
- package/esm/styled-element.spec.js +86 -0
- package/esm/styled-element.spec.js.map +1 -0
- package/esm/styled-shade.spec.d.ts +2 -0
- package/esm/styled-shade.spec.d.ts.map +1 -0
- package/esm/styled-shade.spec.js +66 -0
- package/esm/styled-shade.spec.js.map +1 -0
- package/esm/svg-types.d.ts +389 -0
- package/esm/svg-types.d.ts.map +1 -0
- package/esm/svg-types.js +9 -0
- package/esm/svg-types.js.map +1 -0
- package/esm/svg.d.ts +15 -0
- package/esm/svg.d.ts.map +1 -0
- package/esm/svg.js +76 -0
- package/esm/svg.js.map +1 -0
- package/esm/svg.spec.d.ts +2 -0
- package/esm/svg.spec.d.ts.map +1 -0
- package/esm/svg.spec.js +80 -0
- package/esm/svg.spec.js.map +1 -0
- package/esm/vnode.d.ts +103 -0
- package/esm/vnode.d.ts.map +1 -0
- package/esm/vnode.integration.spec.d.ts +2 -0
- package/esm/vnode.integration.spec.d.ts.map +1 -0
- package/esm/vnode.integration.spec.js +494 -0
- package/esm/vnode.integration.spec.js.map +1 -0
- package/esm/vnode.js +453 -0
- package/esm/vnode.js.map +1 -0
- package/esm/vnode.spec.d.ts +2 -0
- package/esm/vnode.spec.d.ts.map +1 -0
- package/esm/vnode.spec.js +473 -0
- package/esm/vnode.spec.js.map +1 -0
- package/package.json +3 -3
- package/src/compile-route.spec.ts +39 -0
- package/src/component-factory.spec.tsx +18 -5
- package/src/components/index.ts +4 -1
- package/src/components/lazy-load.spec.tsx +82 -75
- package/src/components/lazy-load.tsx +49 -27
- package/src/components/link-to-route.spec.tsx +25 -21
- package/src/components/link-to-route.tsx +4 -2
- package/src/components/nested-route-link.spec.tsx +303 -0
- package/src/components/nested-route-link.tsx +100 -0
- package/src/components/nested-route-types.ts +42 -0
- package/src/components/nested-router.spec.tsx +817 -0
- package/src/components/nested-router.tsx +256 -0
- package/src/components/route-link.spec.tsx +22 -18
- package/src/components/route-link.tsx +10 -10
- package/src/components/router.spec.tsx +109 -108
- package/src/components/router.tsx +15 -2
- package/src/css-generator.spec.ts +183 -0
- package/src/css-generator.ts +117 -0
- package/src/index.ts +2 -0
- package/src/initialize.ts +12 -0
- package/src/jsx.ts +129 -2
- package/src/models/children-list.ts +7 -1
- package/src/models/css-object.ts +34 -0
- package/src/models/index.ts +1 -0
- package/src/models/partial-element.ts +13 -2
- package/src/models/render-options.ts +90 -3
- package/src/models/selection-state.ts +4 -0
- package/src/services/location-service.tsx +11 -0
- package/src/services/resource-manager.spec.ts +116 -0
- package/src/services/resource-manager.ts +30 -0
- package/src/services/screen-service.spec.ts +109 -7
- package/src/services/screen-service.ts +81 -4
- package/src/shade-component.ts +72 -6
- package/src/shade-host-props-ref.integration.spec.tsx +460 -0
- package/src/shade-resources.integration.spec.tsx +276 -52
- package/src/shade.spec.tsx +239 -0
- package/src/shade.ts +211 -56
- package/src/shades.integration.spec.tsx +154 -80
- package/src/style-manager.spec.ts +229 -0
- package/src/style-manager.ts +104 -0
- package/src/styled-element.spec.tsx +117 -0
- package/src/styled-shade.spec.ts +86 -0
- package/src/svg-types.ts +437 -0
- package/src/svg.spec.ts +89 -0
- package/src/svg.ts +78 -0
- package/src/vnode.integration.spec.tsx +657 -0
- package/src/vnode.spec.ts +579 -0
- package/src/vnode.ts +508 -0
package/src/shade.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import type { Constructable } from '@furystack/inject'
|
|
2
2
|
import { hasInjectorReference, Injector } from '@furystack/inject'
|
|
3
3
|
import { ObservableValue } from '@furystack/utils'
|
|
4
|
-
import type { ChildrenList, PartialElement, RenderOptions } from './models/index.js'
|
|
4
|
+
import type { ChildrenList, CSSObject, PartialElement, RenderOptions } from './models/index.js'
|
|
5
|
+
import type { RefObject } from './models/render-options.js'
|
|
5
6
|
import { LocationService } from './services/location-service.js'
|
|
6
7
|
import { ResourceManager } from './services/resource-manager.js'
|
|
7
|
-
import { attachProps, attachStyles } from './shade-component.js'
|
|
8
|
+
import { attachProps, attachStyles, setRenderMode } from './shade-component.js'
|
|
9
|
+
import { StyleManager } from './style-manager.js'
|
|
10
|
+
import type { VChild } from './vnode.js'
|
|
11
|
+
import { patchChildren, toVChildArray } from './vnode.js'
|
|
8
12
|
|
|
9
13
|
export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
|
|
10
14
|
/**
|
|
11
|
-
*
|
|
15
|
+
* The custom element tag name used to register the component.
|
|
16
|
+
* Must follow the Custom Elements naming convention (lowercase, must contain a hyphen).
|
|
17
|
+
*
|
|
18
|
+
* @example 'my-button', 'shade-dialog', 'app-header'
|
|
12
19
|
*/
|
|
13
20
|
shadowDomName: string
|
|
14
21
|
|
|
@@ -17,23 +24,6 @@ export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
|
|
|
17
24
|
*/
|
|
18
25
|
render: (options: RenderOptions<TProps, TElementBase>) => JSX.Element | string | null
|
|
19
26
|
|
|
20
|
-
/**
|
|
21
|
-
* Construct hook. Will be executed once when the element has been constructed and initialized
|
|
22
|
-
*/
|
|
23
|
-
constructed?: (
|
|
24
|
-
options: RenderOptions<TProps, TElementBase>,
|
|
25
|
-
) => void | undefined | (() => void) | Promise<void | undefined | (() => void)>
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Will be executed when the element is attached to the DOM.
|
|
29
|
-
*/
|
|
30
|
-
onAttach?: (options: RenderOptions<TProps, TElementBase>) => void
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Will be executed when the element is detached from the DOM.
|
|
34
|
-
*/
|
|
35
|
-
onDetach?: (options: RenderOptions<TProps, TElementBase>) => void
|
|
36
|
-
|
|
37
27
|
/**
|
|
38
28
|
* Name of the HTML Element's base class. Needs to be defined if the elementBase is set. E.g.: 'div', 'button', 'input'
|
|
39
29
|
*/
|
|
@@ -45,9 +35,29 @@ export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
|
|
|
45
35
|
elementBase?: Constructable<TElementBase>
|
|
46
36
|
|
|
47
37
|
/**
|
|
48
|
-
*
|
|
38
|
+
* Inline styles applied to each component instance.
|
|
39
|
+
* Use for per-instance dynamic overrides. Prefer `css` for component-level defaults.
|
|
49
40
|
*/
|
|
50
41
|
style?: Partial<CSSStyleDeclaration>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* CSS styles injected as a stylesheet during component registration.
|
|
45
|
+
* Supports pseudo-selectors (`&:hover`, `&:active`) and nested selectors (`& .class`).
|
|
46
|
+
*
|
|
47
|
+
* **Best practice:** Prefer `css` over `style` for component defaults -- styles are injected
|
|
48
|
+
* once per component type (better performance), and support pseudo-selectors and nesting.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* css: {
|
|
53
|
+
* display: 'flex',
|
|
54
|
+
* padding: '16px',
|
|
55
|
+
* '&:hover': { backgroundColor: '#f0f0f0' },
|
|
56
|
+
* '& .title': { fontWeight: 'bold' }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
css?: CSSObject
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/**
|
|
@@ -58,11 +68,16 @@ export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
|
|
|
58
68
|
export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
|
|
59
69
|
o: ShadeOptions<TProps, TElementBase>,
|
|
60
70
|
) => {
|
|
61
|
-
//
|
|
71
|
+
// Register custom element
|
|
62
72
|
const customElementName = o.shadowDomName
|
|
63
73
|
|
|
64
74
|
const existing = customElements.get(customElementName)
|
|
65
75
|
if (!existing) {
|
|
76
|
+
// Register CSS styles if provided
|
|
77
|
+
if (o.css) {
|
|
78
|
+
StyleManager.registerComponentStyles(customElementName, o.css, o.elementBaseName)
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
const ElementBase = o.elementBase || HTMLElement
|
|
67
82
|
|
|
68
83
|
customElements.define(
|
|
@@ -79,15 +94,31 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
|
|
|
79
94
|
|
|
80
95
|
public resourceManager = new ResourceManager()
|
|
81
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Host props collected during the current render pass via `useHostProps`.
|
|
99
|
+
* Applied to the host element after each render.
|
|
100
|
+
*/
|
|
101
|
+
private _pendingHostProps: Array<Record<string, unknown> & { style?: Record<string, string> }> = []
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The host props that were applied in the previous render, used for diffing.
|
|
105
|
+
*/
|
|
106
|
+
private _prevHostProps: Record<string, unknown> | null = null
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cached ref objects keyed by the user-provided key string.
|
|
110
|
+
*/
|
|
111
|
+
private _refs = new Map<string, RefObject<Element>>()
|
|
112
|
+
|
|
82
113
|
public connectedCallback() {
|
|
83
|
-
|
|
84
|
-
this.callConstructed()
|
|
114
|
+
this.updateComponent()
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
public async disconnectedCallback() {
|
|
88
|
-
|
|
118
|
+
this._refs.clear()
|
|
119
|
+
this._prevVTree = null
|
|
120
|
+
this._prevHostProps = null
|
|
89
121
|
await this.resourceManager[Symbol.asyncDispose]()
|
|
90
|
-
this.cleanup?.()
|
|
91
122
|
}
|
|
92
123
|
|
|
93
124
|
/**
|
|
@@ -117,8 +148,17 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
|
|
|
117
148
|
props: this.props,
|
|
118
149
|
injector: this.injector,
|
|
119
150
|
children: this.shadeChildren,
|
|
120
|
-
element: this,
|
|
121
151
|
renderCount: this._renderCount,
|
|
152
|
+
useHostProps: (hostProps) => {
|
|
153
|
+
this._pendingHostProps.push(hostProps)
|
|
154
|
+
},
|
|
155
|
+
useRef: <T extends Element = HTMLElement>(key: string): RefObject<T> => {
|
|
156
|
+
const existingRef = this._refs.get(key) as RefObject<T> | undefined
|
|
157
|
+
if (existingRef) return existingRef
|
|
158
|
+
const refObject = { current: null } as { current: T | null }
|
|
159
|
+
this._refs.set(key, refObject as unknown as RefObject<Element>)
|
|
160
|
+
return refObject as RefObject<T>
|
|
161
|
+
},
|
|
122
162
|
useObservable: (key, obesrvable, options) => {
|
|
123
163
|
const onChange = options?.onChange || (() => this.updateComponent())
|
|
124
164
|
return this.resourceManager.useObservable(key, obesrvable, onChange, options)
|
|
@@ -193,47 +233,148 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
|
|
|
193
233
|
return renderOptions
|
|
194
234
|
}
|
|
195
235
|
|
|
236
|
+
private _updateScheduled = false
|
|
237
|
+
|
|
196
238
|
/**
|
|
197
|
-
*
|
|
239
|
+
* The VChild array from the previous render, with `_el` references
|
|
240
|
+
* pointing to the real DOM nodes. Used to diff against the next render.
|
|
198
241
|
*/
|
|
199
|
-
|
|
200
|
-
const renderResult = this.render(this.getRenderOptions())
|
|
242
|
+
private _prevVTree: VChild[] | null = null
|
|
201
243
|
|
|
202
|
-
|
|
203
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Schedules a component update via microtask. Multiple calls before the microtask
|
|
246
|
+
* runs are coalesced into a single render pass.
|
|
247
|
+
*/
|
|
248
|
+
public updateComponent() {
|
|
249
|
+
if (!this._updateScheduled) {
|
|
250
|
+
this._updateScheduled = true
|
|
251
|
+
queueMicrotask(() => {
|
|
252
|
+
this._updateScheduled = false
|
|
253
|
+
this._performUpdate()
|
|
254
|
+
})
|
|
204
255
|
}
|
|
256
|
+
}
|
|
205
257
|
|
|
206
|
-
|
|
207
|
-
|
|
258
|
+
private _performUpdate() {
|
|
259
|
+
this._pendingHostProps = []
|
|
260
|
+
let renderResult: unknown
|
|
261
|
+
setRenderMode(true)
|
|
262
|
+
try {
|
|
263
|
+
renderResult = this.render(this.getRenderOptions())
|
|
264
|
+
} finally {
|
|
265
|
+
setRenderMode(false)
|
|
208
266
|
}
|
|
209
267
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
268
|
+
const newVTree = toVChildArray(renderResult)
|
|
269
|
+
patchChildren(this, this._prevVTree || [], newVTree)
|
|
270
|
+
this._prevVTree = newVTree
|
|
271
|
+
|
|
272
|
+
this._applyHostProps()
|
|
216
273
|
}
|
|
217
274
|
|
|
218
275
|
/**
|
|
219
|
-
*
|
|
276
|
+
* Merges all pending host props from the render pass and applies them
|
|
277
|
+
* to the host element, diffing against the previously applied host props.
|
|
220
278
|
*/
|
|
221
|
-
|
|
222
|
-
this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
279
|
+
private _applyHostProps() {
|
|
280
|
+
if (this._pendingHostProps.length === 0) {
|
|
281
|
+
if (this._prevHostProps) {
|
|
282
|
+
// All host props were removed — clean up
|
|
283
|
+
for (const key of Object.keys(this._prevHostProps)) {
|
|
284
|
+
if (key === 'style') continue
|
|
285
|
+
this.removeAttribute(key)
|
|
286
|
+
}
|
|
287
|
+
if (this._prevHostProps.style) {
|
|
288
|
+
for (const sk of Object.keys(this._prevHostProps.style as Record<string, string>)) {
|
|
289
|
+
if (sk.startsWith('--')) {
|
|
290
|
+
this.style.removeProperty(sk)
|
|
291
|
+
} else {
|
|
292
|
+
;(this.style as unknown as Record<string, string>)[sk] = ''
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
this._prevHostProps = null
|
|
297
|
+
}
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Merge all pending host prop calls into a single object
|
|
302
|
+
const merged: Record<string, unknown> = {}
|
|
303
|
+
let mergedStyle: Record<string, string> | undefined
|
|
304
|
+
|
|
305
|
+
for (const hp of this._pendingHostProps) {
|
|
306
|
+
for (const [key, value] of Object.entries(hp)) {
|
|
307
|
+
if (key === 'style' && typeof value === 'object' && value !== null) {
|
|
308
|
+
mergedStyle = { ...mergedStyle, ...(value as Record<string, string>) }
|
|
309
|
+
} else {
|
|
310
|
+
merged[key] = value
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (mergedStyle) {
|
|
316
|
+
merged.style = mergedStyle
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const oldHP = this._prevHostProps || {}
|
|
320
|
+
const newHP = merged
|
|
321
|
+
|
|
322
|
+
// Remove attributes no longer present
|
|
323
|
+
for (const key of Object.keys(oldHP)) {
|
|
324
|
+
if (key === 'style') continue
|
|
325
|
+
if (!(key in newHP)) {
|
|
326
|
+
if (key.startsWith('on') && typeof oldHP[key] === 'function') {
|
|
327
|
+
;(this as unknown as Record<string, unknown>)[key] = null
|
|
328
|
+
} else {
|
|
329
|
+
this.removeAttribute(key)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Apply new/changed attributes
|
|
335
|
+
for (const [key, value] of Object.entries(newHP)) {
|
|
336
|
+
if (key === 'style') continue
|
|
337
|
+
if (oldHP[key] !== value) {
|
|
338
|
+
if (typeof value === 'function' || (typeof value === 'object' && value !== null)) {
|
|
339
|
+
;(this as unknown as Record<string, unknown>)[key] = value
|
|
340
|
+
} else if (value === null || value === undefined || value === false) {
|
|
341
|
+
if (key in this) {
|
|
342
|
+
;(this as unknown as Record<string, unknown>)[key] = undefined
|
|
343
|
+
}
|
|
344
|
+
this.removeAttribute(key)
|
|
345
|
+
} else {
|
|
346
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
347
|
+
this.setAttribute(key, String(value))
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Diff styles
|
|
353
|
+
const oldStyle = (oldHP.style as Record<string, string>) || {}
|
|
354
|
+
const newStyle = (mergedStyle as Record<string, string>) || {}
|
|
355
|
+
|
|
356
|
+
for (const sk of Object.keys(oldStyle)) {
|
|
357
|
+
if (!(sk in newStyle)) {
|
|
358
|
+
if (sk.startsWith('--')) {
|
|
359
|
+
this.style.removeProperty(sk)
|
|
360
|
+
} else {
|
|
361
|
+
;(this.style as unknown as Record<string, string>)[sk] = ''
|
|
362
|
+
}
|
|
363
|
+
}
|
|
233
364
|
}
|
|
234
|
-
}
|
|
235
365
|
|
|
236
|
-
|
|
366
|
+
for (const [sk, sv] of Object.entries(newStyle)) {
|
|
367
|
+
if (oldStyle[sk] !== sv) {
|
|
368
|
+
if (sk.startsWith('--')) {
|
|
369
|
+
this.style.setProperty(sk, sv)
|
|
370
|
+
} else {
|
|
371
|
+
;(this.style as unknown as Record<string, string>)[sk] = sv
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this._prevHostProps = merged
|
|
377
|
+
}
|
|
237
378
|
|
|
238
379
|
private _injector?: Injector
|
|
239
380
|
|
|
@@ -273,7 +414,7 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
|
|
|
273
414
|
o.elementBaseName ? { extends: o.elementBaseName } : undefined,
|
|
274
415
|
)
|
|
275
416
|
} else {
|
|
276
|
-
throw Error(`A custom shade with
|
|
417
|
+
throw Error(`A custom shade with name '${o.shadowDomName}' has already been registered!`)
|
|
277
418
|
}
|
|
278
419
|
|
|
279
420
|
return (props: TProps & PartialElement<TElementBase>, children?: ChildrenList) => {
|
|
@@ -285,9 +426,23 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
|
|
|
285
426
|
el.props = props || ({} as TProps & PartialElement<TElementBase>)
|
|
286
427
|
el.shadeChildren = children
|
|
287
428
|
|
|
429
|
+
if (o.elementBaseName) {
|
|
430
|
+
el.setAttribute('is', customElementName)
|
|
431
|
+
}
|
|
432
|
+
|
|
288
433
|
attachStyles(el, { style: o.style })
|
|
289
434
|
attachProps(el, props)
|
|
290
435
|
|
|
291
436
|
return el as JSX.Element
|
|
292
437
|
}
|
|
293
438
|
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Flushes any pending microtask-based component updates.
|
|
442
|
+
* Useful in tests to wait for batched renders to complete before asserting DOM state.
|
|
443
|
+
*
|
|
444
|
+
* Note: this flushes one level of pending updates. If a render itself triggers new
|
|
445
|
+
* `updateComponent()` calls, an additional `await flushUpdates()` may be needed.
|
|
446
|
+
* @returns a promise that resolves after the current microtask queue has been processed
|
|
447
|
+
*/
|
|
448
|
+
export const flushUpdates = (): Promise<void> => new Promise<void>((resolve) => queueMicrotask(resolve))
|