@nordcraft/runtime 1.0.95 → 1.0.96

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 (74) hide show
  1. package/dist/api/createAPI.js +29 -11
  2. package/dist/api/createAPI.js.map +1 -1
  3. package/dist/api/createAPIv2.js +49 -11
  4. package/dist/api/createAPIv2.js.map +1 -1
  5. package/dist/components/createComponent.js +14 -5
  6. package/dist/components/createComponent.js.map +1 -1
  7. package/dist/components/createElement.js +48 -16
  8. package/dist/components/createElement.js.map +1 -1
  9. package/dist/components/createNode.js +23 -10
  10. package/dist/components/createNode.js.map +1 -1
  11. package/dist/components/createNode.test.js +3 -3
  12. package/dist/components/createNode.test.js.map +1 -1
  13. package/dist/components/createSlot.js +2 -1
  14. package/dist/components/createSlot.js.map +1 -1
  15. package/dist/components/createText.js +7 -2
  16. package/dist/components/createText.js.map +1 -1
  17. package/dist/components/renderComponent.d.ts +4 -1
  18. package/dist/components/renderComponent.js +4 -2
  19. package/dist/components/renderComponent.js.map +1 -1
  20. package/dist/context/subscribeToContext.js +12 -2
  21. package/dist/context/subscribeToContext.js.map +1 -1
  22. package/dist/custom-element/ToddleComponent.js +12 -4
  23. package/dist/custom-element/ToddleComponent.js.map +1 -1
  24. package/dist/custom-element.main.esm.js +29 -29
  25. package/dist/custom-element.main.esm.js.map +4 -4
  26. package/dist/editor/editorUtils.d.ts +1 -0
  27. package/dist/editor/editorUtils.js +14 -0
  28. package/dist/editor/editorUtils.js.map +1 -0
  29. package/dist/editor/types.d.ts +4 -0
  30. package/dist/editor/types.js.map +1 -1
  31. package/dist/editor-preview.main.js +71 -18
  32. package/dist/editor-preview.main.js.map +1 -1
  33. package/dist/events/handleAction.js +62 -31
  34. package/dist/events/handleAction.js.map +1 -1
  35. package/dist/page.main.esm.js +3 -3
  36. package/dist/page.main.esm.js.map +4 -4
  37. package/dist/page.main.js +39 -8
  38. package/dist/page.main.js.map +1 -1
  39. package/dist/utils/createFormulaCache.js.map +1 -1
  40. package/dist/utils/nodes.d.ts +1 -0
  41. package/dist/utils/nodes.js +9 -0
  42. package/dist/utils/nodes.js.map +1 -1
  43. package/dist/utils/nodes.test.d.ts +1 -0
  44. package/dist/utils/nodes.test.js +192 -0
  45. package/dist/utils/nodes.test.js.map +1 -0
  46. package/dist/utils/subscribeCustomProperty.d.ts +1 -1
  47. package/dist/utils/subscribeCustomProperty.js +8 -4
  48. package/dist/utils/subscribeCustomProperty.js.map +1 -1
  49. package/dist/utils/subscribeCustomProperty.test.d.ts +1 -0
  50. package/dist/utils/subscribeCustomProperty.test.js +63 -0
  51. package/dist/utils/subscribeCustomProperty.test.js.map +1 -0
  52. package/package.json +3 -3
  53. package/src/api/createAPI.ts +90 -46
  54. package/src/api/createAPIv2.ts +67 -13
  55. package/src/components/createComponent.ts +85 -67
  56. package/src/components/createElement.ts +63 -27
  57. package/src/components/createNode.test.ts +3 -3
  58. package/src/components/createNode.ts +47 -22
  59. package/src/components/createSlot.ts +2 -1
  60. package/src/components/createText.ts +34 -18
  61. package/src/components/renderComponent.ts +8 -1
  62. package/src/context/subscribeToContext.ts +12 -2
  63. package/src/custom-element/ToddleComponent.ts +37 -22
  64. package/src/editor/editorUtils.ts +13 -0
  65. package/src/editor/types.ts +5 -0
  66. package/src/editor-preview.main.ts +125 -46
  67. package/src/events/handleAction.ts +173 -96
  68. package/src/page.main.ts +64 -26
  69. package/src/types.d.ts +3 -0
  70. package/src/utils/createFormulaCache.ts +2 -2
  71. package/src/utils/nodes.test.ts +246 -0
  72. package/src/utils/nodes.ts +11 -0
  73. package/src/utils/subscribeCustomProperty.test.ts +78 -0
  74. package/src/utils/subscribeCustomProperty.ts +21 -22
@@ -0,0 +1,246 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import '../happydom'
3
+ import {
4
+ ensureEfficientOrdering,
5
+ getNextSiblingElement,
6
+ stripNodeIdRepeatIndices,
7
+ } from './nodes'
8
+
9
+ describe('getNextSiblingElement', () => {
10
+ it('should return null if there are no children', () => {
11
+ const parent = document.createElement('div')
12
+ expect(getNextSiblingElement('0.1', parent)).toBeNull()
13
+ })
14
+
15
+ it('should return the next sibling based on index', () => {
16
+ const parent = document.createElement('div')
17
+ const child1 = document.createElement('div')
18
+ child1.setAttribute('data-id', '0.1')
19
+ const child2 = document.createElement('div')
20
+ child2.setAttribute('data-id', '0.2')
21
+ parent.appendChild(child1)
22
+ parent.appendChild(child2)
23
+
24
+ expect(getNextSiblingElement('0.0', parent)).toBe(child1)
25
+ expect(getNextSiblingElement('0.1', parent)).toBe(child2)
26
+ expect(getNextSiblingElement('0.2', parent)).toBeNull()
27
+ })
28
+
29
+ it('should return the next sibling based on repeat index', () => {
30
+ const parent = document.createElement('div')
31
+ const child1 = document.createElement('div')
32
+ child1.setAttribute('data-id', '0.1(0)')
33
+ const child2 = document.createElement('div')
34
+ child2.setAttribute('data-id', '0.1(1)')
35
+ parent.appendChild(child1)
36
+ parent.appendChild(child2)
37
+
38
+ expect(getNextSiblingElement('0.1(0)', parent)).toBe(child2)
39
+ expect(getNextSiblingElement('0.1(1)', parent)).toBeNull()
40
+ })
41
+
42
+ it('should skip children with lower indices', () => {
43
+ const parent = document.createElement('div')
44
+ const child1 = document.createElement('div')
45
+ child1.setAttribute('data-id', '0.1')
46
+ const child2 = document.createElement('div')
47
+ child2.setAttribute('data-id', '0.3')
48
+ parent.appendChild(child1)
49
+ parent.appendChild(child2)
50
+
51
+ expect(getNextSiblingElement('0.2', parent)).toBe(child2)
52
+ })
53
+
54
+ it('should work with complex paths', () => {
55
+ const parent = document.createElement('div')
56
+ const child = document.createElement('div')
57
+ child.setAttribute('data-id', '1.2.3(5)')
58
+ parent.appendChild(child)
59
+
60
+ expect(getNextSiblingElement('1.2.3(4)', parent)).toBe(child)
61
+ expect(getNextSiblingElement('1.2.3(5)', parent)).toBeNull()
62
+ expect(getNextSiblingElement('1.2.2', parent)).toBe(child)
63
+ })
64
+ })
65
+
66
+ describe('ensureEfficientOrdering and getNextSiblingElement together', () => {
67
+ it('should insert a new item in the correct position among existing items', () => {
68
+ const parent = document.createElement('div')
69
+
70
+ // Initial state: [node-1, node-3]
71
+ const node1 = document.createElement('div')
72
+ node1.setAttribute('data-id', '0.1')
73
+ const node3 = document.createElement('div')
74
+ node3.setAttribute('data-id', '0.3')
75
+
76
+ parent.appendChild(node1)
77
+ parent.appendChild(node3)
78
+
79
+ // We want to insert node-2 (data-id: 0.2)
80
+ const node2 = document.createElement('div')
81
+ node2.setAttribute('data-id', '0.2')
82
+
83
+ // Find where node2 should go
84
+ const nextSibling = getNextSiblingElement('0.2', parent)
85
+ expect(nextSibling).toBe(node3)
86
+
87
+ // Use ensureEfficientOrdering to place it using nextSibling as the anchor
88
+ ensureEfficientOrdering(parent, [node1, node2], nextSibling)
89
+
90
+ expect(parent.childNodes[0]).toBe(node1)
91
+ expect(parent.childNodes[1]).toBe(node2)
92
+ expect(parent.childNodes[2]).toBe(node3)
93
+ })
94
+
95
+ it('should handle repeat indices correctly when inserting', () => {
96
+ const parent = document.createElement('div')
97
+
98
+ const node1_0 = document.createElement('div')
99
+ node1_0.setAttribute('data-id', '0.1(0)')
100
+ const node1_2 = document.createElement('div')
101
+ node1_2.setAttribute('data-id', '0.1(2)')
102
+
103
+ parent.appendChild(node1_0)
104
+ parent.appendChild(node1_2)
105
+
106
+ const node1_1 = document.createElement('div')
107
+ node1_1.setAttribute('data-id', '0.1(1)')
108
+
109
+ const nextSibling = getNextSiblingElement('0.1(1)', parent)
110
+ expect(nextSibling).toBe(node1_2)
111
+
112
+ // Use ensureEfficientOrdering to place it using nextSibling as the anchor
113
+ ensureEfficientOrdering(parent, [node1_0, node1_1], nextSibling)
114
+
115
+ expect(parent.childNodes[0]).toBe(node1_0)
116
+ expect(parent.childNodes[1]).toBe(node1_1)
117
+ expect(parent.childNodes[2]).toBe(node1_2)
118
+ })
119
+ })
120
+
121
+ describe('ensureEfficientOrdering', () => {
122
+ it('should append items to an empty parent', () => {
123
+ const parent = document.createElement('div')
124
+ const item1 = document.createElement('span')
125
+ const item2 = document.createElement('span')
126
+
127
+ ensureEfficientOrdering(parent, [item1, item2])
128
+
129
+ expect(parent.childNodes.length).toBe(2)
130
+ expect(parent.childNodes[0]).toBe(item1)
131
+ expect(parent.childNodes[1]).toBe(item2)
132
+ })
133
+
134
+ it('should insert items at the correct position without moving existing balanced items', () => {
135
+ const parent = document.createElement('div')
136
+ const existing1 = document.createElement('div')
137
+ const existing2 = document.createElement('div')
138
+ parent.appendChild(existing1)
139
+ parent.appendChild(existing2)
140
+
141
+ const newItem = document.createElement('span')
142
+
143
+ // Insert newItem before existing2
144
+ ensureEfficientOrdering(parent, [existing1, newItem, existing2])
145
+
146
+ expect(parent.childNodes.length).toBe(3)
147
+ expect(parent.childNodes[0]).toBe(existing1)
148
+ expect(parent.childNodes[1]).toBe(newItem)
149
+ expect(parent.childNodes[2]).toBe(existing2)
150
+ })
151
+
152
+ it('should reorder items if they are not in the correct order', () => {
153
+ const parent = document.createElement('div')
154
+ const item1 = document.createElement('div')
155
+ const item2 = document.createElement('div')
156
+ parent.appendChild(item2)
157
+ parent.appendChild(item1)
158
+
159
+ // Desired order is item1, item2
160
+ ensureEfficientOrdering(parent, [item1, item2])
161
+
162
+ expect(parent.childNodes.length).toBe(2)
163
+ expect(parent.childNodes[0]).toBe(item1)
164
+ expect(parent.childNodes[1]).toBe(item2)
165
+ })
166
+
167
+ it('should handle a mix of Elements and Text nodes', () => {
168
+ const parent = document.createElement('div')
169
+ const item1 = document.createElement('span')
170
+ const item2 = document.createTextNode('text node')
171
+
172
+ ensureEfficientOrdering(parent, [item1, item2])
173
+
174
+ expect(parent.childNodes.length).toBe(2)
175
+ expect(parent.childNodes[0]).toBe(item1)
176
+ expect(parent.childNodes[1]).toBe(item2)
177
+ })
178
+
179
+ it('should insert before a specified nextElement', () => {
180
+ const parent = document.createElement('div')
181
+ const spacer = document.createElement('div')
182
+ parent.appendChild(spacer)
183
+
184
+ const item1 = document.createElement('span')
185
+ const item2 = document.createElement('span')
186
+
187
+ ensureEfficientOrdering(parent, [item1, item2], spacer)
188
+
189
+ expect(parent.childNodes.length).toBe(3)
190
+ expect(parent.childNodes[0]).toBe(item1)
191
+ expect(parent.childNodes[1]).toBe(item2)
192
+ expect(parent.childNodes[2]).toBe(spacer)
193
+ })
194
+
195
+ it('should handle moving an item from the end to the beginning', () => {
196
+ const parent = document.createElement('div')
197
+ const item1 = document.createElement('div')
198
+ const item2 = document.createElement('div')
199
+ const item3 = document.createElement('div')
200
+ parent.appendChild(item1)
201
+ parent.appendChild(item2)
202
+ parent.appendChild(item3)
203
+
204
+ // Move item3 to the front
205
+ ensureEfficientOrdering(parent, [item3, item1, item2])
206
+
207
+ expect(parent.childNodes[0]).toBe(item3)
208
+ expect(parent.childNodes[1]).toBe(item1)
209
+ expect(parent.childNodes[2]).toBe(item2)
210
+ })
211
+ })
212
+
213
+ describe('stripNodeIdRepeatIndices', () => {
214
+ it('should return null if nodeId is null', () => {
215
+ expect(stripNodeIdRepeatIndices(null)).toBeNull()
216
+ })
217
+
218
+ it('should return null if nodeId is undefined', () => {
219
+ // @ts-expect-error testing undefined input
220
+ expect(stripNodeIdRepeatIndices(undefined)).toBeNull()
221
+ })
222
+
223
+ it('should return the same string if there are no repeat indices', () => {
224
+ expect(stripNodeIdRepeatIndices('1.2.3')).toBe('1.2.3')
225
+ })
226
+
227
+ it('should strip repeat indices from a single part', () => {
228
+ expect(stripNodeIdRepeatIndices('1(0)')).toBe('1')
229
+ })
230
+
231
+ it('should strip repeat indices from multiple parts', () => {
232
+ expect(stripNodeIdRepeatIndices('1.2(3).4(5)')).toBe('1.2.4')
233
+ })
234
+
235
+ it('should handle mixed parts with and without repeat indices', () => {
236
+ expect(stripNodeIdRepeatIndices('1.2(3).4.5(6)')).toBe('1.2.4.5')
237
+ })
238
+
239
+ it('should handle parts with multiple parentheses', () => {
240
+ expect(stripNodeIdRepeatIndices('1(0(1)).2(3)')).toBe('1.2')
241
+ })
242
+
243
+ it('should handle nodeId with only repeat indices', () => {
244
+ expect(stripNodeIdRepeatIndices('(0)')).toBe('')
245
+ })
246
+ })
@@ -123,3 +123,14 @@ export function ensureEfficientOrdering(
123
123
  insertBeforeElement = item
124
124
  }
125
125
  }
126
+
127
+ export function stripNodeIdRepeatIndices(nodeId: string | null): string | null {
128
+ if (!nodeId) {
129
+ return null
130
+ }
131
+
132
+ return nodeId
133
+ .split('.')
134
+ .map((part) => part.split('(')[0])
135
+ .join('.')
136
+ }
@@ -0,0 +1,78 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test'
2
+ import '../happydom'
3
+ import { Signal } from '../signal/signal'
4
+ import { subscribeCustomProperty } from './subscribeCustomProperty'
5
+
6
+ describe('subscribeCustomProperty - multiple roots', () => {
7
+ afterEach(() => {
8
+ // Note: We can't easily clear the WeakMap, but each test uses new elements
9
+ })
10
+
11
+ test('it works with multiple shadow roots independently', () => {
12
+ const div1 = document.createElement('div')
13
+ const shadow1 = div1.attachShadow({ mode: 'open' })
14
+ const div2 = document.createElement('div')
15
+ const shadow2 = div2.attachShadow({ mode: 'open' })
16
+
17
+ const signal1 = new Signal('red')
18
+ const signal2 = new Signal('blue')
19
+
20
+ subscribeCustomProperty({
21
+ root: shadow1,
22
+ selector: '.test',
23
+ customPropertyName: '--color',
24
+ signal: signal1,
25
+ })
26
+
27
+ subscribeCustomProperty({
28
+ root: shadow2,
29
+ selector: '.test',
30
+ customPropertyName: '--color',
31
+ signal: signal2,
32
+ })
33
+
34
+ // Check that each shadow root has its own stylesheet
35
+ expect(shadow1.adoptedStyleSheets).toHaveLength(1)
36
+ expect(shadow2.adoptedStyleSheets).toHaveLength(1)
37
+ expect(shadow1.adoptedStyleSheets[0]).not.toBe(
38
+ shadow2.adoptedStyleSheets[0],
39
+ )
40
+
41
+ const sheet1 = shadow1.adoptedStyleSheets[0]
42
+ const sheet2 = shadow2.adoptedStyleSheets[0]
43
+
44
+ expect(sheet1.cssRules[0].cssText).toContain('--color: red')
45
+ expect(sheet2.cssRules[0].cssText).toContain('--color: blue')
46
+
47
+ // Update signal1
48
+ signal1.set('green')
49
+ expect(sheet1.cssRules[0].cssText).toContain('--color: green')
50
+ expect(sheet2.cssRules[0].cssText).toContain('--color: blue') // Should remain blue
51
+ })
52
+
53
+ test('it fetches the same stylesheet for the same root', () => {
54
+ const div = document.createElement('div')
55
+ const shadow = div.attachShadow({ mode: 'open' })
56
+
57
+ const signal1 = new Signal('red')
58
+ const signal2 = new Signal('blue')
59
+
60
+ subscribeCustomProperty({
61
+ root: shadow,
62
+ selector: '.test1',
63
+ customPropertyName: '--color1',
64
+ signal: signal1,
65
+ })
66
+
67
+ subscribeCustomProperty({
68
+ root: shadow,
69
+ selector: '.test2',
70
+ customPropertyName: '--color2',
71
+ signal: signal2,
72
+ })
73
+
74
+ expect(shadow.adoptedStyleSheets).toHaveLength(1)
75
+ const sheet = shadow.adoptedStyleSheets[0]
76
+ expect(sheet.cssRules).toHaveLength(2)
77
+ })
78
+ })
@@ -4,7 +4,10 @@ import { CUSTOM_PROPERTIES_STYLESHEET_ID } from '@nordcraft/core/dist/styling/th
4
4
  import type { StyleVariant } from '@nordcraft/core/dist/styling/variantSelector'
5
5
  import { CustomPropertyStyleSheet } from '../styles/CustomPropertyStyleSheet'
6
6
 
7
- export let customPropertiesStylesheet: CustomPropertyStyleSheet | undefined
7
+ export const customPropertiesStylesheets = new WeakMap<
8
+ Document | ShadowRoot,
9
+ CustomPropertyStyleSheet
10
+ >()
8
11
 
9
12
  export function subscribeCustomProperty({
10
13
  selector,
@@ -19,31 +22,27 @@ export function subscribeCustomProperty({
19
22
  variant?: StyleVariant
20
23
  root: Document | ShadowRoot
21
24
  }) {
22
- customPropertiesStylesheet ??= new CustomPropertyStyleSheet(
23
- root,
24
- (
25
- root.getElementById(CUSTOM_PROPERTIES_STYLESHEET_ID) as
26
- | HTMLStyleElement
27
- | undefined
28
- )?.sheet,
29
- )
25
+ let stylesheet = customPropertiesStylesheets.get(root)
26
+ if (!stylesheet) {
27
+ stylesheet = new CustomPropertyStyleSheet(
28
+ root,
29
+ (
30
+ root.getElementById(CUSTOM_PROPERTIES_STYLESHEET_ID) as
31
+ | HTMLStyleElement
32
+ | undefined
33
+ )?.sheet,
34
+ )
35
+ customPropertiesStylesheets.set(root, stylesheet)
36
+ }
30
37
 
31
38
  signal.subscribe(
32
- customPropertiesStylesheet.registerProperty(
33
- selector,
34
- customPropertyName,
35
- variant,
36
- ),
39
+ stylesheet.registerProperty(selector, customPropertyName, variant),
37
40
  {
38
41
  destroy: () => {
39
- customPropertiesStylesheet?.unregisterProperty(
40
- selector,
41
- customPropertyName,
42
- {
43
- mediaQuery: variant?.mediaQuery,
44
- startingStyle: variant?.startingStyle,
45
- },
46
- )
42
+ stylesheet?.unregisterProperty(selector, customPropertyName, {
43
+ mediaQuery: variant?.mediaQuery,
44
+ startingStyle: variant?.startingStyle,
45
+ })
47
46
  },
48
47
  },
49
48
  )