@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
@@ -2,10 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Monitor = void 0;
4
4
  const lit_html_1 = require("lit-html");
5
- const Component_1 = require("~/decorators/Component");
6
- const Listen_1 = require("~/decorators/Listen");
7
- const Watch_1 = require("~/decorators/Watch");
8
- const template_helpers_1 = require("~/utils/template-helpers");
5
+ const Component_1 = require("../../decorators/Component");
6
+ const Listen_1 = require("../../decorators/Listen");
7
+ const Watch_1 = require("../../decorators/Watch");
8
+ const template_helpers_1 = require("../../utils/template-helpers");
9
9
  require("./fps.cjs");
10
10
  const METRIC_THRESHOLDS = {
11
11
  LCP: { good: 2500, poor: 4000 },
@@ -1,8 +1,8 @@
1
1
  import { html } from 'lit-html';
2
- import { Component } from '~/decorators/Component';
3
- import { Listen } from '~/decorators/Listen';
4
- import { Watch } from '~/decorators/Watch';
5
- import { styleObject } from '~/utils/template-helpers';
2
+ import { Component } from '../../decorators/Component';
3
+ import { Listen } from '../../decorators/Listen';
4
+ import { Watch } from '../../decorators/Watch';
5
+ import { styleObject } from '../../utils/template-helpers';
6
6
  import "./fps.js";
7
7
  const METRIC_THRESHOLDS = {
8
8
  LCP: { good: 2500, poor: 4000 },
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NumberInput = 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
  require("../input/index.cjs");
11
11
  require("../icons/interface/minus.cjs");
12
12
  require("../icons/interface/plus.cjs");
@@ -1,9 +1,9 @@
1
1
  import { html } from 'lit-html';
2
2
  import { coerceNumber, dispatchControlEvent, setBooleanAttribute } from "../shared.js";
3
- import { Component } from '~/decorators/Component';
4
- import { Listen } from '~/decorators/Listen';
5
- import { Prop } from '~/decorators/Prop';
6
- import { Query } from '~/decorators/Query';
3
+ import { Component } from '../../decorators/Component';
4
+ import { Listen } from '../../decorators/Listen';
5
+ import { Prop } from '../../decorators/Prop';
6
+ import { Query } from '../../decorators/Query';
7
7
  import "../input/index.js";
8
8
  import "../icons/interface/minus.js";
9
9
  import "../icons/interface/plus.js";
@@ -4,10 +4,10 @@ exports.Origin = void 0;
4
4
  require("../dropdown/index.cjs");
5
5
  const lit_html_1 = require("lit-html");
6
6
  const shared_1 = require("../shared.cjs");
7
- const Component_1 = require("~/decorators/Component");
8
- const Listen_1 = require("~/decorators/Listen");
9
- const Prop_1 = require("~/decorators/Prop");
10
- const Query_1 = require("~/decorators/Query");
7
+ const Component_1 = require("../../decorators/Component");
8
+ const Listen_1 = require("../../decorators/Listen");
9
+ const Prop_1 = require("../../decorators/Prop");
10
+ const Query_1 = require("../../decorators/Query");
11
11
  @(0, Component_1.Component)({
12
12
  tag: 'ease-origin',
13
13
  styles: `
@@ -1,10 +1,10 @@
1
1
  import "../dropdown/index.js";
2
2
  import { html } from 'lit-html';
3
3
  import { dispatchControlEvent, setBooleanAttribute } from "../shared.js";
4
- import { Component } from '~/decorators/Component';
5
- import { Listen } from '~/decorators/Listen';
6
- import { Prop } from '~/decorators/Prop';
7
- import { Query } from '~/decorators/Query';
4
+ import { Component } from '../../decorators/Component';
5
+ import { Listen } from '../../decorators/Listen';
6
+ import { Prop } from '../../decorators/Prop';
7
+ import { Query } from '../../decorators/Query';
8
8
  @Component({
9
9
  tag: 'ease-origin',
10
10
  styles: `
@@ -0,0 +1,496 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Panel = void 0;
4
+ const lit_html_1 = require("lit-html");
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");
10
+ /**
11
+ * Panel component - visual container with optional tabs and header actions.
12
+ *
13
+ * Use this component when you want the panel UI without state management,
14
+ * or wrap it around `<ease-state>` for full functionality.
15
+ *
16
+ * @tag ease-panel
17
+ *
18
+ * @slot headline - Panel title text (hidden when tabs are present)
19
+ * @slot actions - Header action buttons, links, or dropdowns
20
+ * @slot - Default slot for main content
21
+ * @slot tab-{id} - Tab panel content (use `data-tab-label` for display name)
22
+ * @slot footer - Footer content below all panels
23
+ *
24
+ * @csspart section - Outer container
25
+ * @csspart header - Header row containing headline/tabs and actions
26
+ * @csspart headline - Title element
27
+ * @csspart tabs - Tab button container
28
+ * @csspart tab - Individual tab button
29
+ * @csspart actions - Actions container
30
+ * @csspart content - Content wrapper (handles height animations)
31
+ * @csspart body - Inner body container
32
+ * @csspart tab-panel - Individual tab panel
33
+ * @csspart footer - Footer container
34
+ *
35
+ * @fires tab-change - Fired when the active tab changes
36
+ */
37
+ @(0, Component_1.Component)({
38
+ tag: 'ease-panel',
39
+ shadowMode: 'open',
40
+ styles: `
41
+ :host {
42
+ --ease-panel-transition-duration: 120ms;
43
+ --ease-panel-transition-easing: cubic-bezier(.25, 0, .5, 1);
44
+ }
45
+
46
+ [part="section"] {
47
+ display: block;
48
+ width: 100%;
49
+ max-width: var(--ease-panel-max-width, 332px);
50
+ border-radius: var(--ease-panel-radius, 12px);
51
+ border: 1px solid var(--ease-panel-border-color, var(--color-white-6));
52
+ background-clip: padding-box;
53
+ background: var(--ease-panel-background, var(--color-gray-1000));
54
+ box-shadow: var(--ease-panel-shadow, 0 0 40px 0 var(--color-white-2) inset);
55
+ box-sizing: border-box;
56
+ padding: var(--ease-panel-padding, 12px);
57
+ margin: auto;
58
+ }
59
+
60
+ [part="header"] {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ width: 100%;
65
+ margin-bottom: 12px;
66
+ }
67
+
68
+ [part="header"]:not(:has([part="headline"] slot[name="headline"]::slotted(*))):not(:has([part="tabs"]:not(:empty))):not(:has([part="actions"] slot[name="actions"]::slotted(*))) {
69
+ display: none;
70
+ margin-bottom: 0;
71
+ }
72
+
73
+ [part="headline"] {
74
+ font-size: var(--ease-panel-title-font-size, 14px);
75
+ font-weight: var(--ease-panel-title-font-weight, 500);
76
+ line-height: var(--ease-panel-title-line-height, 24px);
77
+ font-family: var(--ease-font-family, "Instrument Sans", sans-serif);
78
+ color: var(--ease-panel-title-color, var(--color-blue-100));
79
+ margin: 0 0 0 4px;
80
+ flex-grow: 1;
81
+ text-ellipsis: ellipsis;
82
+ overflow: hidden;
83
+ white-space: nowrap;
84
+ }
85
+
86
+ [part="headline"]:has(+ [part="tabs"]:not(:empty)) {
87
+ display: none;
88
+ }
89
+
90
+ [part="tabs"] {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 2px;
94
+ flex-grow: 1;
95
+ margin: 0 0 0 4px;
96
+ }
97
+
98
+ [part="tabs"]:empty {
99
+ display: none;
100
+ }
101
+
102
+ [part="tab"] {
103
+ appearance: none;
104
+ font-size: var(--ease-panel-tab-font-size, 13px);
105
+ font-weight: var(--ease-panel-tab-font-weight, 500);
106
+ line-height: var(--ease-panel-tab-line-height, 24px);
107
+ font-family: var(--ease-font-family, "Instrument Sans", sans-serif);
108
+ color: var(--ease-panel-tab-color, var(--color-gray-600));
109
+ background: transparent;
110
+ border: none;
111
+ padding: 4px 8px;
112
+ margin: 0;
113
+ cursor: pointer;
114
+ border-radius: var(--ease-panel-tab-radius, 6px);
115
+ transition: color 0.2s, background-color 0.2s;
116
+ }
117
+
118
+ [part="tab"]:hover {
119
+ color: var(--ease-panel-tab-color-hover, var(--color-blue-100));
120
+ }
121
+
122
+ [part="tab"][aria-selected="true"] {
123
+ color: var(--ease-panel-tab-color-active, var(--color-blue-100));
124
+ background: var(--ease-panel-tab-background-active, var(--color-white-4));
125
+ }
126
+
127
+ [part="actions"] {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 4px;
131
+ margin-left: auto;
132
+ }
133
+
134
+ slot[name="actions"]::slotted(button),
135
+ slot[name="actions"]::slotted(a) {
136
+ --ease-icon-size: var(--ease-panel-action-icon-size, 16px);
137
+
138
+ appearance: none;
139
+ flex: 0 0 24px;
140
+ border: none;
141
+ outline: none;
142
+ background-color: transparent;
143
+ padding: 4px;
144
+ margin: 0;
145
+ cursor: pointer;
146
+ color: var(--color-gray-600);
147
+ transition: color 0.2s;
148
+ text-decoration: none;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ }
153
+
154
+ slot[name="actions"]::slotted(button:hover),
155
+ slot[name="actions"]::slotted(button:focus-visible),
156
+ slot[name="actions"]::slotted(a:hover),
157
+ slot[name="actions"]::slotted(a:focus-visible) {
158
+ color: var(--color-blue-100);
159
+ }
160
+
161
+ slot[name="actions"]::slotted(ease-dropdown) {
162
+ flex: 0 0 auto;
163
+ width: auto;
164
+
165
+ --ease-icon-size: var(--ease-panel-action-icon-size, 16px);
166
+ --ease-dropdown-trigger-padding: 4px;
167
+ --ease-dropdown-radius: 6px;
168
+ --ease-dropdown-background: transparent;
169
+ --ease-dropdown-background-hover: transparent;
170
+ --ease-dropdown-shadow: none;
171
+ --ease-dropdown-color: var(--color-gray-600);
172
+ --ease-popover-placement: bottom-end;
173
+ }
174
+
175
+ slot[name="actions"]::slotted(ease-dropdown:hover),
176
+ slot[name="actions"]::slotted(ease-dropdown:focus-within) {
177
+ --ease-dropdown-color: var(--color-blue-100);
178
+ }
179
+
180
+ [part="content"] {
181
+ display: block;
182
+ width: 100%;
183
+ box-sizing: border-box;
184
+ margin: auto;
185
+ overflow: hidden;
186
+ }
187
+
188
+ [part="content"][data-animating="true"] {
189
+ transition: height var(--ease-panel-transition-duration) var(--ease-panel-transition-easing);
190
+ }
191
+
192
+ [part="body"] {
193
+ width: 100%;
194
+ position: relative;
195
+ }
196
+
197
+ [part="tab-panel"] {
198
+ width: 100%;
199
+ pointer-events: none;
200
+ display: none;
201
+ }
202
+
203
+ [part="tab-panel"][data-state="active"] {
204
+ display: block;
205
+ pointer-events: auto;
206
+ }
207
+
208
+ [part="tab-panel"][data-state="hidden"] {
209
+ display: none;
210
+ pointer-events: none;
211
+ }
212
+
213
+ [part="footer"] {
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: space-between;
217
+ width: 100%;
218
+ padding: var(--ease-panel-footer-padding, 12px);
219
+ box-sizing: border-box;
220
+ border-top: 1px solid var(--color-white-4);
221
+
222
+ &:not(:has([data-has-content="true"])) {
223
+ display: none;
224
+ }
225
+ }
226
+
227
+ ::slotted(:not([slot])),
228
+ ::slotted([slot^="tab-"]) {
229
+ display: grid;
230
+ gap: 12px;
231
+ box-sizing: border-box;
232
+ width: 100%;
233
+ }
234
+ `
235
+ })
236
+ class Panel extends HTMLElement {
237
+ #tabs = [];
238
+ #isAnimating = false;
239
+ @(0, Prop_1.Prop)({
240
+ type: Number,
241
+ reflect: true,
242
+ attribute: 'active-tab',
243
+ defaultValue: 0,
244
+ onChange(next, previous) {
245
+ const self = this;
246
+ if (next !== previous && previous !== undefined) {
247
+ self.handleActiveTabChange(previous, next);
248
+ }
249
+ }
250
+ })
251
+ accessor activeTab = 0;
252
+ /** @internal */
253
+ handleActiveTabChange(previous, next) {
254
+ this.performTabAnimation(previous, next);
255
+ }
256
+ @(0, Query_1.Query)('[part="content"]')
257
+ accessor contentElement;
258
+ @(0, Query_1.Query)('[part="body"]')
259
+ accessor bodyElement;
260
+ /**
261
+ * Get the tab configuration
262
+ */
263
+ get tabs() {
264
+ return this.#tabs;
265
+ }
266
+ /**
267
+ * Switch to a specific tab by index
268
+ * @param index - The tab index (0-based)
269
+ */
270
+ setTab(index) {
271
+ if (index >= 0 && index < this.#tabs.length && index !== this.activeTab) {
272
+ this.activeTab = index;
273
+ }
274
+ }
275
+ connectedCallback() {
276
+ this.#syncTabs();
277
+ }
278
+ afterRender() {
279
+ this.#syncTabs();
280
+ }
281
+ render() {
282
+ const hasTabs = this.#tabs.length > 0;
283
+ return (0, lit_html_1.html) `
284
+ <section part="section">
285
+ <div part="header">
286
+ <h3 part="headline"><slot name="headline"></slot></h3>
287
+ ${this.#renderTabs()}
288
+ <div part="actions">
289
+ <slot name="actions"></slot>
290
+ </div>
291
+ </div>
292
+ <div part="content">
293
+ <div part="body">
294
+ ${hasTabs ? this.#renderTabPanels() : (0, lit_html_1.html) `<slot></slot>`}
295
+ </div>
296
+ </div>
297
+ <div part="footer">
298
+ <slot name="footer"></slot>
299
+ </div>
300
+ </section>
301
+ `;
302
+ }
303
+ #renderTabs() {
304
+ if (this.#tabs.length === 0) {
305
+ return lit_html_1.nothing;
306
+ }
307
+ return (0, lit_html_1.html) `
308
+ <div part="tabs" role="tablist">
309
+ ${this.#tabs.map((tab, index) => (0, lit_html_1.html) `
310
+ <button
311
+ part="tab"
312
+ role="tab"
313
+ aria-selected=${index === this.activeTab ? 'true' : 'false'}
314
+ aria-controls=${`panel-${tab.id}`}
315
+ tabindex=${index === this.activeTab ? 0 : -1}
316
+ @click=${(e) => this.#handleTabClick(index, tab.id, e)}
317
+ @keydown=${(e) => this.#handleTabKeydown(e, index)}
318
+ >
319
+ ${tab.label}
320
+ </button>
321
+ `)}
322
+ </div>
323
+ `;
324
+ }
325
+ #renderTabPanels() {
326
+ return (0, lit_html_1.html) `
327
+ ${this.#tabs.map((tab, index) => (0, lit_html_1.html) `
328
+ <div
329
+ part="tab-panel"
330
+ role="tabpanel"
331
+ id=${`panel-${tab.id}`}
332
+ aria-labelledby=${`tab-${tab.id}`}
333
+ data-state=${index === this.activeTab ? 'active' : 'hidden'}
334
+ data-index=${index}
335
+ >
336
+ <slot name=${`tab-${tab.id}`}></slot>
337
+ </div>
338
+ `)}
339
+ `;
340
+ }
341
+ #handleTabClick(index, id, event) {
342
+ if (index === this.activeTab) {
343
+ return;
344
+ }
345
+ this.activeTab = index;
346
+ this.dispatchEvent(new CustomEvent('tab-change', {
347
+ detail: { index, id, event },
348
+ bubbles: true,
349
+ composed: true
350
+ }));
351
+ }
352
+ #handleTabKeydown(event, currentIndex) {
353
+ let newIndex = currentIndex;
354
+ switch (event.key) {
355
+ case 'ArrowLeft':
356
+ event.preventDefault();
357
+ newIndex = currentIndex > 0 ? currentIndex - 1 : this.#tabs.length - 1;
358
+ break;
359
+ case 'ArrowRight':
360
+ event.preventDefault();
361
+ newIndex = currentIndex < this.#tabs.length - 1 ? currentIndex + 1 : 0;
362
+ break;
363
+ case 'Home':
364
+ event.preventDefault();
365
+ newIndex = 0;
366
+ break;
367
+ case 'End':
368
+ event.preventDefault();
369
+ newIndex = this.#tabs.length - 1;
370
+ break;
371
+ default:
372
+ return;
373
+ }
374
+ if (newIndex !== currentIndex) {
375
+ this.activeTab = newIndex;
376
+ // Focus the new tab button
377
+ queueMicrotask(() => {
378
+ const tabButtons = this.shadowRoot?.querySelectorAll('[part="tab"]');
379
+ const newTabButton = tabButtons?.[newIndex];
380
+ newTabButton?.focus();
381
+ });
382
+ }
383
+ }
384
+ async performTabAnimation(fromIndex, toIndex) {
385
+ if (this.#isAnimating) {
386
+ return;
387
+ }
388
+ this.#isAnimating = true;
389
+ const duration = 120;
390
+ const easing = 'cubic-bezier(.25, 0, .5, 1)';
391
+ const content = this.contentElement;
392
+ if (!content) {
393
+ this.#isAnimating = false;
394
+ this.requestRender();
395
+ return;
396
+ }
397
+ // Get the panels by data-index attribute for reliability
398
+ const fromPanel = this.shadowRoot?.querySelector(`[part="tab-panel"][data-index="${fromIndex}"]`);
399
+ const toPanel = this.shadowRoot?.querySelector(`[part="tab-panel"][data-index="${toIndex}"]`);
400
+ if (!fromPanel || !toPanel) {
401
+ this.#isAnimating = false;
402
+ this.requestRender();
403
+ return;
404
+ }
405
+ // Lock the current height
406
+ const startHeight = content.getBoundingClientRect().height;
407
+ content.style.height = `${startHeight}px`;
408
+ // FIX: Ensure the new panel is hidden immediately.
409
+ toPanel.style.display = 'none';
410
+ toPanel.style.opacity = '0';
411
+ // Fade out old content via WAAPI
412
+ try {
413
+ const fadeOut = fromPanel.animate([{ opacity: 1 }, { opacity: 0 }], { duration, easing, fill: 'forwards' });
414
+ await fadeOut.finished;
415
+ fadeOut.cancel();
416
+ }
417
+ catch {
418
+ // ignore
419
+ }
420
+ fromPanel.setAttribute('data-state', 'hidden');
421
+ // Prepare and measure new panel while completely invisible
422
+ const previousToState = toPanel.getAttribute('data-state');
423
+ toPanel.style.display = 'block';
424
+ toPanel.style.visibility = 'hidden';
425
+ toPanel.style.opacity = '0';
426
+ // Force layout, then measure
427
+ void toPanel.offsetHeight;
428
+ const endHeight = toPanel.getBoundingClientRect().height;
429
+ // Animate height
430
+ if (startHeight !== endHeight) {
431
+ content.setAttribute('data-animating', 'true');
432
+ void content.offsetHeight;
433
+ content.style.height = `${endHeight}px`;
434
+ await this.#wait(duration);
435
+ }
436
+ // Show panel but keep opacity at 0, then fade in
437
+ toPanel.style.visibility = 'visible';
438
+ toPanel.style.opacity = '0';
439
+ void toPanel.offsetHeight;
440
+ try {
441
+ const fadeIn = toPanel.animate([{ opacity: 0 }, { opacity: 1 }], { duration, easing, fill: 'forwards' });
442
+ await fadeIn.finished;
443
+ fadeIn.cancel();
444
+ }
445
+ catch {
446
+ // ignore
447
+ }
448
+ // Finalize new tab state and cleanup
449
+ toPanel.style.display = '';
450
+ toPanel.style.visibility = '';
451
+ toPanel.style.opacity = '';
452
+ if (previousToState !== 'active') {
453
+ toPanel.setAttribute('data-state', 'active');
454
+ }
455
+ content.style.height = '';
456
+ content.removeAttribute('data-animating');
457
+ this.#isAnimating = false;
458
+ }
459
+ #wait(ms) {
460
+ return new Promise((resolve) => setTimeout(resolve, ms));
461
+ }
462
+ #syncTabs() {
463
+ const tabs = [];
464
+ for (const child of Array.from(this.children)) {
465
+ const slot = child.getAttribute('slot');
466
+ if (slot?.startsWith('tab-')) {
467
+ const id = slot.replace('tab-', '');
468
+ const label = child.getAttribute('data-tab-label') || id;
469
+ tabs.push({ id, label });
470
+ }
471
+ }
472
+ this.#tabs = tabs.slice(0, 3);
473
+ if (this.activeTab >= this.#tabs.length && this.#tabs.length > 0) {
474
+ this.activeTab = 0;
475
+ }
476
+ }
477
+ @(0, Listen_1.Listen)('slotchange', { selector: 'slot[name="footer"]' })
478
+ onFooterSlotChange() {
479
+ this.updateFooterAttribute();
480
+ }
481
+ @(0, Listen_1.Listen)('slotchange', { selector: 'slot:not([name])' })
482
+ onDefaultSlotChange() {
483
+ this.#syncTabs();
484
+ this.requestRender();
485
+ }
486
+ updateFooterAttribute() {
487
+ const footer = this.shadowRoot?.querySelector('[part="footer"]');
488
+ if (!footer) {
489
+ return;
490
+ }
491
+ const footerSlot = this.shadowRoot?.querySelector('slot[name="footer"]');
492
+ const hasFooter = Boolean(footerSlot?.assignedNodes({ flatten: true }).length > 0);
493
+ (0, shared_1.setBooleanAttribute)(footer, 'data-has-content', hasFooter);
494
+ }
495
+ }
496
+ exports.Panel = Panel;
@@ -0,0 +1,67 @@
1
+ import { type TemplateResult } from 'lit-html';
2
+ /**
3
+ * Event detail for tab change events
4
+ */
5
+ export interface TabChangeEventDetail {
6
+ /** The index of the active tab */
7
+ index: number;
8
+ /** The tab id */
9
+ id: string;
10
+ /** The original event */
11
+ event: Event;
12
+ }
13
+ /**
14
+ * Panel component - visual container with optional tabs and header actions.
15
+ *
16
+ * Use this component when you want the panel UI without state management,
17
+ * or wrap it around `<ease-state>` for full functionality.
18
+ *
19
+ * @tag ease-panel
20
+ *
21
+ * @slot headline - Panel title text (hidden when tabs are present)
22
+ * @slot actions - Header action buttons, links, or dropdowns
23
+ * @slot - Default slot for main content
24
+ * @slot tab-{id} - Tab panel content (use `data-tab-label` for display name)
25
+ * @slot footer - Footer content below all panels
26
+ *
27
+ * @csspart section - Outer container
28
+ * @csspart header - Header row containing headline/tabs and actions
29
+ * @csspart headline - Title element
30
+ * @csspart tabs - Tab button container
31
+ * @csspart tab - Individual tab button
32
+ * @csspart actions - Actions container
33
+ * @csspart content - Content wrapper (handles height animations)
34
+ * @csspart body - Inner body container
35
+ * @csspart tab-panel - Individual tab panel
36
+ * @csspart footer - Footer container
37
+ *
38
+ * @fires tab-change - Fired when the active tab changes
39
+ */
40
+ export declare class Panel extends HTMLElement {
41
+ #private;
42
+ requestRender: () => void;
43
+ accessor activeTab: number;
44
+ /** @internal */
45
+ handleActiveTabChange(previous: number, next: number): void;
46
+ accessor contentElement: HTMLElement | null;
47
+ accessor bodyElement: HTMLElement | null;
48
+ /**
49
+ * Get the tab configuration
50
+ */
51
+ get tabs(): ReadonlyArray<{
52
+ id: string;
53
+ label: string;
54
+ }>;
55
+ /**
56
+ * Switch to a specific tab by index
57
+ * @param index - The tab index (0-based)
58
+ */
59
+ setTab(index: number): void;
60
+ connectedCallback(): void;
61
+ afterRender(): void;
62
+ render(): TemplateResult;
63
+ performTabAnimation(fromIndex: number, toIndex: number): Promise<void>;
64
+ onFooterSlotChange(): void;
65
+ onDefaultSlotChange(): void;
66
+ private updateFooterAttribute;
67
+ }