bento-grid-builder 0.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,748 @@
1
+ // src/BentoGrid.tsx
2
+ import React, { useMemo as useMemo2, useEffect as useEffect2 } from "react";
3
+
4
+ // src/presets.ts
5
+ var PRESET_LAYOUTS = {
6
+ "2x2": {
7
+ name: "2x2",
8
+ columns: 2,
9
+ rows: 2,
10
+ slots: [
11
+ { name: "top-left", col: 1, row: 1 },
12
+ { name: "top-right", col: 2, row: 1 },
13
+ { name: "bottom-left", col: 1, row: 2 },
14
+ { name: "bottom-right", col: 2, row: 2 }
15
+ ]
16
+ },
17
+ "3x2": {
18
+ name: "3x2",
19
+ columns: 3,
20
+ rows: 2,
21
+ slots: [
22
+ { name: "top-1", col: 1, row: 1 },
23
+ { name: "top-2", col: 2, row: 1 },
24
+ { name: "top-3", col: 3, row: 1 },
25
+ { name: "bottom-1", col: 1, row: 2 },
26
+ { name: "bottom-2", col: 2, row: 2 },
27
+ { name: "bottom-3", col: 3, row: 2 }
28
+ ]
29
+ },
30
+ "3x3": {
31
+ name: "3x3",
32
+ columns: 3,
33
+ rows: 3,
34
+ slots: [
35
+ { name: "r1-c1", col: 1, row: 1 },
36
+ { name: "r1-c2", col: 2, row: 1 },
37
+ { name: "r1-c3", col: 3, row: 1 },
38
+ { name: "r2-c1", col: 1, row: 2 },
39
+ { name: "r2-c2", col: 2, row: 2 },
40
+ { name: "r2-c3", col: 3, row: 2 },
41
+ { name: "r3-c1", col: 1, row: 3 },
42
+ { name: "r3-c2", col: 2, row: 3 },
43
+ { name: "r3-c3", col: 3, row: 3 }
44
+ ]
45
+ },
46
+ "4x2": {
47
+ name: "4x2",
48
+ columns: 4,
49
+ rows: 2,
50
+ slots: [
51
+ { name: "top-1", col: 1, row: 1 },
52
+ { name: "top-2", col: 2, row: 1 },
53
+ { name: "top-3", col: 3, row: 1 },
54
+ { name: "top-4", col: 4, row: 1 },
55
+ { name: "bottom-1", col: 1, row: 2 },
56
+ { name: "bottom-2", col: 2, row: 2 },
57
+ { name: "bottom-3", col: 3, row: 2 },
58
+ { name: "bottom-4", col: 4, row: 2 }
59
+ ]
60
+ },
61
+ "2x1-hero-left": {
62
+ name: "2x1-hero-left",
63
+ columns: 3,
64
+ rows: 2,
65
+ slots: [
66
+ { name: "hero", col: 1, row: 1, colSpan: 2, rowSpan: 2 },
67
+ { name: "side-top", col: 3, row: 1 },
68
+ { name: "side-bottom", col: 3, row: 2 }
69
+ ]
70
+ },
71
+ "2x1-hero-right": {
72
+ name: "2x1-hero-right",
73
+ columns: 3,
74
+ rows: 2,
75
+ slots: [
76
+ { name: "side-top", col: 1, row: 1 },
77
+ { name: "side-bottom", col: 1, row: 2 },
78
+ { name: "hero", col: 2, row: 1, colSpan: 2, rowSpan: 2 }
79
+ ]
80
+ },
81
+ "dashboard-9": {
82
+ name: "dashboard-9",
83
+ columns: 3,
84
+ rows: 4,
85
+ slots: [
86
+ { name: "header-wide", col: 1, row: 1, colSpan: 2 },
87
+ { name: "header-right", col: 3, row: 1 },
88
+ { name: "hero", col: 1, row: 2, rowSpan: 2 },
89
+ { name: "mid-center", col: 2, row: 2 },
90
+ { name: "mid-right", col: 3, row: 2 },
91
+ { name: "lower-center", col: 2, row: 3 },
92
+ { name: "lower-right", col: 3, row: 3 },
93
+ { name: "footer-left", col: 1, row: 4 },
94
+ { name: "footer-wide", col: 2, row: 4, colSpan: 2 }
95
+ ]
96
+ }
97
+ };
98
+ function presetToLayout(presetName, cardIdsOrMapping) {
99
+ const preset = PRESET_LAYOUTS[presetName];
100
+ if (!preset) {
101
+ throw new Error(`Unknown preset layout: ${presetName}`);
102
+ }
103
+ let placements;
104
+ if (cardIdsOrMapping.length > 0 && typeof cardIdsOrMapping[0] === "object" && "slot" in cardIdsOrMapping[0]) {
105
+ const slotMapping = cardIdsOrMapping;
106
+ placements = slotMapping.map((mapping) => {
107
+ const slotIndex = typeof mapping.slot === "number" ? mapping.slot : preset.slots.findIndex((s) => s.name === mapping.slot);
108
+ if (slotIndex === -1 || slotIndex >= preset.slots.length) {
109
+ console.warn(
110
+ `Preset "${presetName}": slot "${mapping.slot}" not found`
111
+ );
112
+ return null;
113
+ }
114
+ const slot = preset.slots[slotIndex];
115
+ return {
116
+ cardId: mapping.cardId,
117
+ col: slot.col,
118
+ row: slot.row,
119
+ colSpan: slot.colSpan,
120
+ rowSpan: slot.rowSpan
121
+ };
122
+ }).filter((p) => p !== null);
123
+ } else {
124
+ const cardIds = cardIdsOrMapping;
125
+ placements = preset.slots.map((slot, index) => ({
126
+ cardId: cardIds[index] ?? `card-${index}`,
127
+ col: slot.col,
128
+ row: slot.row,
129
+ colSpan: slot.colSpan,
130
+ rowSpan: slot.rowSpan
131
+ }));
132
+ }
133
+ return {
134
+ columns: preset.columns,
135
+ rows: preset.rows,
136
+ placements
137
+ };
138
+ }
139
+ function getPresetSlotNames(presetName) {
140
+ const preset = PRESET_LAYOUTS[presetName];
141
+ if (!preset) {
142
+ throw new Error(`Unknown preset layout: ${presetName}`);
143
+ }
144
+ return preset.slots.map((s, i) => s.name ?? `slot-${i}`);
145
+ }
146
+ function isPresetName(value) {
147
+ return typeof value === "string" && value in PRESET_LAYOUTS;
148
+ }
149
+
150
+ // src/BentoCard.tsx
151
+ import { jsx } from "react/jsx-runtime";
152
+ var BentoCard = ({
153
+ children,
154
+ className = "",
155
+ style
156
+ }) => /* @__PURE__ */ jsx(
157
+ "div",
158
+ {
159
+ className: `bento-card ${className}`,
160
+ style: {
161
+ backgroundColor: "var(--bento-card-bg, transparent)",
162
+ borderRadius: "var(--bento-card-radius, 12px)",
163
+ padding: "var(--bento-card-padding, 16px)",
164
+ border: "var(--bento-card-border, 1px solid rgba(128, 128, 128, 0.2))",
165
+ height: "100%",
166
+ boxSizing: "border-box",
167
+ ...style
168
+ },
169
+ children
170
+ }
171
+ );
172
+
173
+ // src/utils.ts
174
+ function createSimpleLayout(columns, cardIds, gap) {
175
+ const placements = cardIds.map((cardId, index) => ({
176
+ cardId,
177
+ col: index % columns + 1,
178
+ row: Math.floor(index / columns) + 1
179
+ }));
180
+ return {
181
+ columns,
182
+ gap,
183
+ placements
184
+ };
185
+ }
186
+ function layoutBuilder(columns) {
187
+ const config = {
188
+ columns,
189
+ placements: []
190
+ };
191
+ const builder = {
192
+ gap(value) {
193
+ config.gap = value;
194
+ return builder;
195
+ },
196
+ columnGap(value) {
197
+ config.columnGap = value;
198
+ return builder;
199
+ },
200
+ rowGap(value) {
201
+ config.rowGap = value;
202
+ return builder;
203
+ },
204
+ rows(value) {
205
+ config.rows = value;
206
+ return builder;
207
+ },
208
+ place(cardId, col, row, options) {
209
+ config.placements.push({
210
+ cardId,
211
+ col,
212
+ row,
213
+ ...options
214
+ });
215
+ return builder;
216
+ },
217
+ build() {
218
+ return config;
219
+ }
220
+ };
221
+ return builder;
222
+ }
223
+ function validateLayout(layout) {
224
+ const errors = [];
225
+ if (layout.columns < 1) {
226
+ errors.push("columns must be at least 1");
227
+ }
228
+ if (layout.rows !== void 0 && layout.rows < 1) {
229
+ errors.push("rows must be at least 1");
230
+ }
231
+ if (layout.gap !== void 0 && layout.gap < 0) {
232
+ errors.push("gap cannot be negative");
233
+ }
234
+ const occupied = /* @__PURE__ */ new Set();
235
+ for (const placement of layout.placements) {
236
+ const colSpan = placement.colSpan ?? 1;
237
+ const rowSpan = placement.rowSpan ?? 1;
238
+ if (placement.col < 1 || placement.col + colSpan - 1 > layout.columns) {
239
+ errors.push(
240
+ `Card "${placement.cardId}" exceeds column bounds (col: ${placement.col}, span: ${colSpan})`
241
+ );
242
+ }
243
+ if (placement.row < 1) {
244
+ errors.push(
245
+ `Card "${placement.cardId}" has invalid row: ${placement.row}`
246
+ );
247
+ }
248
+ for (let c = placement.col; c < placement.col + colSpan; c++) {
249
+ for (let r = placement.row; r < placement.row + rowSpan; r++) {
250
+ const key = `${c},${r}`;
251
+ if (occupied.has(key)) {
252
+ errors.push(
253
+ `Card "${placement.cardId}" overlaps at position (${c}, ${r})`
254
+ );
255
+ }
256
+ occupied.add(key);
257
+ }
258
+ }
259
+ }
260
+ return errors;
261
+ }
262
+
263
+ // src/hooks.ts
264
+ import { useMemo, useRef, useState, useEffect } from "react";
265
+ function useStableValue(value) {
266
+ const ref = useRef(value);
267
+ const prevJson = useRef("");
268
+ const json = JSON.stringify(value, (key, val) => {
269
+ if (typeof val === "function") {
270
+ return val.toString();
271
+ }
272
+ return val;
273
+ });
274
+ if (json !== prevJson.current) {
275
+ ref.current = value;
276
+ prevJson.current = json;
277
+ }
278
+ return ref.current;
279
+ }
280
+ function isResponsiveConfig(layout) {
281
+ return typeof layout === "object" && "default" in layout && "breakpoints" in layout;
282
+ }
283
+ function useResponsiveLayout(config) {
284
+ const [width, setWidth] = useState(
285
+ () => typeof window !== "undefined" ? window.innerWidth : 0
286
+ );
287
+ useEffect(() => {
288
+ if (typeof window === "undefined") return;
289
+ const handleResize = () => setWidth(window.innerWidth);
290
+ window.addEventListener("resize", handleResize);
291
+ return () => window.removeEventListener("resize", handleResize);
292
+ }, []);
293
+ return useMemo(() => {
294
+ if (!isResponsiveConfig(config)) {
295
+ return config;
296
+ }
297
+ const sortedBreakpoints = [...config.breakpoints].sort(
298
+ (a, b) => b.minWidth - a.minWidth
299
+ );
300
+ for (const bp of sortedBreakpoints) {
301
+ if (width >= bp.minWidth) {
302
+ return bp.layout;
303
+ }
304
+ }
305
+ return config.default;
306
+ }, [config, width]);
307
+ }
308
+ function useWindowWidth(debounceMs = 100) {
309
+ const [width, setWidth] = useState(
310
+ () => typeof window !== "undefined" ? window.innerWidth : 0
311
+ );
312
+ useEffect(() => {
313
+ if (typeof window === "undefined") return;
314
+ let timeoutId;
315
+ const handleResize = () => {
316
+ clearTimeout(timeoutId);
317
+ timeoutId = setTimeout(() => setWidth(window.innerWidth), debounceMs);
318
+ };
319
+ window.addEventListener("resize", handleResize);
320
+ return () => {
321
+ clearTimeout(timeoutId);
322
+ window.removeEventListener("resize", handleResize);
323
+ };
324
+ }, [debounceMs]);
325
+ return width;
326
+ }
327
+ function useCardDefinitions(definitions) {
328
+ const stableDefinitions = useStableValue(definitions);
329
+ return useMemo(() => {
330
+ return Object.entries(stableDefinitions).map(([id, def]) => ({
331
+ id,
332
+ ...def
333
+ }));
334
+ }, [stableDefinitions]);
335
+ }
336
+ function useDataMapping(mappings) {
337
+ const stableMappings = useStableValue(mappings);
338
+ return useMemo(() => {
339
+ return Object.entries(stableMappings).map(([cardId, propsSelector]) => ({
340
+ cardId,
341
+ propsSelector
342
+ }));
343
+ }, [stableMappings]);
344
+ }
345
+ function useLayout(config, cardIds) {
346
+ return useMemo(() => {
347
+ if (isPresetName(config)) {
348
+ if (!cardIds) {
349
+ throw new Error("cardIds required when using preset layout name");
350
+ }
351
+ return presetToLayout(config, cardIds);
352
+ }
353
+ return config;
354
+ }, [config, cardIds]);
355
+ }
356
+
357
+ // src/BentoGrid.tsx
358
+ import { jsx as jsx2 } from "react/jsx-runtime";
359
+ var DefaultCardWrapper = ({
360
+ children,
361
+ className,
362
+ style
363
+ }) => /* @__PURE__ */ jsx2(BentoCard, { className, style, children });
364
+ var DefaultLoadingComponent = () => /* @__PURE__ */ jsx2(
365
+ "div",
366
+ {
367
+ className: "bento-card-loading",
368
+ style: {
369
+ width: "100%",
370
+ height: "100%",
371
+ minHeight: 100,
372
+ backgroundColor: "var(--bento-loading-bg, rgba(128, 128, 128, 0.1))",
373
+ borderRadius: "var(--bento-card-radius, 12px)",
374
+ animation: "bento-pulse 1.5s ease-in-out infinite"
375
+ },
376
+ "aria-label": "Loading",
377
+ role: "status"
378
+ }
379
+ );
380
+ function useGridStyles() {
381
+ useEffect2(() => {
382
+ if (typeof document === "undefined") return;
383
+ const styleId = "bento-grid-styles";
384
+ if (document.getElementById(styleId)) return;
385
+ const style = document.createElement("style");
386
+ style.id = styleId;
387
+ style.textContent = `
388
+ .bento-grid {
389
+ display: grid;
390
+ grid-template-columns: repeat(var(--bento-columns, 3), 1fr);
391
+ grid-template-rows: repeat(var(--bento-rows, 2), minmax(0, 1fr));
392
+ column-gap: var(--bento-column-gap, var(--bento-gap, 16px));
393
+ row-gap: var(--bento-row-gap, var(--bento-gap, 16px));
394
+ }
395
+ .bento-cell {
396
+ grid-column: var(--bento-col, 1) / span var(--bento-col-span, 1);
397
+ grid-row: var(--bento-row, 1) / span var(--bento-row-span, 1);
398
+ }
399
+ `;
400
+ document.head.appendChild(style);
401
+ }, []);
402
+ }
403
+ function useAnimationStyles() {
404
+ useEffect2(() => {
405
+ if (typeof document === "undefined") return;
406
+ const styleId = "bento-grid-animations";
407
+ if (document.getElementById(styleId)) return;
408
+ const style = document.createElement("style");
409
+ style.id = styleId;
410
+ style.textContent = `
411
+ @keyframes bento-pulse {
412
+ 0%, 100% { opacity: 1; }
413
+ 50% { opacity: 0.5; }
414
+ }
415
+ @keyframes bento-fade-in {
416
+ from { opacity: 0; transform: scale(0.95); }
417
+ to { opacity: 1; transform: scale(1); }
418
+ }
419
+ .bento-cell-animated {
420
+ animation: bento-fade-in var(--bento-animation-duration, 300ms) ease-out;
421
+ }
422
+ `;
423
+ document.head.appendChild(style);
424
+ }, []);
425
+ }
426
+ var CardErrorBoundary = class extends React.Component {
427
+ constructor(props) {
428
+ super(props);
429
+ this.state = {
430
+ hasError: false,
431
+ errorKey: `${props.cardId}-${props.resetKey ?? ""}`
432
+ };
433
+ }
434
+ static getDerivedStateFromError() {
435
+ return { hasError: true };
436
+ }
437
+ static getDerivedStateFromProps(props, state) {
438
+ const newKey = `${props.cardId}-${props.resetKey ?? ""}`;
439
+ if (newKey !== state.errorKey) {
440
+ return { hasError: false, errorKey: newKey };
441
+ }
442
+ return null;
443
+ }
444
+ componentDidCatch(error) {
445
+ this.props.onError?.(this.props.cardId, error);
446
+ }
447
+ render() {
448
+ if (this.state.hasError) {
449
+ return /* @__PURE__ */ jsx2(
450
+ "div",
451
+ {
452
+ className: "bento-card-error",
453
+ role: "alert",
454
+ style: { color: "#ef4444", padding: 16 },
455
+ children: "Failed to render card"
456
+ }
457
+ );
458
+ }
459
+ return this.props.children;
460
+ }
461
+ };
462
+ function BentoGrid({
463
+ layout,
464
+ cards,
465
+ data,
466
+ dataMapping,
467
+ className = "",
468
+ style,
469
+ cardWrapper,
470
+ onCardError,
471
+ ariaLabel,
472
+ ariaLabelledBy,
473
+ animated = false,
474
+ animationDuration = 300,
475
+ cellClassName
476
+ }) {
477
+ useGridStyles();
478
+ useAnimationStyles();
479
+ const responsiveLayout = useResponsiveLayout(layout);
480
+ const CardWrapper = cardWrapper ?? DefaultCardWrapper;
481
+ const resolvedLayout = useMemo2(() => {
482
+ if (isPresetName(responsiveLayout)) {
483
+ const cardIds = dataMapping.map((m) => m.cardId);
484
+ return presetToLayout(responsiveLayout, cardIds);
485
+ }
486
+ return responsiveLayout;
487
+ }, [responsiveLayout, dataMapping]);
488
+ const cardMap = useMemo2(() => {
489
+ const map = /* @__PURE__ */ new Map();
490
+ cards.forEach((card) => map.set(card.id, card));
491
+ return map;
492
+ }, [cards]);
493
+ const dataMappingMap = useMemo2(() => {
494
+ const map = /* @__PURE__ */ new Map();
495
+ dataMapping.forEach((mapping) => map.set(mapping.cardId, mapping));
496
+ return map;
497
+ }, [dataMapping]);
498
+ useMemo2(() => {
499
+ if (process.env.NODE_ENV !== "production") {
500
+ const errors = validateLayout(resolvedLayout);
501
+ if (errors.length > 0) {
502
+ console.warn("BentoGrid layout validation errors:", errors);
503
+ }
504
+ for (const placement of resolvedLayout.placements) {
505
+ if (!cards.some((c) => c.id === placement.cardId)) {
506
+ console.warn(
507
+ `BentoGrid: Card "${placement.cardId}" in layout but not in cards array`
508
+ );
509
+ }
510
+ }
511
+ for (const placement of resolvedLayout.placements) {
512
+ if (!dataMapping.some((m) => m.cardId === placement.cardId)) {
513
+ console.warn(
514
+ `BentoGrid: Card "${placement.cardId}" has no data mapping`
515
+ );
516
+ }
517
+ }
518
+ }
519
+ }, [resolvedLayout, cards, dataMapping]);
520
+ const {
521
+ columns,
522
+ rows,
523
+ gap = 16,
524
+ columnGap,
525
+ rowGap,
526
+ placements
527
+ } = resolvedLayout;
528
+ const calculatedRows = rows ?? Math.max(...placements.map((p) => p.row + (p.rowSpan ?? 1) - 1), 1);
529
+ const gridStyle = {
530
+ "--bento-columns": columns,
531
+ "--bento-rows": calculatedRows,
532
+ "--bento-gap": `${gap}px`,
533
+ "--bento-column-gap": `${columnGap ?? gap}px`,
534
+ "--bento-row-gap": `${rowGap ?? gap}px`,
535
+ ...animated && { "--bento-animation-duration": `${animationDuration}ms` },
536
+ ...style
537
+ };
538
+ const getCellClassName = (cardId) => {
539
+ const baseClass = `bento-cell ${animated ? "bento-cell-animated" : ""}`;
540
+ if (!cellClassName) return baseClass;
541
+ if (typeof cellClassName === "function")
542
+ return `${baseClass} ${cellClassName(cardId)}`;
543
+ return `${baseClass} ${cellClassName}`;
544
+ };
545
+ return /* @__PURE__ */ jsx2(
546
+ "div",
547
+ {
548
+ className: `bento-grid ${className}`,
549
+ style: gridStyle,
550
+ role: "region",
551
+ "aria-label": ariaLabel ?? (ariaLabelledBy ? void 0 : "Dashboard grid"),
552
+ "aria-labelledby": ariaLabelledBy,
553
+ children: placements.map((placement) => {
554
+ const card = cardMap.get(placement.cardId);
555
+ const mapping = dataMappingMap.get(placement.cardId);
556
+ if (!card) {
557
+ console.warn(`BentoGrid: Card "${placement.cardId}" not found`);
558
+ return null;
559
+ }
560
+ const cardProps = mapping ? mapping.propsSelector(data) : {};
561
+ const resetKey = JSON.stringify(cardProps);
562
+ const colSpan = placement.colSpan ?? card.colSpan ?? 1;
563
+ const rowSpan = placement.rowSpan ?? card.rowSpan ?? 1;
564
+ const cellStyle = {
565
+ "--bento-col": placement.col,
566
+ "--bento-row": placement.row,
567
+ "--bento-col-span": colSpan,
568
+ "--bento-row-span": rowSpan
569
+ };
570
+ const CardComponent = card.component;
571
+ return /* @__PURE__ */ jsx2(
572
+ "div",
573
+ {
574
+ className: getCellClassName(placement.cardId),
575
+ style: cellStyle,
576
+ "data-card-id": placement.cardId,
577
+ children: /* @__PURE__ */ jsx2(
578
+ CardErrorBoundary,
579
+ {
580
+ cardId: placement.cardId,
581
+ onError: onCardError,
582
+ resetKey,
583
+ children: /* @__PURE__ */ jsx2(CardWrapper, { cardId: placement.cardId, children: /* @__PURE__ */ jsx2(CardComponent, { ...cardProps }) })
584
+ }
585
+ )
586
+ },
587
+ `${placement.cardId}-${placement.col}-${placement.row}`
588
+ );
589
+ })
590
+ }
591
+ );
592
+ }
593
+ function isCardVisible(card, data) {
594
+ if (card.visible === void 0) return true;
595
+ if (typeof card.visible === "function") return card.visible(data);
596
+ return card.visible;
597
+ }
598
+ function isCardLoading(card, data) {
599
+ if (card.loading === void 0) return false;
600
+ if (typeof card.loading === "function") return card.loading(data);
601
+ return card.loading;
602
+ }
603
+ function UnifiedBentoGrid({
604
+ layout,
605
+ cards,
606
+ data,
607
+ className = "",
608
+ style,
609
+ cardWrapper,
610
+ onCardError,
611
+ ariaLabel,
612
+ ariaLabelledBy,
613
+ animated = false,
614
+ animationDuration = 300,
615
+ loadingComponent: GlobalLoadingComponent = DefaultLoadingComponent,
616
+ cellClassName
617
+ }) {
618
+ useGridStyles();
619
+ useAnimationStyles();
620
+ const responsiveLayout = useResponsiveLayout(layout);
621
+ const CardWrapper = cardWrapper ?? DefaultCardWrapper;
622
+ const visibleCards = useMemo2(() => {
623
+ return cards.filter((card) => isCardVisible(card, data));
624
+ }, [cards, data]);
625
+ const resolvedLayout = useMemo2(() => {
626
+ if (isPresetName(responsiveLayout)) {
627
+ const cardIds = visibleCards.map((c) => c.id);
628
+ return presetToLayout(responsiveLayout, cardIds);
629
+ }
630
+ return responsiveLayout;
631
+ }, [responsiveLayout, visibleCards]);
632
+ const cardMap = useMemo2(() => {
633
+ const map = /* @__PURE__ */ new Map();
634
+ visibleCards.forEach((card) => map.set(card.id, card));
635
+ return map;
636
+ }, [visibleCards]);
637
+ useMemo2(() => {
638
+ if (process.env.NODE_ENV !== "production") {
639
+ const errors = validateLayout(resolvedLayout);
640
+ if (errors.length > 0) {
641
+ console.warn("UnifiedBentoGrid layout validation errors:", errors);
642
+ }
643
+ for (const placement of resolvedLayout.placements) {
644
+ if (!visibleCards.some((c) => c.id === placement.cardId)) {
645
+ console.warn(
646
+ `UnifiedBentoGrid: Card "${placement.cardId}" in layout but not in cards array`
647
+ );
648
+ }
649
+ }
650
+ }
651
+ }, [resolvedLayout, visibleCards]);
652
+ const {
653
+ columns,
654
+ rows,
655
+ gap = 16,
656
+ columnGap,
657
+ rowGap,
658
+ placements
659
+ } = resolvedLayout;
660
+ const calculatedRows = rows ?? Math.max(...placements.map((p) => p.row + (p.rowSpan ?? 1) - 1), 1);
661
+ const gridStyle = {
662
+ "--bento-columns": columns,
663
+ "--bento-rows": calculatedRows,
664
+ "--bento-gap": `${gap}px`,
665
+ "--bento-column-gap": `${columnGap ?? gap}px`,
666
+ "--bento-row-gap": `${rowGap ?? gap}px`,
667
+ ...animated && { "--bento-animation-duration": `${animationDuration}ms` },
668
+ ...style
669
+ };
670
+ const getCellClassName = (cardId) => {
671
+ const baseClass = `bento-cell ${animated ? "bento-cell-animated" : ""}`;
672
+ if (!cellClassName) return baseClass;
673
+ if (typeof cellClassName === "function")
674
+ return `${baseClass} ${cellClassName(cardId)}`;
675
+ return `${baseClass} ${cellClassName}`;
676
+ };
677
+ return /* @__PURE__ */ jsx2(
678
+ "div",
679
+ {
680
+ className: `bento-grid ${className}`,
681
+ style: gridStyle,
682
+ role: "region",
683
+ "aria-label": ariaLabel ?? (ariaLabelledBy ? void 0 : "Dashboard grid"),
684
+ "aria-labelledby": ariaLabelledBy,
685
+ children: placements.map((placement) => {
686
+ const card = cardMap.get(placement.cardId);
687
+ if (!card) {
688
+ if (process.env.NODE_ENV !== "production") {
689
+ console.warn(
690
+ `UnifiedBentoGrid: Card "${placement.cardId}" not found`
691
+ );
692
+ }
693
+ return null;
694
+ }
695
+ const loading = isCardLoading(card, data);
696
+ const cardProps = card.propsSelector(data);
697
+ const resetKey = JSON.stringify(cardProps);
698
+ const colSpan = placement.colSpan ?? card.colSpan ?? 1;
699
+ const rowSpan = placement.rowSpan ?? card.rowSpan ?? 1;
700
+ const cellStyle = {
701
+ "--bento-col": placement.col,
702
+ "--bento-row": placement.row,
703
+ "--bento-col-span": colSpan,
704
+ "--bento-row-span": rowSpan
705
+ };
706
+ const CardComponent = card.component;
707
+ const LoadingComponent = card.loadingComponent ?? GlobalLoadingComponent;
708
+ return /* @__PURE__ */ jsx2(
709
+ "div",
710
+ {
711
+ className: getCellClassName(placement.cardId),
712
+ style: cellStyle,
713
+ "data-card-id": placement.cardId,
714
+ children: /* @__PURE__ */ jsx2(
715
+ CardErrorBoundary,
716
+ {
717
+ cardId: placement.cardId,
718
+ onError: onCardError,
719
+ resetKey,
720
+ children: /* @__PURE__ */ jsx2(CardWrapper, { cardId: placement.cardId, children: loading ? /* @__PURE__ */ jsx2(LoadingComponent, {}) : /* @__PURE__ */ jsx2(CardComponent, { ...cardProps }) })
721
+ }
722
+ )
723
+ },
724
+ `${placement.cardId}-${placement.col}-${placement.row}`
725
+ );
726
+ })
727
+ }
728
+ );
729
+ }
730
+ export {
731
+ BentoCard,
732
+ BentoGrid,
733
+ PRESET_LAYOUTS,
734
+ UnifiedBentoGrid,
735
+ createSimpleLayout,
736
+ getPresetSlotNames,
737
+ isPresetName,
738
+ isResponsiveConfig,
739
+ layoutBuilder,
740
+ presetToLayout,
741
+ useCardDefinitions,
742
+ useDataMapping,
743
+ useLayout,
744
+ useResponsiveLayout,
745
+ useWindowWidth,
746
+ validateLayout
747
+ };
748
+ //# sourceMappingURL=index.mjs.map