@easemate/web-kit 0.1.4 → 0.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 (162) hide show
  1. package/README.md +360 -168
  2. package/build/components/code/index.cjs +3 -3
  3. package/build/components/code/index.js +3 -3
  4. package/build/components/curve/canvas-controls.cjs +3 -3
  5. package/build/components/curve/canvas-controls.js +3 -3
  6. package/build/components/curve/canvas.cjs +4 -4
  7. package/build/components/curve/canvas.js +4 -4
  8. package/build/components/curve/controls.cjs +6 -6
  9. package/build/components/curve/controls.d.cts +4 -4
  10. package/build/components/curve/controls.d.ts +4 -4
  11. package/build/components/curve/controls.js +6 -6
  12. package/build/components/curve/index.cjs +3 -3
  13. package/build/components/curve/index.d.cts +1 -1
  14. package/build/components/curve/index.d.ts +1 -1
  15. package/build/components/curve/index.js +3 -3
  16. package/build/components/curve/output.cjs +3 -3
  17. package/build/components/curve/output.d.cts +1 -1
  18. package/build/components/curve/output.d.ts +1 -1
  19. package/build/components/curve/output.js +3 -3
  20. package/build/components/curve/toolbar.cjs +7 -7
  21. package/build/components/curve/toolbar.d.cts +4 -4
  22. package/build/components/curve/toolbar.d.ts +4 -4
  23. package/build/components/curve/toolbar.js +7 -7
  24. package/build/decorators/OutsideClick.cjs +2 -2
  25. package/build/decorators/OutsideClick.d.cts +2 -2
  26. package/build/decorators/OutsideClick.d.ts +2 -2
  27. package/build/decorators/OutsideClick.js +2 -2
  28. package/build/elements/button/index.cjs +2 -2
  29. package/build/elements/button/index.js +2 -2
  30. package/build/elements/checkbox/index.cjs +4 -4
  31. package/build/elements/checkbox/index.js +4 -4
  32. package/build/elements/color/index.cjs +4 -4
  33. package/build/elements/color/index.d.cts +1 -1
  34. package/build/elements/color/index.d.ts +1 -1
  35. package/build/elements/color/index.js +4 -4
  36. package/build/elements/color/picker.cjs +4 -4
  37. package/build/elements/color/picker.js +4 -4
  38. package/build/elements/dropdown/index.cjs +4 -4
  39. package/build/elements/dropdown/index.d.cts +1 -1
  40. package/build/elements/dropdown/index.d.ts +1 -1
  41. package/build/elements/dropdown/index.js +4 -4
  42. package/build/elements/field/index.cjs +2 -2
  43. package/build/elements/field/index.js +2 -2
  44. package/build/elements/icons/animation/chevron.cjs +2 -2
  45. package/build/elements/icons/animation/chevron.js +2 -2
  46. package/build/elements/icons/animation/clear.cjs +1 -1
  47. package/build/elements/icons/animation/clear.js +1 -1
  48. package/build/elements/icons/animation/grid.cjs +2 -2
  49. package/build/elements/icons/animation/grid.js +2 -2
  50. package/build/elements/icons/animation/loading.cjs +1 -1
  51. package/build/elements/icons/animation/loading.js +1 -1
  52. package/build/elements/icons/animation/snap.cjs +2 -2
  53. package/build/elements/icons/animation/snap.js +2 -2
  54. package/build/elements/icons/interface/anchor-add.cjs +1 -1
  55. package/build/elements/icons/interface/anchor-add.js +1 -1
  56. package/build/elements/icons/interface/anchor-remove.cjs +1 -1
  57. package/build/elements/icons/interface/anchor-remove.js +1 -1
  58. package/build/elements/icons/interface/arrow-up.cjs +1 -1
  59. package/build/elements/icons/interface/arrow-up.js +1 -1
  60. package/build/elements/icons/interface/arrows-vertical.cjs +1 -1
  61. package/build/elements/icons/interface/arrows-vertical.js +1 -1
  62. package/build/elements/icons/interface/bezier-angle.cjs +1 -1
  63. package/build/elements/icons/interface/bezier-angle.js +1 -1
  64. package/build/elements/icons/interface/bezier-distribute.cjs +1 -1
  65. package/build/elements/icons/interface/bezier-distribute.js +1 -1
  66. package/build/elements/icons/interface/bezier-length.cjs +1 -1
  67. package/build/elements/icons/interface/bezier-length.js +1 -1
  68. package/build/elements/icons/interface/bezier-mirror.cjs +1 -1
  69. package/build/elements/icons/interface/bezier-mirror.js +1 -1
  70. package/build/elements/icons/interface/bezier.cjs +1 -1
  71. package/build/elements/icons/interface/bezier.js +1 -1
  72. package/build/elements/icons/interface/check.cjs +1 -1
  73. package/build/elements/icons/interface/check.js +1 -1
  74. package/build/elements/icons/interface/circle-arrow-left.cjs +1 -1
  75. package/build/elements/icons/interface/circle-arrow-left.js +1 -1
  76. package/build/elements/icons/interface/circle-arrow-right.cjs +1 -1
  77. package/build/elements/icons/interface/circle-arrow-right.js +1 -1
  78. package/build/elements/icons/interface/code.cjs +1 -1
  79. package/build/elements/icons/interface/code.js +1 -1
  80. package/build/elements/icons/interface/dots.cjs +1 -1
  81. package/build/elements/icons/interface/dots.js +1 -1
  82. package/build/elements/icons/interface/mention.cjs +1 -1
  83. package/build/elements/icons/interface/mention.js +1 -1
  84. package/build/elements/icons/interface/minus.cjs +1 -1
  85. package/build/elements/icons/interface/minus.js +1 -1
  86. package/build/elements/icons/interface/picker.cjs +1 -1
  87. package/build/elements/icons/interface/picker.js +1 -1
  88. package/build/elements/icons/interface/plus.cjs +1 -1
  89. package/build/elements/icons/interface/plus.js +1 -1
  90. package/build/elements/icons/interface/settings.cjs +1 -1
  91. package/build/elements/icons/interface/settings.js +1 -1
  92. package/build/elements/index.cjs +5 -2
  93. package/build/elements/index.d.cts +2 -1
  94. package/build/elements/index.d.ts +2 -1
  95. package/build/elements/index.js +2 -1
  96. package/build/elements/input/index.cjs +4 -4
  97. package/build/elements/input/index.js +4 -4
  98. package/build/elements/logo/index.cjs +2 -2
  99. package/build/elements/logo/index.js +2 -2
  100. package/build/elements/monitor/fps.cjs +3 -3
  101. package/build/elements/monitor/fps.js +3 -3
  102. package/build/elements/monitor/index.cjs +4 -4
  103. package/build/elements/monitor/index.js +4 -4
  104. package/build/elements/number/index.cjs +4 -4
  105. package/build/elements/number/index.js +4 -4
  106. package/build/elements/origin/index.cjs +4 -4
  107. package/build/elements/origin/index.js +4 -4
  108. package/build/elements/panel/index.cjs +496 -0
  109. package/build/elements/panel/index.d.cts +67 -0
  110. package/build/elements/panel/index.d.ts +67 -0
  111. package/build/elements/panel/index.js +492 -0
  112. package/build/elements/popover/index.cjs +2 -2
  113. package/build/elements/popover/index.js +2 -2
  114. package/build/elements/radio/index.cjs +3 -3
  115. package/build/elements/radio/index.js +3 -3
  116. package/build/elements/radio/input.cjs +4 -4
  117. package/build/elements/radio/input.js +4 -4
  118. package/build/elements/slider/index.cjs +4 -4
  119. package/build/elements/slider/index.js +4 -4
  120. package/build/elements/state/index.cjs +61 -468
  121. package/build/elements/state/index.d.cts +34 -25
  122. package/build/elements/state/index.d.ts +34 -25
  123. package/build/elements/state/index.js +63 -470
  124. package/build/elements/toggle/index.cjs +4 -4
  125. package/build/elements/toggle/index.js +4 -4
  126. package/build/elements/tooltip/index.cjs +4 -4
  127. package/build/elements/tooltip/index.d.cts +1 -1
  128. package/build/elements/tooltip/index.d.ts +1 -1
  129. package/build/elements/tooltip/index.js +4 -4
  130. package/build/internal/component-loaders.cjs +2 -0
  131. package/build/internal/component-loaders.d.cts +2 -2
  132. package/build/internal/component-loaders.d.ts +2 -2
  133. package/build/internal/component-loaders.js +2 -0
  134. package/build/react/events.cjs +25 -0
  135. package/build/react/events.d.cts +39 -0
  136. package/build/react/events.d.ts +39 -0
  137. package/build/react/events.js +22 -0
  138. package/build/react/index.cjs +19 -0
  139. package/build/react/index.d.cts +13 -0
  140. package/build/react/index.d.ts +13 -0
  141. package/build/react/index.js +12 -0
  142. package/build/react/provider.cjs +134 -0
  143. package/build/react/provider.d.cts +81 -0
  144. package/build/react/provider.d.ts +81 -0
  145. package/build/react/provider.js +98 -0
  146. package/build/react/types.cjs +8 -0
  147. package/build/react/types.d.cts +55 -0
  148. package/build/react/types.d.ts +55 -0
  149. package/build/react/types.js +7 -0
  150. package/build/react/use-ease-state.cjs +129 -0
  151. package/build/react/use-ease-state.d.cts +95 -0
  152. package/build/react/use-ease-state.d.ts +95 -0
  153. package/build/react/use-ease-state.js +126 -0
  154. package/build/react/use-web-kit.cjs +150 -0
  155. package/build/react/use-web-kit.d.cts +80 -0
  156. package/build/react/use-web-kit.d.ts +80 -0
  157. package/build/react/use-web-kit.js +114 -0
  158. package/build/register.cjs +1 -0
  159. package/build/register.d.cts +1 -0
  160. package/build/register.d.ts +1 -0
  161. package/build/register.js +1 -0
  162. package/package.json +16 -2
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.State = void 0;
4
4
  const lit_html_1 = require("lit-html");
5
5
  const shared_1 = require("../shared.cjs");
6
- const Component_1 = require("~/decorators/Component");
7
- const Listen_1 = require("~/decorators/Listen");
8
- const Prop_1 = require("~/decorators/Prop");
9
- const Query_1 = require("~/decorators/Query");
6
+ const Component_1 = require("../../decorators/Component");
7
+ const Listen_1 = require("../../decorators/Listen");
8
+ const Prop_1 = require("../../decorators/Prop");
9
+ const Query_1 = require("../../decorators/Query");
10
10
  const readControlValue = (element) => {
11
11
  if (typeof element.value === 'string' || typeof element.value === 'number') {
12
12
  return element.value;
@@ -28,195 +28,52 @@ const getControlName = (element) => {
28
28
  }
29
29
  return element.getAttribute?.('name') ?? null;
30
30
  };
31
+ /**
32
+ * State aggregator component - collects and manages state from child controls.
33
+ *
34
+ * This component provides state management without any visual styling.
35
+ * Use it standalone or wrap it with `<ease-panel>` for a styled container.
36
+ *
37
+ * @tag ease-state
38
+ *
39
+ * @slot - Default slot for controls
40
+ *
41
+ * @fires state-change - Fired when any control value changes
42
+ *
43
+ * @example
44
+ * ```html
45
+ * <!-- Standalone usage (no panel) -->
46
+ * <ease-state>
47
+ * <ease-field label="Duration">
48
+ * <ease-slider name="duration" value="1" min="0" max="5"></ease-slider>
49
+ * </ease-field>
50
+ * <ease-field label="Loop">
51
+ * <ease-toggle name="loop"></ease-toggle>
52
+ * </ease-field>
53
+ * </ease-state>
54
+ *
55
+ * <!-- With panel wrapper -->
56
+ * <ease-panel>
57
+ * <span slot="headline">Animation Controls</span>
58
+ * <ease-state>
59
+ * <ease-field label="Duration">
60
+ * <ease-slider name="duration" value="1" min="0" max="5"></ease-slider>
61
+ * </ease-field>
62
+ * </ease-state>
63
+ * </ease-panel>
64
+ * ```
65
+ */
31
66
  @(0, Component_1.Component)({
32
67
  tag: 'ease-state',
33
68
  shadowMode: 'open',
34
69
  styles: `
35
70
  :host {
36
- --ease-state-transition-duration: 120ms;
37
- --ease-state-transition-easing: cubic-bezier(.25, 0, .5, 1);
71
+ display: contents;
38
72
  }
39
73
 
40
- [part="section"] {
41
- display: block;
42
- width: 100%;
43
- max-width: var(--ease-panel-max-width, 332px);
44
- border-radius: var(--ease-panel-radius, 12px);
45
- border: 1px solid var(--ease-panel-border-color, var(--color-white-6));
46
- background-clip: padding-box;
47
- background: var(--ease-panel-background, var(--color-gray-1000));
48
- box-shadow: var(--ease-panel-shadow, 0 0 40px 0 var(--color-white-2) inset);
49
- box-sizing: border-box;
50
- padding: var(--ease-panel-padding, 12px);
51
- margin: auto;
52
- }
53
-
54
- [part="header"] {
55
- display: flex;
56
- align-items: center;
57
- gap: 8px;
58
- width: 100%;
59
- margin-bottom: 12px;
60
- }
61
-
62
- [part="headline"] {
63
- font-size: var(--ease-panel-title-font-size, 14px);
64
- font-weight: var(--ease-panel-title-font-weight, 500);
65
- line-height: var(--ease-panel-title-line-height, 24px);
66
- font-family: var(--ease-font-family, "Instrument Sans", sans-serif);
67
- color: var(--ease-panel-title-color, var(--color-blue-100));
68
- margin: 0 0 0 4px;
69
- flex-grow: 1;
70
- text-ellipsis: ellipsis;
71
- overflow: hidden;
72
- white-space: nowrap;
73
- }
74
-
75
- [part="headline"]:has(+ [part="tabs"]:not(:empty)) {
76
- display: none;
77
- }
78
-
79
- [part="tabs"] {
80
- display: flex;
81
- align-items: center;
82
- gap: 2px;
83
- flex-grow: 1;
84
- margin: 0 0 0 4px;
85
- }
86
-
87
- [part="tabs"]:empty {
88
- display: none;
89
- }
90
-
91
- [part="tab"] {
92
- appearance: none;
93
- font-size: var(--ease-panel-tab-font-size, 13px);
94
- font-weight: var(--ease-panel-tab-font-weight, 500);
95
- line-height: var(--ease-panel-tab-line-height, 24px);
96
- font-family: var(--ease-font-family, "Instrument Sans", sans-serif);
97
- color: var(--ease-panel-tab-color, var(--color-gray-600));
98
- background: transparent;
99
- border: none;
100
- padding: 4px 8px;
101
- margin: 0;
102
- cursor: pointer;
103
- border-radius: var(--ease-panel-tab-radius, 6px);
104
- transition: color 0.2s, background-color 0.2s;
105
- }
106
-
107
- [part="tab"]:hover {
108
- color: var(--ease-panel-tab-color-hover, var(--color-blue-100));
109
- }
110
-
111
- [part="tab"][aria-selected="true"] {
112
- color: var(--ease-panel-tab-color-active, var(--color-blue-100));
113
- background: var(--ease-panel-tab-background-active, var(--color-white-4));
114
- }
115
-
116
- [part="actions"] {
117
- display: flex;
118
- align-items: center;
119
- gap: 4px;
120
- margin-left: auto;
121
- }
122
-
123
- slot[name="actions"]::slotted(button),
124
- slot[name="actions"]::slotted(a) {
125
- --ease-icon-size: var(--ease-panel-action-icon-size, 16px);
126
-
127
- appearance: none;
128
- flex: 0 0 24px;
129
- border: none;
130
- outline: none;
131
- background-color: transparent;
132
- padding: 4px;
133
- margin: 0;
134
- cursor: pointer;
135
- color: var(--color-gray-600);
136
- transition: color 0.2s;
137
- text-decoration: none;
138
- display: flex;
139
- align-items: center;
140
- justify-content: center;
141
- }
142
-
143
- slot[name="actions"]::slotted(button:hover),
144
- slot[name="actions"]::slotted(button:focus-visible),
145
- slot[name="actions"]::slotted(a:hover),
146
- slot[name="actions"]::slotted(a:focus-visible) {
147
- color: var(--color-blue-100);
148
- }
149
-
150
- slot[name="actions"]::slotted(ease-dropdown) {
151
- flex: 0 0 auto;
152
- width: auto;
153
-
154
- --ease-icon-size: var(--ease-panel-action-icon-size, 16px);
155
- --ease-dropdown-trigger-padding: 4px;
156
- --ease-dropdown-radius: 6px;
157
- --ease-dropdown-background: transparent;
158
- --ease-dropdown-background-hover: transparent;
159
- --ease-dropdown-shadow: none;
160
- --ease-dropdown-color: var(--color-gray-600);
161
- --ease-popover-placement: bottom-end;
162
- }
163
-
164
- slot[name="actions"]::slotted(ease-dropdown:hover),
165
- slot[name="actions"]::slotted(ease-dropdown:focus-within) {
166
- --ease-dropdown-color: var(--color-blue-100);
167
- }
168
-
169
- [part="content"] {
170
- display: block;
171
- width: 100%;
172
- box-sizing: border-box;
173
- margin: auto;
174
- overflow: hidden;
175
- }
176
-
177
- [part="content"][data-animating="true"] {
178
- transition: height var(--ease-state-transition-duration) var(--ease-state-transition-easing);
179
- }
180
-
181
- [part="form"] {
182
- width: 100%;
183
- position: relative;
184
- }
185
-
186
- [part="tab-panel"] {
187
- width: 100%;
188
- pointer-events: none;
189
- display: none;
190
- }
191
-
192
- [part="tab-panel"][data-state="active"] {
193
- display: block;
194
- pointer-events: auto;
195
- }
196
-
197
- [part="tab-panel"][data-state="hidden"] {
198
- display: none;
199
- pointer-events: none;
200
- }
201
-
202
- [part="footer"] {
203
- display: flex;
204
- align-items: center;
205
- justify-content: space-between;
206
- width: 100%;
207
- padding: var(--ease-panel-footer-padding, 12px);
208
- box-sizing: border-box;
209
- border-top: 1px solid var(--color-white-4);
210
-
211
- &:not(:has([data-has-content="true"])) {
212
- display: none;
213
- }
214
- }
215
-
216
- ::slotted([slot="entry"]),
217
- ::slotted([slot^="tab-"]) {
74
+ [part="container"] {
218
75
  display: grid;
219
- gap: 12px;
76
+ gap: var(--ease-state-gap, 12px);
220
77
  box-sizing: border-box;
221
78
  width: 100%;
222
79
  }
@@ -227,35 +84,10 @@ class State extends HTMLElement {
227
84
  #state = {};
228
85
  #initialState = {};
229
86
  #subscribers = new Map();
230
- #tabs = [];
231
- #isAnimating = false;
232
87
  @(0, Prop_1.Prop)({ reflect: true })
233
88
  accessor value;
234
- @(0, Prop_1.Prop)({
235
- type: Number,
236
- reflect: true,
237
- attribute: 'active-tab',
238
- defaultValue: 0,
239
- onChange(next, previous) {
240
- const self = this;
241
- if (next !== previous && previous !== undefined) {
242
- self.handleActiveTabChange(previous, next);
243
- }
244
- }
245
- })
246
- accessor activeTab = 0;
247
- /** @internal */
248
- handleActiveTabChange(previous, next) {
249
- this.performTabAnimation(previous, next);
250
- }
251
- @(0, Query_1.Query)('slot[name="entry"]')
252
- accessor entrySlot;
253
- @(0, Query_1.Query)('output')
254
- accessor outputElement;
255
- @(0, Query_1.Query)('[part="content"]')
256
- accessor contentElement;
257
- @(0, Query_1.Query)('[part="form"]')
258
- accessor formElement;
89
+ @(0, Query_1.Query)('slot')
90
+ accessor defaultSlot;
259
91
  /**
260
92
  * Get the current state object with all control values
261
93
  */
@@ -320,233 +152,21 @@ class State extends HTMLElement {
320
152
  this.set(name, value);
321
153
  }
322
154
  }
323
- /**
324
- * Switch to a specific tab by index
325
- * @param index - The tab index (0-based)
326
- */
327
- setTab(index) {
328
- if (index >= 0 && index < this.#tabs.length && index !== this.activeTab) {
329
- this.activeTab = index;
330
- }
331
- }
332
155
  connectedCallback() {
333
- this.#syncTabs();
334
156
  this.#attach();
335
- this.entrySlot?.addEventListener('slotchange', this.#handleSlotChange);
157
+ this.defaultSlot?.addEventListener('slotchange', this.#handleSlotChange);
336
158
  }
337
159
  disconnectedCallback() {
338
160
  this.#detach();
339
- this.entrySlot?.removeEventListener('slotchange', this.#handleSlotChange);
340
- }
341
- afterRender() {
342
- if (this.outputElement) {
343
- this.outputElement.value = this.value ?? '';
344
- }
345
- this.#syncTabs();
161
+ this.defaultSlot?.removeEventListener('slotchange', this.#handleSlotChange);
346
162
  }
347
163
  render() {
348
- const hasTabs = this.#tabs.length > 0;
349
164
  return (0, lit_html_1.html) `
350
- <section part="section">
351
- <div part="header">
352
- <h3 part="headline"><slot name="headline"></slot></h3>
353
- ${this.#renderTabs()}
354
- <div part="actions">
355
- <slot name="actions"></slot>
356
- </div>
357
- </div>
358
- <div part="content">
359
- <div part="form">
360
- ${hasTabs ? this.#renderTabPanels() : (0, lit_html_1.html) `<slot name="entry"></slot>`}
361
- </div>
362
- </div>
363
- <div part="footer">
364
- <slot name="footer"></slot>
365
- </div>
366
- </section>
367
- `;
368
- }
369
- #renderTabs() {
370
- if (this.#tabs.length === 0) {
371
- return lit_html_1.nothing;
372
- }
373
- return (0, lit_html_1.html) `
374
- <div part="tabs" role="tablist">
375
- ${this.#tabs.map((tab, index) => (0, lit_html_1.html) `
376
- <button
377
- part="tab"
378
- role="tab"
379
- aria-selected=${index === this.activeTab ? 'true' : 'false'}
380
- aria-controls=${`panel-${tab.id}`}
381
- tabindex=${index === this.activeTab ? 0 : -1}
382
- @click=${(e) => this.#handleTabClick(index, tab.id, e)}
383
- @keydown=${(e) => this.#handleTabKeydown(e, index)}
384
- >
385
- ${tab.label}
386
- </button>
387
- `)}
165
+ <div part="container">
166
+ <slot></slot>
388
167
  </div>
389
168
  `;
390
169
  }
391
- #renderTabPanels() {
392
- return (0, lit_html_1.html) `
393
- ${this.#tabs.map((tab, index) => (0, lit_html_1.html) `
394
- <div
395
- part="tab-panel"
396
- role="tabpanel"
397
- id=${`panel-${tab.id}`}
398
- aria-labelledby=${`tab-${tab.id}`}
399
- data-state=${index === this.activeTab ? 'active' : 'hidden'}
400
- data-index=${index}
401
- >
402
- <slot name=${`tab-${tab.id}`}></slot>
403
- </div>
404
- `)}
405
- `;
406
- }
407
- #handleTabClick(index, id, event) {
408
- if (index === this.activeTab) {
409
- return;
410
- }
411
- this.activeTab = index;
412
- (0, shared_1.dispatchControlEvent)(this, 'tab-change', {
413
- index,
414
- id,
415
- event
416
- });
417
- }
418
- #handleTabKeydown(event, currentIndex) {
419
- let newIndex = currentIndex;
420
- switch (event.key) {
421
- case 'ArrowLeft':
422
- event.preventDefault();
423
- newIndex = currentIndex > 0 ? currentIndex - 1 : this.#tabs.length - 1;
424
- break;
425
- case 'ArrowRight':
426
- event.preventDefault();
427
- newIndex = currentIndex < this.#tabs.length - 1 ? currentIndex + 1 : 0;
428
- break;
429
- case 'Home':
430
- event.preventDefault();
431
- newIndex = 0;
432
- break;
433
- case 'End':
434
- event.preventDefault();
435
- newIndex = this.#tabs.length - 1;
436
- break;
437
- default:
438
- return;
439
- }
440
- if (newIndex !== currentIndex) {
441
- this.activeTab = newIndex;
442
- // Focus the new tab button
443
- queueMicrotask(() => {
444
- const tabButtons = this.shadowRoot?.querySelectorAll('[part="tab"]');
445
- const newTabButton = tabButtons?.[newIndex];
446
- newTabButton?.focus();
447
- });
448
- }
449
- }
450
- async performTabAnimation(fromIndex, toIndex) {
451
- if (this.#isAnimating) {
452
- return;
453
- }
454
- this.#isAnimating = true;
455
- const duration = 120;
456
- const easing = 'cubic-bezier(.25, 0, .5, 1)';
457
- const content = this.contentElement;
458
- if (!content) {
459
- this.#isAnimating = false;
460
- this.requestRender();
461
- return;
462
- }
463
- // Get the panels by data-index attribute for reliability
464
- const fromPanel = this.shadowRoot?.querySelector(`[part="tab-panel"][data-index="${fromIndex}"]`);
465
- const toPanel = this.shadowRoot?.querySelector(`[part="tab-panel"][data-index="${toIndex}"]`);
466
- if (!fromPanel || !toPanel) {
467
- this.#isAnimating = false;
468
- this.requestRender();
469
- return;
470
- }
471
- // Lock the current height
472
- const startHeight = content.getBoundingClientRect().height;
473
- content.style.height = `${startHeight}px`;
474
- // FIX: Ensure the new panel is hidden immediately.
475
- // Changing activeTab triggers a render which sets data-state="active" (display: block).
476
- // We must override this with inline styles to prevent the content from showing during the fade-out.
477
- toPanel.style.display = 'none';
478
- toPanel.style.opacity = '0';
479
- // Fade out old content via WAAPI (avoids any "one-frame" flashes)
480
- try {
481
- const fadeOut = fromPanel.animate([{ opacity: 1 }, { opacity: 0 }], { duration, easing, fill: 'forwards' });
482
- await fadeOut.finished;
483
- fadeOut.cancel();
484
- }
485
- catch {
486
- // ignore
487
- }
488
- fromPanel.setAttribute('data-state', 'hidden');
489
- // Prepare and measure new panel while completely invisible
490
- const previousToState = toPanel.getAttribute('data-state');
491
- // Reset display to block (overriding our 'none' above) but keep invisible for measuring
492
- toPanel.style.display = 'block';
493
- toPanel.style.visibility = 'hidden';
494
- toPanel.style.opacity = '0';
495
- // Force layout, then measure
496
- void toPanel.offsetHeight;
497
- const endHeight = toPanel.getBoundingClientRect().height;
498
- // Animate height
499
- if (startHeight !== endHeight) {
500
- content.setAttribute('data-animating', 'true');
501
- void content.offsetHeight;
502
- content.style.height = `${endHeight}px`;
503
- await this.#wait(duration);
504
- }
505
- // Show panel but keep opacity at 0, then fade in
506
- toPanel.style.visibility = 'visible';
507
- toPanel.style.opacity = '0';
508
- // Ensure the 0-opacity state is committed
509
- void toPanel.offsetHeight;
510
- try {
511
- const fadeIn = toPanel.animate([{ opacity: 0 }, { opacity: 1 }], { duration, easing, fill: 'forwards' });
512
- await fadeIn.finished;
513
- fadeIn.cancel();
514
- }
515
- catch {
516
- // ignore
517
- }
518
- // Finalize new tab state and cleanup
519
- toPanel.style.display = '';
520
- toPanel.style.visibility = '';
521
- toPanel.style.opacity = '';
522
- // Restore state attribute (we only want one active)
523
- if (previousToState !== 'active') {
524
- toPanel.setAttribute('data-state', 'active');
525
- }
526
- content.style.height = '';
527
- content.removeAttribute('data-animating');
528
- this.#isAnimating = false;
529
- this.#detach();
530
- this.#attach();
531
- }
532
- #wait(ms) {
533
- return new Promise((resolve) => setTimeout(resolve, ms));
534
- }
535
- #syncTabs() {
536
- const tabs = [];
537
- for (const child of Array.from(this.children)) {
538
- const slot = child.getAttribute('slot');
539
- if (slot?.startsWith('tab-')) {
540
- const id = slot.replace('tab-', '');
541
- const label = child.getAttribute('data-tab-label') || id;
542
- tabs.push({ id, label });
543
- }
544
- }
545
- this.#tabs = tabs.slice(0, 3);
546
- if (this.activeTab >= this.#tabs.length && this.#tabs.length > 0) {
547
- this.activeTab = 0;
548
- }
549
- }
550
170
  @(0, Listen_1.Listen)('input', { target: (host) => host })
551
171
  handleInternalInput(event) {
552
172
  this.#handleControlEvent(event);
@@ -559,10 +179,6 @@ class State extends HTMLElement {
559
179
  handleControlChange(event) {
560
180
  this.#handleControlEvent(event);
561
181
  }
562
- @(0, Listen_1.Listen)('slotchange', { selector: 'slot[name="footer"]' })
563
- onFooterSlotChange() {
564
- this.updateFooterAttribute();
565
- }
566
182
  #handleControlEvent(event) {
567
183
  if ('detail' in event && event.detail?.name) {
568
184
  this.#updateState(event.detail.name, event.detail.value, event);
@@ -580,25 +196,13 @@ class State extends HTMLElement {
580
196
  this.#updateState(name, value, event);
581
197
  }
582
198
  #handleSlotChange = () => {
583
- this.#syncTabs();
584
199
  this.#detach();
585
200
  this.#attach();
586
201
  };
587
202
  #attach() {
588
- const slots = [];
589
- if (this.#tabs.length === 0) {
590
- if (this.entrySlot) {
591
- slots.push(this.entrySlot);
592
- }
593
- }
594
- else {
595
- const activeTab = this.#tabs[this.activeTab];
596
- if (activeTab) {
597
- const tabSlot = this.shadowRoot?.querySelector(`slot[name="tab-${activeTab.id}"]`);
598
- if (tabSlot) {
599
- slots.push(tabSlot);
600
- }
601
- }
203
+ const slot = this.defaultSlot;
204
+ if (!slot) {
205
+ return;
602
206
  }
603
207
  const findControls = (el) => {
604
208
  const controls = [];
@@ -624,18 +228,16 @@ class State extends HTMLElement {
624
228
  };
625
229
  this.#controls.clear();
626
230
  this.#state = {};
627
- for (const slot of slots) {
628
- const elements = slot.assignedElements({ flatten: true });
629
- for (const element of elements) {
630
- const controls = findControls(element);
631
- for (const control of controls) {
632
- const name = getControlName(control);
633
- if (name) {
634
- this.#controls.set(name, control);
635
- const value = readControlValue(control);
636
- this.#state[name] = value;
637
- this.#initialState[name] = value;
638
- }
231
+ const elements = slot.assignedElements({ flatten: true });
232
+ for (const element of elements) {
233
+ const controls = findControls(element);
234
+ for (const control of controls) {
235
+ const name = getControlName(control);
236
+ if (name) {
237
+ this.#controls.set(name, control);
238
+ const value = readControlValue(control);
239
+ this.#state[name] = value;
240
+ this.#initialState[name] = value;
639
241
  }
640
242
  }
641
243
  }
@@ -668,14 +270,5 @@ class State extends HTMLElement {
668
270
  event
669
271
  });
670
272
  }
671
- updateFooterAttribute() {
672
- const footer = this.shadowRoot?.querySelector('[part="footer"]');
673
- if (!footer) {
674
- return;
675
- }
676
- const footerSlot = this.shadowRoot?.querySelector('slot[name="footer"]');
677
- const hasFooter = Boolean(footerSlot?.assignedNodes({ flatten: true }).length > 0);
678
- (0, shared_1.setBooleanAttribute)(footer, 'data-has-content', hasFooter);
679
- }
680
273
  }
681
274
  exports.State = State;
@@ -18,27 +18,45 @@ export interface StateChangeEventDetail {
18
18
  event: Event;
19
19
  }
20
20
  /**
21
- * Event detail for tab change events
21
+ * State aggregator component - collects and manages state from child controls.
22
+ *
23
+ * This component provides state management without any visual styling.
24
+ * Use it standalone or wrap it with `<ease-panel>` for a styled container.
25
+ *
26
+ * @tag ease-state
27
+ *
28
+ * @slot - Default slot for controls
29
+ *
30
+ * @fires state-change - Fired when any control value changes
31
+ *
32
+ * @example
33
+ * ```html
34
+ * <!-- Standalone usage (no panel) -->
35
+ * <ease-state>
36
+ * <ease-field label="Duration">
37
+ * <ease-slider name="duration" value="1" min="0" max="5"></ease-slider>
38
+ * </ease-field>
39
+ * <ease-field label="Loop">
40
+ * <ease-toggle name="loop"></ease-toggle>
41
+ * </ease-field>
42
+ * </ease-state>
43
+ *
44
+ * <!-- With panel wrapper -->
45
+ * <ease-panel>
46
+ * <span slot="headline">Animation Controls</span>
47
+ * <ease-state>
48
+ * <ease-field label="Duration">
49
+ * <ease-slider name="duration" value="1" min="0" max="5"></ease-slider>
50
+ * </ease-field>
51
+ * </ease-state>
52
+ * </ease-panel>
53
+ * ```
22
54
  */
23
- export interface TabChangeEventDetail {
24
- /** The index of the active tab */
25
- index: number;
26
- /** The tab id */
27
- id: string;
28
- /** The original event */
29
- event: Event;
30
- }
31
55
  export declare class State extends HTMLElement {
32
56
  #private;
33
57
  requestRender: () => void;
34
58
  accessor value: string | null;
35
- accessor activeTab: number;
36
- /** @internal */
37
- handleActiveTabChange(previous: number, next: number): void;
38
- accessor entrySlot: HTMLSlotElement | null;
39
- accessor outputElement: HTMLOutputElement | null;
40
- accessor contentElement: HTMLElement | null;
41
- accessor formElement: HTMLElement | null;
59
+ accessor defaultSlot: HTMLSlotElement | null;
42
60
  /**
43
61
  * Get the current state object with all control values
44
62
  */
@@ -67,20 +85,11 @@ export declare class State extends HTMLElement {
67
85
  * Reset all controls to their initial values
68
86
  */
69
87
  reset(): void;
70
- /**
71
- * Switch to a specific tab by index
72
- * @param index - The tab index (0-based)
73
- */
74
- setTab(index: number): void;
75
88
  connectedCallback(): void;
76
89
  disconnectedCallback(): void;
77
- afterRender(): void;
78
90
  render(): TemplateResult;
79
- performTabAnimation(fromIndex: number, toIndex: number): Promise<void>;
80
91
  handleInternalInput(event: CustomEvent<ControlEventDetail>): void;
81
92
  handleInternalChange(event: CustomEvent<ControlEventDetail>): void;
82
93
  handleControlChange(event: CustomEvent<ControlEventDetail>): void;
83
- onFooterSlotChange(): void;
84
- private updateFooterAttribute;
85
94
  }
86
95
  export {};