@edgeandnode/eds-react 1.1.0 → 1.2.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.
Files changed (118) hide show
  1. package/README.md +27 -3
  2. package/dist/components/Address.d.ts +3 -3
  3. package/dist/components/Address.d.ts.map +1 -1
  4. package/dist/components/Address.js +1 -1
  5. package/dist/components/Address.js.map +1 -1
  6. package/dist/components/Avatar.d.ts +3 -3
  7. package/dist/components/Avatar.d.ts.map +1 -1
  8. package/dist/components/Avatar.js +13 -1
  9. package/dist/components/Avatar.js.map +1 -1
  10. package/dist/components/Breadcrumbs.parts.d.ts +3 -3
  11. package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
  12. package/dist/components/Breadcrumbs.parts.js +1 -1
  13. package/dist/components/Breadcrumbs.parts.js.map +1 -1
  14. package/dist/components/Button.js +1 -1
  15. package/dist/components/Button.js.map +1 -1
  16. package/dist/components/Card.js +1 -1
  17. package/dist/components/Card.js.map +1 -1
  18. package/dist/components/Chip.parts.d.ts.map +1 -1
  19. package/dist/components/Chip.parts.js +1 -1
  20. package/dist/components/Chip.parts.js.map +1 -1
  21. package/dist/components/Cluster.d.ts +3 -3
  22. package/dist/components/Cluster.d.ts.map +1 -1
  23. package/dist/components/Cluster.js.map +1 -1
  24. package/dist/components/Icon.d.ts.map +1 -1
  25. package/dist/components/Icon.js +0 -2
  26. package/dist/components/Icon.js.map +1 -1
  27. package/dist/components/Link.d.ts.map +1 -1
  28. package/dist/components/Link.js +2 -0
  29. package/dist/components/Link.js.map +1 -1
  30. package/dist/components/Menu.parts.js +1 -1
  31. package/dist/components/Menu.parts.js.map +1 -1
  32. package/dist/components/Search.d.ts.map +1 -1
  33. package/dist/components/Search.js +1 -1
  34. package/dist/components/Search.js.map +1 -1
  35. package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
  36. package/dist/components/SegmentedControl.parts.js +1 -1
  37. package/dist/components/SegmentedControl.parts.js.map +1 -1
  38. package/dist/components/TabSet.parts.js +1 -1
  39. package/dist/components/TabSet.parts.js.map +1 -1
  40. package/dist/components/Tooltip.parts.d.ts.map +1 -1
  41. package/dist/components/Tooltip.parts.js.map +1 -1
  42. package/dist/components/base/Checkable.parts.js +1 -1
  43. package/dist/components/base/Checkable.parts.js.map +1 -1
  44. package/dist/components/base/Field.parts.js +1 -1
  45. package/dist/components/base/Field.parts.js.map +1 -1
  46. package/dist/components/base/Portal.js +3 -3
  47. package/dist/components/base/Portal.js.map +1 -1
  48. package/dist/components/base/Transition.js +1 -1
  49. package/dist/components/base/Transition.js.map +1 -1
  50. package/dist/hooks/useCSSPropsPolyfill.d.ts.map +1 -1
  51. package/dist/hooks/useCSSPropsPolyfill.js +7 -19
  52. package/dist/hooks/useCSSPropsPolyfill.js.map +1 -1
  53. package/dist/hooks/useCSSState.d.ts +4 -0
  54. package/dist/hooks/useCSSState.d.ts.map +1 -1
  55. package/dist/hooks/useCSSState.js +24 -6
  56. package/dist/hooks/useCSSState.js.map +1 -1
  57. package/dist/hooks/useStyleObserver.d.ts.map +1 -1
  58. package/dist/hooks/useStyleObserver.js +9 -5
  59. package/dist/hooks/useStyleObserver.js.map +1 -1
  60. package/dist/postcss-plugin.d.ts +2 -0
  61. package/dist/postcss-plugin.d.ts.map +1 -0
  62. package/dist/postcss-plugin.js +2 -0
  63. package/dist/postcss-plugin.js.map +1 -0
  64. package/dist/utils/internal/BatchedElementStyleObserver.d.ts +13 -0
  65. package/dist/utils/internal/BatchedElementStyleObserver.d.ts.map +1 -0
  66. package/dist/utils/internal/BatchedElementStyleObserver.js +28 -0
  67. package/dist/utils/internal/BatchedElementStyleObserver.js.map +1 -0
  68. package/dist/utils/internal/getStyleQueriesSupported.d.ts +2 -0
  69. package/dist/utils/internal/getStyleQueriesSupported.d.ts.map +1 -0
  70. package/dist/utils/internal/getStyleQueriesSupported.js +12 -0
  71. package/dist/utils/internal/getStyleQueriesSupported.js.map +1 -0
  72. package/dist/utils/internal/index.d.ts +2 -0
  73. package/dist/utils/internal/index.d.ts.map +1 -1
  74. package/dist/utils/internal/index.js +2 -0
  75. package/dist/utils/internal/index.js.map +1 -1
  76. package/dist/utils/internal/styleScheduler.d.ts +8 -3
  77. package/dist/utils/internal/styleScheduler.d.ts.map +1 -1
  78. package/dist/utils/internal/styleScheduler.js +46 -16
  79. package/dist/utils/internal/styleScheduler.js.map +1 -1
  80. package/dist/utils/styleObserver.d.ts +2 -0
  81. package/dist/utils/styleObserver.d.ts.map +1 -1
  82. package/dist/utils/styleObserver.js +40 -33
  83. package/dist/utils/styleObserver.js.map +1 -1
  84. package/dist/vite-plugin.d.ts +2 -0
  85. package/dist/vite-plugin.d.ts.map +1 -0
  86. package/dist/vite-plugin.js +2 -0
  87. package/dist/vite-plugin.js.map +1 -0
  88. package/package.json +13 -11
  89. package/scripts/update-skill.cjs +42 -0
  90. package/skill/SKILL.md +18 -14
  91. package/src/components/Address.tsx +12 -10
  92. package/src/components/Avatar.tsx +61 -49
  93. package/src/components/Breadcrumbs.parts.tsx +11 -9
  94. package/src/components/Button.tsx +1 -1
  95. package/src/components/Card.tsx +1 -1
  96. package/src/components/Chip.parts.tsx +9 -6
  97. package/src/components/Cluster.tsx +3 -5
  98. package/src/components/Icon.tsx +0 -2
  99. package/src/components/Link.tsx +2 -0
  100. package/src/components/Menu.parts.tsx +1 -1
  101. package/src/components/Search.tsx +9 -6
  102. package/src/components/SegmentedControl.parts.tsx +8 -5
  103. package/src/components/TabSet.parts.tsx +1 -1
  104. package/src/components/Tooltip.parts.tsx +4 -1
  105. package/src/components/base/Checkable.parts.tsx +1 -1
  106. package/src/components/base/Field.parts.tsx +1 -1
  107. package/src/components/base/Portal.tsx +3 -3
  108. package/src/components/base/Transition.tsx +1 -1
  109. package/src/hooks/useCSSPropsPolyfill.ts +7 -20
  110. package/src/hooks/useCSSState.ts +29 -8
  111. package/src/hooks/useStyleObserver.ts +18 -14
  112. package/src/postcss-plugin.ts +1 -0
  113. package/src/utils/internal/BatchedElementStyleObserver.ts +31 -0
  114. package/src/utils/internal/getStyleQueriesSupported.ts +11 -0
  115. package/src/utils/internal/index.ts +2 -0
  116. package/src/utils/internal/styleScheduler.ts +43 -15
  117. package/src/utils/styleObserver.ts +38 -40
  118. package/src/vite-plugin.ts +1 -0
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Batched style scheduler that collects style operations within a single frame and executes them
3
- * together using `requestAnimationFrame`. This reduces layout thrashing by batching all operations
4
- * into a single reflow.
3
+ * together using `requestAnimationFrame`. Reads run before writes to avoid layout thrashing.
5
4
  *
6
5
  * @example
7
6
  * ````ts
@@ -23,8 +22,9 @@
23
22
  */
24
23
 
25
24
  class StyleScheduler {
26
- private rafId: number | null = null
27
- private pending: (() => void)[] = []
25
+ private animationFrameId: number | null = null
26
+ private pending = new Set<() => void>()
27
+ private pendingWrites = new Set<() => void>()
28
28
 
29
29
  /**
30
30
  * Schedule an operation to be executed in the next animation frame. All operations scheduled
@@ -32,24 +32,52 @@ class StyleScheduler {
32
32
  * Returns a function that cancels the scheduled callback if it hasn't run yet.
33
33
  */
34
34
  schedule(callback: () => void): () => void {
35
- this.pending.push(callback)
35
+ return this.enqueue(callback, this.pending)
36
+ }
36
37
 
37
- if (this.rafId === null) {
38
- this.rafId = requestAnimationFrame(() => {
39
- const callbacks = this.pending
40
- this.pending = []
41
- this.rafId = null
38
+ /** Schedule a write after all reads in the frame, including writes queued during those reads. */
39
+ scheduleWrite(callback: () => void): () => void {
40
+ return this.enqueue(callback, this.pendingWrites)
41
+ }
42
42
 
43
- callbacks.forEach((cb) => cb())
44
- })
43
+ private enqueue(callback: () => void, queue: Set<() => void>): () => void {
44
+ const operation = () => callback()
45
+ queue.add(operation)
46
+ this.requestFrame()
47
+ return () => {
48
+ queue.delete(operation)
45
49
  }
50
+ }
46
51
 
47
- return () => {
48
- const index = this.pending.indexOf(callback)
49
- if (index !== -1) this.pending.splice(index, 1)
52
+ private runBatch(queue: Set<() => void>) {
53
+ try {
54
+ for (const callback of queue) {
55
+ queue.delete(callback)
56
+ callback()
57
+ }
58
+ } finally {
59
+ // Cancellation closures can outlive the batch, including when a callback throws.
60
+ queue.clear()
50
61
  }
51
62
  }
52
63
 
64
+ private requestFrame() {
65
+ if (this.animationFrameId !== null) return
66
+ this.animationFrameId = requestAnimationFrame(() => {
67
+ const callbacks = this.pending
68
+ this.pending = new Set()
69
+ try {
70
+ this.runBatch(callbacks)
71
+ const writes = this.pendingWrites
72
+ this.pendingWrites = new Set()
73
+ this.runBatch(writes)
74
+ } finally {
75
+ this.animationFrameId = null
76
+ if (this.pending.size > 0 || this.pendingWrites.size > 0) this.requestFrame()
77
+ }
78
+ })
79
+ }
80
+
53
81
  /**
54
82
  * Get computed style for an element in a batched manner. Returns a promise that resolves with the
55
83
  * computed style once all batched operations are executed.
@@ -1,6 +1,6 @@
1
1
  import StyleObserver from 'style-observer'
2
2
 
3
- import { styleScheduler } from './internal/index.ts'
3
+ import { BatchedElementStyleObserver, styleScheduler } from './internal/index.ts'
4
4
 
5
5
  export interface StyleObserverRecord<T extends string = string> {
6
6
  target: Element
@@ -59,14 +59,6 @@ class StyleObserverManager {
59
59
  this.subscriptions.set(element, elementSubscriptions)
60
60
  }
61
61
 
62
- let elementObserved = this.observed.get(element)
63
- if (!elementObserved) {
64
- elementObserved = new Set()
65
- this.observed.set(element, elementObserved)
66
- }
67
-
68
- const newProperties: string[] = []
69
-
70
62
  for (const property of properties) {
71
63
  let propertySubscriptions = elementSubscriptions.get(property)
72
64
  if (!propertySubscriptions) {
@@ -74,49 +66,55 @@ class StyleObserverManager {
74
66
  elementSubscriptions.set(property, propertySubscriptions)
75
67
  }
76
68
  propertySubscriptions.add(callback)
77
-
78
- if (!elementObserved.has(property)) {
79
- newProperties.push(property)
80
- elementObserved.add(property)
81
- }
82
69
  }
83
70
 
84
- // Defer observe to rAF to batch with other subscriptions in the same frame
85
- const cancelObserve =
86
- newProperties.length > 0
87
- ? styleScheduler.schedule(() => {
88
- this.observer.observe(element, newProperties)
89
- })
90
- : undefined
71
+ this.scheduleUpdate(element)
91
72
 
92
73
  return () => {
93
- cancelObserve?.()
94
-
95
- const removedProperties: string[] = []
96
-
97
74
  for (const property of properties) {
98
75
  const propertySubscriptions = elementSubscriptions.get(property)
99
- if (propertySubscriptions) {
100
- propertySubscriptions.delete(callback)
101
- if (propertySubscriptions.size === 0) {
102
- elementSubscriptions.delete(property)
103
- elementObserved!.delete(property)
104
- removedProperties.push(property)
105
- }
106
- }
76
+ propertySubscriptions?.delete(callback)
77
+ if (propertySubscriptions?.size === 0) elementSubscriptions.delete(property)
107
78
  }
79
+ if (elementSubscriptions.size === 0) this.subscriptions.delete(element)
80
+ this.scheduleUpdate(element)
81
+ }
82
+ }
108
83
 
109
- if (elementSubscriptions.size === 0) {
110
- this.subscriptions.delete(element)
84
+ private pending = new Set<Element>()
85
+
86
+ private scheduleUpdate(element: Element) {
87
+ if (this.pending.has(element)) return
88
+ this.pending.add(element)
89
+ styleScheduler.schedule(() => {
90
+ this.pending.delete(element)
91
+ const properties = new Set(this.subscriptions.get(element)?.keys())
92
+ const observed = this.observed.get(element) ?? new Set<string>()
93
+ const added = [...properties].filter((property) => !observed.has(property))
94
+ const removed = [...observed].filter((property) => !properties.has(property))
95
+
96
+ if (properties.size > 0) {
97
+ this.observed.set(element, properties)
98
+ } else {
111
99
  this.observed.delete(element)
112
100
  }
113
101
 
114
- if (removedProperties.length > 0) {
115
- styleScheduler.schedule(() => {
116
- this.observer.unobserve(element, removedProperties)
117
- })
102
+ if (added.length > 0) {
103
+ if (!this.observer.elementObservers.has(element)) {
104
+ const observer = new BatchedElementStyleObserver(element, (records) => {
105
+ this.observer.changed(records)
106
+ })
107
+ this.observer.elementObservers.set(element, observer)
108
+ styleScheduler.scheduleWrite(() => observer.flush())
109
+ this.observer.observe(element, added)
110
+ } else {
111
+ styleScheduler.scheduleWrite(() => this.observer.observe(element, added))
112
+ }
118
113
  }
119
- }
114
+ if (removed.length > 0) {
115
+ styleScheduler.scheduleWrite(() => this.observer.unobserve(element, removed))
116
+ }
117
+ })
120
118
  }
121
119
  }
122
120
 
@@ -0,0 +1 @@
1
+ export { default } from '@edgeandnode/eds-css/vite-plugin'