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