@furystack/shades 10.0.6 → 11.0.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.
Files changed (77) hide show
  1. package/esm/compile-route.d.ts +2 -0
  2. package/esm/compile-route.d.ts.map +1 -0
  3. package/esm/compile-route.js +4 -0
  4. package/esm/compile-route.js.map +1 -0
  5. package/esm/components/lazy-load.d.ts +2 -2
  6. package/esm/components/lazy-load.d.ts.map +1 -1
  7. package/esm/components/lazy-load.spec.js +2 -2
  8. package/esm/components/lazy-load.spec.js.map +1 -1
  9. package/esm/components/link-to-route.d.ts +1 -1
  10. package/esm/components/link-to-route.d.ts.map +1 -1
  11. package/esm/components/link-to-route.js +3 -3
  12. package/esm/components/link-to-route.js.map +1 -1
  13. package/esm/components/route-link.d.ts +2 -2
  14. package/esm/components/route-link.d.ts.map +1 -1
  15. package/esm/components/router.d.ts +4 -4
  16. package/esm/components/router.d.ts.map +1 -1
  17. package/esm/components/router.js +4 -4
  18. package/esm/components/router.js.map +1 -1
  19. package/esm/components/router.spec.js +1 -1
  20. package/esm/components/router.spec.js.map +1 -1
  21. package/esm/index.d.ts +5 -4
  22. package/esm/index.d.ts.map +1 -1
  23. package/esm/index.js +5 -4
  24. package/esm/index.js.map +1 -1
  25. package/esm/models/render-options.d.ts +2 -2
  26. package/esm/models/render-options.d.ts.map +1 -1
  27. package/esm/models/shade-component.d.ts.map +1 -1
  28. package/esm/services/location-service.d.ts +6 -7
  29. package/esm/services/location-service.d.ts.map +1 -1
  30. package/esm/services/location-service.js +24 -26
  31. package/esm/services/location-service.js.map +1 -1
  32. package/esm/services/location-service.spec.js +21 -21
  33. package/esm/services/location-service.spec.js.map +1 -1
  34. package/esm/services/resource-manager.d.ts +4 -5
  35. package/esm/services/resource-manager.d.ts.map +1 -1
  36. package/esm/services/resource-manager.js +18 -5
  37. package/esm/services/resource-manager.js.map +1 -1
  38. package/esm/services/resource-manager.spec.js +55 -7
  39. package/esm/services/resource-manager.spec.js.map +1 -1
  40. package/esm/services/screen-service.d.ts +1 -2
  41. package/esm/services/screen-service.d.ts.map +1 -1
  42. package/esm/services/screen-service.js +1 -1
  43. package/esm/services/screen-service.js.map +1 -1
  44. package/esm/shade-component.d.ts +6 -6
  45. package/esm/shade-component.d.ts.map +1 -1
  46. package/esm/shade-component.js +8 -5
  47. package/esm/shade-component.js.map +1 -1
  48. package/esm/shade-resources.integration.spec.js +6 -5
  49. package/esm/shade-resources.integration.spec.js.map +1 -1
  50. package/esm/shade.d.ts +1 -1
  51. package/esm/shade.d.ts.map +1 -1
  52. package/esm/shade.js +5 -5
  53. package/esm/shade.js.map +1 -1
  54. package/esm/shades.integration.spec.js +79 -4
  55. package/esm/shades.integration.spec.js.map +1 -1
  56. package/esm/styled-element.d.ts.map +1 -1
  57. package/esm/styled-shade.d.ts.map +1 -1
  58. package/esm/styled-shade.js +13 -7
  59. package/esm/styled-shade.js.map +1 -1
  60. package/package.json +8 -8
  61. package/src/compile-route.ts +6 -0
  62. package/src/components/lazy-load.spec.tsx +3 -3
  63. package/src/components/link-to-route.tsx +4 -4
  64. package/src/components/router.spec.tsx +1 -1
  65. package/src/components/router.tsx +7 -7
  66. package/src/index.ts +5 -4
  67. package/src/models/render-options.ts +2 -2
  68. package/src/services/location-service.spec.ts +23 -23
  69. package/src/services/location-service.tsx +29 -31
  70. package/src/services/resource-manager.spec.ts +74 -7
  71. package/src/services/resource-manager.ts +29 -10
  72. package/src/services/screen-service.ts +1 -2
  73. package/src/shade-component.ts +16 -12
  74. package/src/shade-resources.integration.spec.tsx +7 -5
  75. package/src/shade.ts +6 -6
  76. package/src/shades.integration.spec.tsx +107 -4
  77. package/src/styled-shade.ts +13 -7
@@ -20,8 +20,10 @@ export const appendChild = (el: HTMLElement | DocumentFragment, children: Childr
20
20
  }
21
21
  }
22
22
 
23
- export const hasStyle = (props: any): props is { style: Partial<CSSStyleDeclaration> } => {
24
- return props?.style !== undefined
23
+ export const hasStyle = (props: unknown): props is { style: Partial<CSSStyleDeclaration> } => {
24
+ return (
25
+ !!props && typeof props === 'object' && typeof (props as { style: Partial<CSSStyleDeclaration> }).style === 'object'
26
+ )
25
27
  }
26
28
 
27
29
  /**
@@ -37,7 +39,7 @@ export const attachStyles = (el: HTMLElement, props: any) => {
37
39
  }
38
40
  }
39
41
 
40
- export const attachDataAttributes = (el: HTMLElement, props: any) => {
42
+ export const attachDataAttributes = <TProps extends object>(el: HTMLElement, props: TProps) => {
41
43
  props &&
42
44
  Object.entries(props)
43
45
  .filter(([key]) => key.startsWith('data-'))
@@ -49,17 +51,17 @@ export const attachDataAttributes = (el: HTMLElement, props: any) => {
49
51
  * @param el The Target HTML Element
50
52
  * @param props The Props to attach
51
53
  */
52
- export const attachProps = (el: HTMLElement, props: any) => {
54
+ export const attachProps = <TProps extends object>(el: HTMLElement, props: TProps) => {
53
55
  if (!props) {
54
56
  return
55
57
  }
58
+ attachStyles(el, props)
56
59
 
57
- const { style, ...rest } = props
58
-
59
- Object.assign(el, rest)
60
-
61
- if (props && (props as any).style) {
62
- attachStyles(el, props)
60
+ if (hasStyle(props)) {
61
+ const { style, ...rest } = props
62
+ Object.assign(el, rest)
63
+ } else {
64
+ Object.assign(el, props)
63
65
  }
64
66
  attachDataAttributes(el, props)
65
67
  }
@@ -74,7 +76,9 @@ type CreateComponentArgs<TProps> = [
74
76
  * Factory method that creates a component. This should be configured as a default JSX Factory in tsconfig.
75
77
  * @returns the created JSX element
76
78
  */
77
- export const createComponentInner = <TProps>(...[elementType, props, ...children]: CreateComponentArgs<TProps>) => {
79
+ export const createComponentInner = <TProps extends object>(
80
+ ...[elementType, props, ...children]: CreateComponentArgs<TProps>
81
+ ) => {
78
82
  if (typeof elementType === 'string') {
79
83
  const el = document.createElement(elementType)
80
84
 
@@ -100,7 +104,7 @@ export const createFragmentInner = (...[_props, ...children]: CreateFragmentArgs
100
104
  return fragment
101
105
  }
102
106
 
103
- export const createComponent = <TProps>(...args: CreateComponentArgs<TProps> | CreateFragmentArgs) => {
107
+ export const createComponent = <TProps extends object>(...args: CreateComponentArgs<TProps> | CreateFragmentArgs) => {
104
108
  if (args[0] === null) {
105
109
  return createFragmentInner(...args)
106
110
  }
@@ -1,15 +1,15 @@
1
1
  import { Injector } from '@furystack/inject'
2
2
 
3
- import { TextEncoder, TextDecoder } from 'util'
3
+ import { TextDecoder, TextEncoder } from 'util'
4
4
 
5
5
  global.TextEncoder = TextEncoder
6
6
  global.TextDecoder = TextDecoder as any
7
7
 
8
+ import { ObservableValue, sleepAsync } from '@furystack/utils'
9
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
8
10
  import { initializeShadeRoot } from './initialize.js'
9
- import { Shade } from './shade.js'
10
11
  import { createComponent } from './shade-component.js'
11
- import { ObservableValue } from '@furystack/utils'
12
- import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest'
12
+ import { Shade } from './shade.js'
13
13
 
14
14
  describe('Shade Resources integration tests', () => {
15
15
  beforeEach(() => {
@@ -19,7 +19,7 @@ describe('Shade Resources integration tests', () => {
19
19
  document.body.innerHTML = ''
20
20
  })
21
21
 
22
- it('Should update the component based on a custom observable value change', () => {
22
+ it('Should update the component based on a custom observable value change', async () => {
23
23
  const injector = new Injector()
24
24
  const rootElement = document.getElementById('root') as HTMLDivElement
25
25
 
@@ -77,6 +77,8 @@ describe('Shade Resources integration tests', () => {
77
77
 
78
78
  document.body.innerHTML = ''
79
79
 
80
+ await sleepAsync(10) // Cleanup can be async
81
+
80
82
  expect(obs1.getObservers().length).toBe(0)
81
83
  expect(obs2.getObservers().length).toBe(0)
82
84
 
package/src/shade.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { ObservableValue } from '@furystack/utils'
2
1
  import type { Constructable } from '@furystack/inject'
3
2
  import { Injector } from '@furystack/inject'
3
+ import { ObservableValue } from '@furystack/utils'
4
4
  import type { ChildrenList, PartialElement, RenderOptions } from './models/index.js'
5
- import { ResourceManager } from './services/resource-manager.js'
6
5
  import { LocationService } from './services/location-service.js'
6
+ import { ResourceManager } from './services/resource-manager.js'
7
7
  import { attachProps, attachStyles } from './shade-component.js'
8
8
 
9
9
  export type ShadeOptions<TProps, TElementBase extends HTMLElement> = {
@@ -86,7 +86,7 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
86
86
 
87
87
  public disconnectedCallback() {
88
88
  o.onDetach?.(this.getRenderOptions())
89
- this.resourceManager.dispose()
89
+ this.resourceManager[Symbol.asyncDispose]()
90
90
  this.cleanup?.()
91
91
  }
92
92
 
@@ -173,9 +173,9 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
173
173
  })
174
174
 
175
175
  return {
176
- dispose: () => {
176
+ [Symbol.dispose]: () => {
177
177
  window.removeEventListener('storage', updateFromStorageEvent)
178
- subscription.dispose()
178
+ subscription[Symbol.dispose]()
179
179
  messageChannel.close()
180
180
  },
181
181
  }
@@ -190,7 +190,7 @@ export const Shade = <TProps, TElementBase extends HTMLElement = HTMLElement>(
190
190
  useDisposable: this.resourceManager.useDisposable.bind(this.resourceManager),
191
191
  }
192
192
 
193
- return renderOptions as RenderOptions<TProps, TElementBase>
193
+ return renderOptions
194
194
  }
195
195
 
196
196
  /**
@@ -1,15 +1,16 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { usingAsync } from '@furystack/utils'
2
+ import { sleepAsync, usingAsync } from '@furystack/utils'
3
3
 
4
- import { TextEncoder, TextDecoder } from 'util'
4
+ import { TextDecoder, TextEncoder } from 'util'
5
5
 
6
6
  global.TextEncoder = TextEncoder
7
7
  global.TextDecoder = TextDecoder as any
8
8
 
9
+ import { serializeToQueryString } from '@furystack/rest'
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
9
11
  import { initializeShadeRoot } from './initialize.js'
10
- import { Shade } from './shade.js'
11
12
  import { createComponent } from './shade-component.js'
12
- import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'
13
+ import { Shade } from './shade.js'
13
14
 
14
15
  describe('Shades integration tests', () => {
15
16
  beforeEach(() => {
@@ -199,6 +200,7 @@ describe('Shades integration tests', () => {
199
200
  expect(constructed).toBeCalled()
200
201
  expect(cleanup).not.toBeCalled()
201
202
  document.body.innerHTML = ''
203
+ await sleepAsync(10) // Dispose can be async
202
204
  expect(cleanup).toBeCalled()
203
205
  })
204
206
  })
@@ -271,6 +273,104 @@ describe('Shades integration tests', () => {
271
273
  })
272
274
  })
273
275
 
276
+ it('Should update the stored state', async () => {
277
+ await usingAsync(new Injector(), async (injector) => {
278
+ const rootElement = document.getElementById('root') as HTMLDivElement
279
+
280
+ const ExampleComponent = Shade({
281
+ shadowDomName: 'example-component-3-stored-state',
282
+ render: ({ useStoredState }) => {
283
+ const [count, setCount] = useStoredState('count', 0)
284
+ return (
285
+ <div>
286
+ Count is {count}
287
+ <button id="plus" onclick={() => setCount(count + 1)}>
288
+ +
289
+ </button>
290
+ <button id="minus" onclick={() => setCount(count - 1)}>
291
+ -
292
+ </button>
293
+ </div>
294
+ )
295
+ },
296
+ })
297
+ initializeShadeRoot({
298
+ injector,
299
+ rootElement,
300
+ jsxElement: <ExampleComponent />,
301
+ })
302
+
303
+ const plus = () => document.getElementById('plus')?.click()
304
+ const minus = () => document.getElementById('minus')?.click()
305
+ const expectCount = (count: number) => expect(document.body.innerHTML).toContain(`Count is ${count}`)
306
+
307
+ expectCount(0)
308
+
309
+ await sleepAsync(100)
310
+ plus()
311
+ expectCount(1)
312
+ expect(localStorage.getItem('count')).toBe('1')
313
+
314
+ plus()
315
+ expectCount(2)
316
+ expect(localStorage.getItem('count')).toBe('2')
317
+
318
+ minus()
319
+ minus()
320
+ expectCount(0)
321
+ expect(localStorage.getItem('count')).toBe('0')
322
+ })
323
+ })
324
+
325
+ it('Should update the search state', async () => {
326
+ await usingAsync(new Injector(), async (injector) => {
327
+ const rootElement = document.getElementById('root') as HTMLDivElement
328
+
329
+ const ExampleComponent = Shade({
330
+ shadowDomName: 'example-component-3-search-state',
331
+ render: ({ useSearchState }) => {
332
+ const [count, setCount] = useSearchState('count', 0)
333
+ return (
334
+ <div>
335
+ Count is {count}
336
+ <button id="plus" onclick={() => setCount(count + 1)}>
337
+ +
338
+ </button>
339
+ <button id="minus" onclick={() => setCount(count - 1)}>
340
+ -
341
+ </button>
342
+ </div>
343
+ )
344
+ },
345
+ })
346
+ initializeShadeRoot({
347
+ injector,
348
+ rootElement,
349
+ jsxElement: <ExampleComponent />,
350
+ })
351
+
352
+ const plus = () => document.getElementById('plus')?.click()
353
+ const minus = () => document.getElementById('minus')?.click()
354
+ const expectCount = (count: number) => expect(document.body.innerHTML).toContain(`Count is ${count}`)
355
+
356
+ expectCount(0)
357
+
358
+ await sleepAsync(100)
359
+ plus()
360
+ expectCount(1)
361
+ expect(location.search).toBe(`?${serializeToQueryString({ count: 1 })}`)
362
+
363
+ plus()
364
+ expectCount(2)
365
+ expect(location.search).toBe(`?${serializeToQueryString({ count: 2 })}`)
366
+
367
+ minus()
368
+ minus()
369
+ expectCount(0)
370
+ expect(location.search).toBe(`?${serializeToQueryString({ count: 0 })}`)
371
+ })
372
+ })
373
+
274
374
  it('Should allow children update after unmount and remount', async () => {
275
375
  await usingAsync(new Injector(), async (injector) => {
276
376
  const rootElement = document.getElementById('root') as HTMLDivElement
@@ -325,6 +425,7 @@ describe('Shades integration tests', () => {
325
425
 
326
426
  const plus = () => document.getElementById('plus')?.click()
327
427
  const minus = () => document.getElementById('minus')?.click()
428
+
328
429
  const expectCount = (count: number) => expect(document.body.innerHTML).toContain(`Count is ${count}`)
329
430
  const toggleChildren = () => document.getElementById('showHideChildren')?.click()
330
431
 
@@ -336,6 +437,8 @@ describe('Shades integration tests', () => {
336
437
 
337
438
  expect(document.getElementById('plus')).toBeNull()
338
439
 
440
+ await sleepAsync(10) // Dispose can be async
441
+
339
442
  toggleChildren()
340
443
  expect(document.getElementById('plus')).toBeDefined()
341
444
 
@@ -1,4 +1,5 @@
1
1
  import type { ChildrenList } from './models/children-list.js'
2
+ import { hasStyle } from './shade-component.js'
2
3
 
3
4
  /**
4
5
  * Creates a shortcut for a specific custom Shade element with additional styles
@@ -11,13 +12,18 @@ export const styledShade = <T extends (props: any, children?: ChildrenList) => J
11
12
  styles: Partial<CSSStyleDeclaration>,
12
13
  ) => {
13
14
  return ((props: any, childrenList?: ChildrenList) => {
14
- const mergedProps = {
15
- ...props,
16
- style: {
17
- ...((props as any)?.style || {}),
18
- ...styles,
19
- },
20
- }
15
+ const mergedProps = hasStyle(props)
16
+ ? {
17
+ ...props,
18
+ style: {
19
+ ...props.style,
20
+ ...styles,
21
+ },
22
+ }
23
+ : {
24
+ ...props,
25
+ style: styles,
26
+ }
21
27
  return element(mergedProps, childrenList)
22
28
  }) as T
23
29
  }