@waggylabs/yumekit 0.4.2-beta.30 → 0.4.2-beta.32

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.
package/CHANGELOG.md CHANGED
@@ -31,6 +31,12 @@ Delete any empty sections before publishing.
31
31
  <!-- ### Security -->
32
32
  <!-- Vulnerability patches or hardening changes -->
33
33
 
34
+ ## [Unreleased]
35
+
36
+ ### Added
37
+
38
+ - New `y-stack` component — a layout container for arranging child elements in rows, columns, grids, or masonry patterns. Supports `mode` (`"flex"` | `"grid"` | `"masonry"`), `direction`, `columns`, `gap` (maps to `--spacing-*` tokens), `wrap`, `align`, `justify`, and `responsive` attributes. Masonry mode uses JS absolute positioning with `ResizeObserver`. Responsive mode auto-collapses columns at configurable breakpoints. CSS custom properties: `--component-stack-gap`, `--component-stack-columns`, `--component-stack-mobile-breakpoint`, `--component-stack-tablet-breakpoint`.
39
+
34
40
  ## [0.4.2] - 2026-04-07
35
41
 
36
42
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  ## Overview
19
19
 
20
- YumeKit is a collection of 27 production-ready custom elements built with native Web Components. It works with any framework — or none at all — and ships with a comprehensive design token system, built-in theming, an icon registry, and full TypeScript support.
20
+ YumeKit is a collection of 30 production-ready custom elements built with native Web Components. It works with any framework — or none at all — and ships with a comprehensive design token system, built-in theming, an icon registry, and full TypeScript support.
21
21
 
22
22
  - **Zero dependencies** — built entirely on web standards
23
23
  - **Framework-agnostic** — works with React, Vue, Svelte, or plain HTML
@@ -96,6 +96,7 @@ Then use the `<y-theme>` component to apply a theme:
96
96
  | Rating | `<y-rating>` | Star / icon rating input |
97
97
  | Select | `<y-select>` | Select / dropdown input |
98
98
  | Slider | `<y-slider>` | Range slider input |
99
+ | Stack | `<y-stack>` | Layout container (row, column, grid, masonry) |
99
100
  | Switch | `<y-switch>` | Toggle switch |
100
101
  | Table | `<y-table>` | Sortable data table |
101
102
  | Textarea | `<y-textarea>` | Multi-line text input |
@@ -0,0 +1,52 @@
1
+ export class YumeStack extends HTMLElement {
2
+ static get observedAttributes(): string[];
3
+ _resizeObserver: ResizeObserver;
4
+ _masonryRAF: number;
5
+ connectedCallback(): void;
6
+ disconnectedCallback(): void;
7
+ attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
8
+ set direction(val: string);
9
+ /** Main axis direction (flex mode only). */
10
+ get direction(): string;
11
+ set mode(val: string);
12
+ /** Layout algorithm: "flex" | "grid" | "masonry". */
13
+ get mode(): string;
14
+ set columns(val: number);
15
+ /** Number of columns (grid/masonry modes). */
16
+ get columns(): number;
17
+ set gap(val: string);
18
+ /** Gap between items, maps to --spacing-* tokens. */
19
+ get gap(): string;
20
+ set wrap(val: boolean);
21
+ /** Allow items to wrap (flex mode only). */
22
+ get wrap(): boolean;
23
+ set align(val: string);
24
+ /** Cross-axis alignment. */
25
+ get align(): string;
26
+ set justify(val: string);
27
+ /** Main-axis distribution (flex mode only). */
28
+ get justify(): string;
29
+ set responsive(val: boolean);
30
+ /** Auto-reduce columns at narrow viewports. */
31
+ get responsive(): boolean;
32
+ render(): void;
33
+ _container: Element;
34
+ _slot: HTMLSlotElement;
35
+ _applyLayout(): void;
36
+ _buildStyleSheet(): CSSStyleSheet;
37
+ _buildResponsiveFlexCSS(): string;
38
+ _buildResponsiveGridCSS(cols: any): string;
39
+ _clearMasonryPositions(): void;
40
+ _getBreakpointValue(prop: any, fallback: any): any;
41
+ _getResolvedColumns(): any;
42
+ _getResponsiveColumns(): any;
43
+ _getSlottedElements(): Element[];
44
+ _initMasonry(): void;
45
+ _observedItems: Set<any>;
46
+ _initResponsive(): void;
47
+ _layoutMasonry(): void;
48
+ _resolveGap(): string;
49
+ _resolveGapPx(): number;
50
+ _syncMasonryObserver(): void;
51
+ _teardownMasonry(): void;
52
+ }
@@ -0,0 +1,195 @@
1
+ declare namespace _default {
2
+ let title: string;
3
+ let tags: string[];
4
+ namespace argTypes {
5
+ namespace mode {
6
+ let control: string;
7
+ let options: string[];
8
+ let description: string;
9
+ namespace table {
10
+ namespace defaultValue {
11
+ let summary: string;
12
+ }
13
+ }
14
+ }
15
+ namespace direction {
16
+ let control_1: string;
17
+ export { control_1 as control };
18
+ let options_1: string[];
19
+ export { options_1 as options };
20
+ let description_1: string;
21
+ export { description_1 as description };
22
+ export namespace table_1 {
23
+ export namespace defaultValue_1 {
24
+ let summary_1: string;
25
+ export { summary_1 as summary };
26
+ }
27
+ export { defaultValue_1 as defaultValue };
28
+ }
29
+ export { table_1 as table };
30
+ }
31
+ namespace gap {
32
+ let control_2: string;
33
+ export { control_2 as control };
34
+ let options_2: string[];
35
+ export { options_2 as options };
36
+ let description_2: string;
37
+ export { description_2 as description };
38
+ export namespace table_2 {
39
+ export namespace defaultValue_2 {
40
+ let summary_2: string;
41
+ export { summary_2 as summary };
42
+ }
43
+ export { defaultValue_2 as defaultValue };
44
+ }
45
+ export { table_2 as table };
46
+ }
47
+ namespace columns {
48
+ export namespace control_3 {
49
+ let type: string;
50
+ let min: number;
51
+ let max: number;
52
+ }
53
+ export { control_3 as control };
54
+ let description_3: string;
55
+ export { description_3 as description };
56
+ export namespace table_3 {
57
+ export namespace defaultValue_3 {
58
+ let summary_3: number;
59
+ export { summary_3 as summary };
60
+ }
61
+ export { defaultValue_3 as defaultValue };
62
+ }
63
+ export { table_3 as table };
64
+ }
65
+ namespace wrap {
66
+ let control_4: string;
67
+ export { control_4 as control };
68
+ let description_4: string;
69
+ export { description_4 as description };
70
+ export namespace table_4 {
71
+ export namespace defaultValue_4 {
72
+ let summary_4: boolean;
73
+ export { summary_4 as summary };
74
+ }
75
+ export { defaultValue_4 as defaultValue };
76
+ }
77
+ export { table_4 as table };
78
+ }
79
+ namespace align {
80
+ let control_5: string;
81
+ export { control_5 as control };
82
+ let options_3: string[];
83
+ export { options_3 as options };
84
+ let description_5: string;
85
+ export { description_5 as description };
86
+ export namespace table_5 {
87
+ export namespace defaultValue_5 {
88
+ let summary_5: string;
89
+ export { summary_5 as summary };
90
+ }
91
+ export { defaultValue_5 as defaultValue };
92
+ }
93
+ export { table_5 as table };
94
+ }
95
+ namespace justify {
96
+ let control_6: string;
97
+ export { control_6 as control };
98
+ let options_4: string[];
99
+ export { options_4 as options };
100
+ let description_6: string;
101
+ export { description_6 as description };
102
+ export namespace table_6 {
103
+ export namespace defaultValue_6 {
104
+ let summary_6: string;
105
+ export { summary_6 as summary };
106
+ }
107
+ export { defaultValue_6 as defaultValue };
108
+ }
109
+ export { table_6 as table };
110
+ }
111
+ namespace responsive {
112
+ let control_7: string;
113
+ export { control_7 as control };
114
+ let description_7: string;
115
+ export { description_7 as description };
116
+ export namespace table_7 {
117
+ export namespace defaultValue_7 {
118
+ let summary_7: boolean;
119
+ export { summary_7 as summary };
120
+ }
121
+ export { defaultValue_7 as defaultValue };
122
+ }
123
+ export { table_7 as table };
124
+ }
125
+ }
126
+ namespace args {
127
+ let mode_1: string;
128
+ export { mode_1 as mode };
129
+ let direction_1: string;
130
+ export { direction_1 as direction };
131
+ let gap_1: string;
132
+ export { gap_1 as gap };
133
+ let columns_1: number;
134
+ export { columns_1 as columns };
135
+ let wrap_1: boolean;
136
+ export { wrap_1 as wrap };
137
+ let align_1: string;
138
+ export { align_1 as align };
139
+ let justify_1: string;
140
+ export { justify_1 as justify };
141
+ let responsive_1: boolean;
142
+ export { responsive_1 as responsive };
143
+ }
144
+ function render({ mode, direction, gap, columns, wrap, align, justify, responsive }: {
145
+ mode: any;
146
+ direction: any;
147
+ gap: any;
148
+ columns: any;
149
+ wrap: any;
150
+ align: any;
151
+ justify: any;
152
+ responsive: any;
153
+ }): string;
154
+ }
155
+ export default _default;
156
+ export const Default: {};
157
+ export namespace Row {
158
+ export function render_1(): string;
159
+ export { render_1 as render };
160
+ }
161
+ export namespace Column {
162
+ export function render_2(): string;
163
+ export { render_2 as render };
164
+ }
165
+ export namespace RowWithJustify {
166
+ export function render_3(): string;
167
+ export { render_3 as render };
168
+ }
169
+ export namespace GapSizes {
170
+ export function render_4(): string;
171
+ export { render_4 as render };
172
+ }
173
+ export namespace Grid {
174
+ export function render_5(): string;
175
+ export { render_5 as render };
176
+ }
177
+ export namespace GridResponsive {
178
+ export let name: string;
179
+ export function render_6(): string;
180
+ export { render_6 as render };
181
+ }
182
+ export namespace Masonry {
183
+ export function render_7(): string;
184
+ export { render_7 as render };
185
+ }
186
+ export namespace WrapMode {
187
+ let name_1: string;
188
+ export { name_1 as name };
189
+ export function render_8(): string;
190
+ export { render_8 as render };
191
+ }
192
+ export namespace AlignCenter {
193
+ export function render_9(): string;
194
+ export { render_9 as render };
195
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,411 @@
1
+ class YumeStack extends HTMLElement {
2
+ static get observedAttributes() {
3
+ return [
4
+ "direction",
5
+ "mode",
6
+ "columns",
7
+ "gap",
8
+ "wrap",
9
+ "align",
10
+ "justify",
11
+ "responsive",
12
+ ];
13
+ }
14
+
15
+ // -------------------------------------------------------------------------
16
+ // Lifecycle
17
+ // -------------------------------------------------------------------------
18
+
19
+ constructor() {
20
+ super();
21
+ this.attachShadow({ mode: "open" });
22
+ this._resizeObserver = null;
23
+ this._masonryRAF = null;
24
+ this.render();
25
+ }
26
+
27
+ connectedCallback() {
28
+ this._applyLayout();
29
+ if (this.mode === "masonry") this._initMasonry();
30
+ if (this.responsive) this._initResponsive();
31
+ }
32
+
33
+ disconnectedCallback() {
34
+ this._teardownMasonry();
35
+ }
36
+
37
+ attributeChangedCallback(name, oldValue, newValue) {
38
+ if (oldValue === newValue) return;
39
+
40
+ const wasMasonry = name === "mode" && oldValue === "masonry";
41
+ const isMasonry = this.mode === "masonry";
42
+
43
+ if (wasMasonry) this._teardownMasonry();
44
+
45
+ this._applyLayout();
46
+
47
+ if (isMasonry && this.isConnected) this._initMasonry();
48
+ if (name === "responsive" && this.responsive && this.isConnected)
49
+ this._initResponsive();
50
+ }
51
+
52
+ // -------------------------------------------------------------------------
53
+ // Getters / Setters
54
+ // -------------------------------------------------------------------------
55
+
56
+ /** Main axis direction (flex mode only). */
57
+ get direction() {
58
+ return this.getAttribute("direction") || "row";
59
+ }
60
+ set direction(val) {
61
+ this.setAttribute("direction", val);
62
+ }
63
+
64
+ /** Layout algorithm: "flex" | "grid" | "masonry". */
65
+ get mode() {
66
+ return this.getAttribute("mode") || "flex";
67
+ }
68
+ set mode(val) {
69
+ this.setAttribute("mode", val);
70
+ }
71
+
72
+ /** Number of columns (grid/masonry modes). */
73
+ get columns() {
74
+ return parseInt(this.getAttribute("columns"), 10) || 3;
75
+ }
76
+ set columns(val) {
77
+ this.setAttribute("columns", String(val));
78
+ }
79
+
80
+ /** Gap between items, maps to --spacing-* tokens. */
81
+ get gap() {
82
+ return this.getAttribute("gap") || "medium";
83
+ }
84
+ set gap(val) {
85
+ this.setAttribute("gap", val);
86
+ }
87
+
88
+ /** Allow items to wrap (flex mode only). */
89
+ get wrap() {
90
+ return this.hasAttribute("wrap");
91
+ }
92
+ set wrap(val) {
93
+ if (val) this.setAttribute("wrap", "");
94
+ else this.removeAttribute("wrap");
95
+ }
96
+
97
+ /** Cross-axis alignment. */
98
+ get align() {
99
+ return this.getAttribute("align") || "stretch";
100
+ }
101
+ set align(val) {
102
+ this.setAttribute("align", val);
103
+ }
104
+
105
+ /** Main-axis distribution (flex mode only). */
106
+ get justify() {
107
+ return this.getAttribute("justify") || "start";
108
+ }
109
+ set justify(val) {
110
+ this.setAttribute("justify", val);
111
+ }
112
+
113
+ /** Auto-reduce columns at narrow viewports. */
114
+ get responsive() {
115
+ return this.hasAttribute("responsive");
116
+ }
117
+ set responsive(val) {
118
+ if (val) this.setAttribute("responsive", "");
119
+ else this.removeAttribute("responsive");
120
+ }
121
+
122
+ // -------------------------------------------------------------------------
123
+ // Public
124
+ // -------------------------------------------------------------------------
125
+
126
+ render() {
127
+ this.shadowRoot.innerHTML = `<div class="container" part="container"><slot></slot></div>`;
128
+ this._container = this.shadowRoot.querySelector(".container");
129
+ this._slot = this.shadowRoot.querySelector("slot");
130
+ this._slot.addEventListener("slotchange", () => {
131
+ if (this.mode === "masonry") this._syncMasonryObserver();
132
+ });
133
+ }
134
+
135
+ // -------------------------------------------------------------------------
136
+ // Private
137
+ // -------------------------------------------------------------------------
138
+
139
+ _applyLayout() {
140
+ if (!this._container) return;
141
+ const sheet = this._buildStyleSheet();
142
+ this.shadowRoot.adoptedStyleSheets = [sheet];
143
+
144
+ if (this.mode === "masonry") {
145
+ this._layoutMasonry();
146
+ } else {
147
+ this._clearMasonryPositions();
148
+ }
149
+ }
150
+
151
+ _buildStyleSheet() {
152
+ const sheet = new CSSStyleSheet();
153
+ const gapValue = this._resolveGap();
154
+ const mode = this.mode;
155
+ const cols = this.columns;
156
+ const responsive = this.responsive;
157
+
158
+ let containerCSS;
159
+ let responsiveCSS = "";
160
+
161
+ if (mode === "grid") {
162
+ containerCSS = `
163
+ .container {
164
+ display: grid;
165
+ grid-template-columns: repeat(var(--component-stack-columns, ${cols}), 1fr);
166
+ gap: ${gapValue};
167
+ }
168
+ `;
169
+ if (responsive) {
170
+ responsiveCSS = this._buildResponsiveGridCSS(cols);
171
+ }
172
+ } else if (mode === "masonry") {
173
+ containerCSS = `
174
+ .container {
175
+ position: relative;
176
+ }
177
+ `;
178
+ } else {
179
+ const dir = this.direction;
180
+ const alignMap = {
181
+ start: "flex-start",
182
+ end: "flex-end",
183
+ center: "center",
184
+ stretch: "stretch",
185
+ baseline: "baseline",
186
+ };
187
+ const justifyMap = {
188
+ start: "flex-start",
189
+ end: "flex-end",
190
+ center: "center",
191
+ between: "space-between",
192
+ around: "space-around",
193
+ evenly: "space-evenly",
194
+ };
195
+
196
+ containerCSS = `
197
+ .container {
198
+ display: flex;
199
+ flex-direction: ${dir};
200
+ flex-wrap: ${this.wrap ? "wrap" : "nowrap"};
201
+ align-items: ${alignMap[this.align] || "stretch"};
202
+ justify-content: ${justifyMap[this.justify] || "flex-start"};
203
+ gap: ${gapValue};
204
+ }
205
+ `;
206
+ if (responsive && dir === "row") {
207
+ responsiveCSS = this._buildResponsiveFlexCSS();
208
+ }
209
+ }
210
+
211
+ sheet.replaceSync(`
212
+ :host {
213
+ display: block;
214
+ box-sizing: border-box;
215
+ }
216
+ ${containerCSS}
217
+ ${responsiveCSS}
218
+ `);
219
+ return sheet;
220
+ }
221
+
222
+ _buildResponsiveFlexCSS() {
223
+ return `
224
+ @media (max-width: var(--component-stack-mobile-breakpoint, 576px)) {
225
+ .container {
226
+ flex-direction: column;
227
+ }
228
+ }
229
+ `;
230
+ }
231
+
232
+ _buildResponsiveGridCSS(cols) {
233
+ const tabletCols = Math.min(2, cols);
234
+ return `
235
+ @media (max-width: var(--component-stack-tablet-breakpoint, 768px)) {
236
+ .container {
237
+ grid-template-columns: repeat(${tabletCols}, 1fr);
238
+ }
239
+ }
240
+ @media (max-width: var(--component-stack-mobile-breakpoint, 576px)) {
241
+ .container {
242
+ grid-template-columns: 1fr;
243
+ }
244
+ }
245
+ `;
246
+ }
247
+
248
+ _clearMasonryPositions() {
249
+ const items = this._getSlottedElements();
250
+ items.forEach((item) => {
251
+ item.style.position = "";
252
+ item.style.top = "";
253
+ item.style.left = "";
254
+ item.style.width = "";
255
+ });
256
+ if (this._container) {
257
+ this._container.style.height = "";
258
+ }
259
+ }
260
+
261
+ _getBreakpointValue(prop, fallback) {
262
+ const val = getComputedStyle(this).getPropertyValue(prop).trim();
263
+ return val ? parseInt(val, 10) : fallback;
264
+ }
265
+
266
+ _getResolvedColumns() {
267
+ return this._getBreakpointValue(
268
+ "--component-stack-columns",
269
+ this.columns,
270
+ );
271
+ }
272
+
273
+ _getResponsiveColumns() {
274
+ const cols = this._getResolvedColumns();
275
+ const mobileBreakpoint = this._getBreakpointValue(
276
+ "--component-stack-mobile-breakpoint",
277
+ 576,
278
+ );
279
+ const tabletBreakpoint = this._getBreakpointValue(
280
+ "--component-stack-tablet-breakpoint",
281
+ 768,
282
+ );
283
+ const width = this._container
284
+ ? this._container.offsetWidth
285
+ : window.innerWidth;
286
+
287
+ if (width <= mobileBreakpoint) return 1;
288
+ if (width <= tabletBreakpoint) return Math.min(2, cols);
289
+ return cols;
290
+ }
291
+
292
+ _getSlottedElements() {
293
+ if (!this._slot) return [];
294
+ return this._slot.assignedElements({ flatten: true });
295
+ }
296
+
297
+ _initMasonry() {
298
+ this._teardownMasonry();
299
+ this._observedItems = new Set();
300
+ this._resizeObserver = new ResizeObserver(() => {
301
+ if (this._masonryRAF) cancelAnimationFrame(this._masonryRAF);
302
+ this._masonryRAF = requestAnimationFrame(() =>
303
+ this._layoutMasonry(),
304
+ );
305
+ });
306
+ this._resizeObserver.observe(this._container);
307
+ this._syncMasonryObserver();
308
+ }
309
+
310
+ _initResponsive() {
311
+ if (this.mode !== "masonry") return;
312
+ if (!this._resizeObserver) this._initMasonry();
313
+ }
314
+
315
+ _layoutMasonry() {
316
+ const items = this._getSlottedElements();
317
+ if (!items.length || !this._container) return;
318
+
319
+ const cols = this.responsive
320
+ ? this._getResponsiveColumns()
321
+ : this._getResolvedColumns();
322
+ const gapPx = this._resolveGapPx();
323
+ const containerWidth = this._container.offsetWidth;
324
+ const colWidth = (containerWidth - gapPx * (cols - 1)) / cols;
325
+
326
+ const colHeights = new Array(cols).fill(0);
327
+
328
+ items.forEach((item) => {
329
+ const shortest = colHeights.indexOf(Math.min(...colHeights));
330
+ const x = shortest * (colWidth + gapPx);
331
+ const y = colHeights[shortest];
332
+
333
+ item.style.position = "absolute";
334
+ item.style.top = `${y}px`;
335
+ item.style.left = `${x}px`;
336
+ item.style.width = `${colWidth}px`;
337
+
338
+ colHeights[shortest] += item.offsetHeight + gapPx;
339
+ });
340
+
341
+ this._container.style.height = `${Math.max(...colHeights) - gapPx}px`;
342
+ }
343
+
344
+ _resolveGap() {
345
+ const gapMap = {
346
+ none: "var(--spacing-none)",
347
+ "x-small": "var(--spacing-x-small)",
348
+ small: "var(--spacing-small)",
349
+ medium: "var(--spacing-medium)",
350
+ large: "var(--spacing-large)",
351
+ "x-large": "var(--spacing-x-large)",
352
+ "2x-large": "var(--spacing-2x-large)",
353
+ "4x-large": "var(--spacing-4x-large)",
354
+ };
355
+ return `var(--component-stack-gap, ${gapMap[this.gap] || gapMap.medium})`;
356
+ }
357
+
358
+ _resolveGapPx() {
359
+ const temp = document.createElement("div");
360
+ temp.style.position = "absolute";
361
+ temp.style.visibility = "hidden";
362
+ temp.style.width = this._resolveGap();
363
+ this._container.appendChild(temp);
364
+ const px = temp.offsetWidth;
365
+ temp.remove();
366
+ return px;
367
+ }
368
+
369
+ _syncMasonryObserver() {
370
+ if (!this._resizeObserver) {
371
+ this._initMasonry();
372
+ return;
373
+ }
374
+
375
+ const current = new Set(this._getSlottedElements());
376
+
377
+ for (const item of this._observedItems) {
378
+ if (!current.has(item)) {
379
+ this._resizeObserver.unobserve(item);
380
+ this._observedItems.delete(item);
381
+ }
382
+ }
383
+
384
+ for (const item of current) {
385
+ if (!this._observedItems.has(item)) {
386
+ this._resizeObserver.observe(item);
387
+ this._observedItems.add(item);
388
+ }
389
+ }
390
+
391
+ this._layoutMasonry();
392
+ }
393
+
394
+ _teardownMasonry() {
395
+ if (this._resizeObserver) {
396
+ this._resizeObserver.disconnect();
397
+ this._resizeObserver = null;
398
+ this._observedItems = null;
399
+ }
400
+ if (this._masonryRAF) {
401
+ cancelAnimationFrame(this._masonryRAF);
402
+ this._masonryRAF = null;
403
+ }
404
+ }
405
+ }
406
+
407
+ if (!customElements.get("y-stack")) {
408
+ customElements.define("y-stack", YumeStack);
409
+ }
410
+
411
+ export { YumeStack };