@qiiqa/bob-ui-react-layout 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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1190 @@
1
+ // src/context/LayoutContext.tsx
2
+ import { createContext, useContext, useState, useEffect, useRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var DEFAULT_RESPONSIVENESS_CONFIG = {
5
+ small: { page: 1, group: 1 },
6
+ medium: { page: 12, group: 12 },
7
+ large: { page: 18, group: 12 },
8
+ extraLarge: { page: 24, group: 12 }
9
+ };
10
+ var LayoutContext = createContext(void 0);
11
+ function useLayoutContext() {
12
+ const context = useContext(LayoutContext);
13
+ if (!context) {
14
+ throw new Error("useLayoutContext must be used within a LayoutProvider or BobForm");
15
+ }
16
+ return context;
17
+ }
18
+ var LayoutProvider = ({
19
+ children,
20
+ initialDesignMode = false,
21
+ initialDesignTabs = false,
22
+ initialDesignGroups = false,
23
+ initialDesignFields = false,
24
+ initialLayoutState = {},
25
+ initialPageCols = 12,
26
+ initialGroupCols = 12,
27
+ initialResponsivenessConfig = DEFAULT_RESPONSIVENESS_CONFIG,
28
+ onLayoutChange,
29
+ onPageColsChange,
30
+ onGroupColsChange,
31
+ onResponsivenessConfigChange
32
+ }) => {
33
+ const [designMode, setDesignMode] = useState(initialDesignMode);
34
+ const [designTabs, setDesignTabs] = useState(initialDesignTabs);
35
+ const [designGroups, setDesignGroups] = useState(initialDesignGroups);
36
+ const [designFields, setDesignFields] = useState(initialDesignFields);
37
+ const [layoutState, setLayoutState] = useState(initialLayoutState);
38
+ const [selectedItemIds, setSelectedItemIds] = useState([]);
39
+ const [isDraggingActive, setDraggingActive] = useState(false);
40
+ const [dragOverId, setDragOverId] = useState(null);
41
+ const [dragSide, setDragSide] = useState(null);
42
+ const initialPageColsRef = useRef(initialPageCols);
43
+ const initialGroupColsRef = useRef(initialGroupCols);
44
+ const initialLayoutStateRef = useRef(initialLayoutState);
45
+ useEffect(() => {
46
+ initialPageColsRef.current = initialPageCols;
47
+ }, [initialPageCols]);
48
+ useEffect(() => {
49
+ initialGroupColsRef.current = initialGroupCols;
50
+ }, [initialGroupCols]);
51
+ useEffect(() => {
52
+ initialLayoutStateRef.current = initialLayoutState;
53
+ }, [initialLayoutState]);
54
+ const [pageCols, setPageColsState] = useState(initialPageCols);
55
+ const [groupCols, setGroupColsState] = useState(initialGroupCols);
56
+ const [responsivenessConfig, setResponsivenessConfigState] = useState(initialResponsivenessConfig);
57
+ const [currentBreakpoint, setCurrentBreakpoint] = useState("");
58
+ useEffect(() => {
59
+ const handleResize = () => {
60
+ const width = document.documentElement.clientWidth;
61
+ let category = "small";
62
+ if (width < 600) category = "small";
63
+ else if (width < 1024) category = "medium";
64
+ else if (width < 1600) category = "large";
65
+ else category = "extraLarge";
66
+ if (category !== currentBreakpoint) {
67
+ setCurrentBreakpoint(category);
68
+ const tier = responsivenessConfig[category];
69
+ setPageColsState(tier.page);
70
+ setGroupColsState(tier.group);
71
+ }
72
+ };
73
+ window.addEventListener("resize", handleResize);
74
+ handleResize();
75
+ return () => window.removeEventListener("resize", handleResize);
76
+ }, [responsivenessConfig, currentBreakpoint]);
77
+ useEffect(() => {
78
+ if (onResponsivenessConfigChange) {
79
+ onResponsivenessConfigChange(responsivenessConfig);
80
+ }
81
+ }, [responsivenessConfig, onResponsivenessConfigChange]);
82
+ const setResponsivenessConfig = (newConfig) => {
83
+ setResponsivenessConfigState(newConfig);
84
+ };
85
+ const setDragOverState = (id, side) => {
86
+ setDragOverId(id);
87
+ setDragSide(side);
88
+ };
89
+ const toggleItemSelection = (id, multi) => {
90
+ setSelectedItemIds((prev) => {
91
+ if (multi) {
92
+ return prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id];
93
+ }
94
+ return prev.includes(id) && prev.length === 1 ? [] : [id];
95
+ });
96
+ };
97
+ const clearItemSelection = () => {
98
+ setSelectedItemIds([]);
99
+ };
100
+ useEffect(() => {
101
+ setDesignMode(initialDesignMode);
102
+ }, [initialDesignMode]);
103
+ useEffect(() => {
104
+ setDesignTabs(initialDesignTabs);
105
+ }, [initialDesignTabs]);
106
+ useEffect(() => {
107
+ setDesignGroups(initialDesignGroups);
108
+ }, [initialDesignGroups]);
109
+ useEffect(() => {
110
+ setDesignFields(initialDesignFields);
111
+ }, [initialDesignFields]);
112
+ useEffect(() => {
113
+ if (initialPageCols !== void 0 && initialPageCols !== pageCols) setPageColsState(initialPageCols);
114
+ }, [initialPageCols]);
115
+ useEffect(() => {
116
+ if (initialGroupCols !== void 0 && initialGroupCols !== groupCols) setGroupColsState(initialGroupCols);
117
+ }, [initialGroupCols]);
118
+ useEffect(() => {
119
+ if (initialLayoutState && Object.keys(initialLayoutState).length > 0) {
120
+ setLayoutState(initialLayoutState);
121
+ }
122
+ }, [initialLayoutState]);
123
+ useEffect(() => {
124
+ if (onLayoutChange && layoutState !== initialLayoutStateRef.current) {
125
+ onLayoutChange(layoutState);
126
+ }
127
+ }, [layoutState, onLayoutChange]);
128
+ useEffect(() => {
129
+ if (onPageColsChange && pageCols !== initialPageColsRef.current) {
130
+ onPageColsChange(pageCols);
131
+ }
132
+ }, [pageCols, onPageColsChange]);
133
+ useEffect(() => {
134
+ if (onGroupColsChange && groupCols !== initialGroupColsRef.current) {
135
+ onGroupColsChange(groupCols);
136
+ }
137
+ }, [groupCols, onGroupColsChange]);
138
+ const loadLayoutState = (state) => {
139
+ setLayoutState(state);
140
+ };
141
+ const reorderItemWithinContainer = (containerId, activeId, overId, fallbackItems, insertAfter) => {
142
+ setLayoutState((prev) => {
143
+ const items = prev[containerId] || fallbackItems || [];
144
+ const oldIndex = items.indexOf(activeId);
145
+ if (oldIndex === -1) return prev;
146
+ const newItems = [...items];
147
+ newItems.splice(oldIndex, 1);
148
+ if (overId === "__start__") {
149
+ newItems.unshift(activeId);
150
+ return { ...prev, [containerId]: newItems };
151
+ }
152
+ if (overId === "__end__") {
153
+ newItems.push(activeId);
154
+ return { ...prev, [containerId]: newItems };
155
+ }
156
+ const newIndexRaw = items.indexOf(overId);
157
+ if (newIndexRaw === -1) return prev;
158
+ const adjustedNewIndex = newItems.indexOf(overId);
159
+ const targetIndex = insertAfter ? adjustedNewIndex + 1 : adjustedNewIndex;
160
+ newItems.splice(targetIndex, 0, activeId);
161
+ return { ...prev, [containerId]: newItems };
162
+ });
163
+ };
164
+ const moveItemBetweenContainers = (sourceContainerId, destContainerId, activeId, overId, fallbackItems, insertAfter) => {
165
+ setLayoutState((prev) => {
166
+ const sourceItems = [...prev[sourceContainerId] || fallbackItems || []];
167
+ const destItems = [...prev[destContainerId] || []];
168
+ const sourceIndex = sourceItems.indexOf(activeId);
169
+ if (sourceIndex === -1) return prev;
170
+ sourceItems.splice(sourceIndex, 1);
171
+ if (overId === "__start__") {
172
+ destItems.unshift(activeId);
173
+ } else if (overId === "__end__") {
174
+ destItems.push(activeId);
175
+ } else {
176
+ const destIndex = destItems.indexOf(overId);
177
+ if (destIndex === -1) {
178
+ destItems.push(activeId);
179
+ } else {
180
+ const targetIndex = insertAfter ? destIndex + 1 : destIndex;
181
+ destItems.splice(targetIndex, 0, activeId);
182
+ }
183
+ }
184
+ return {
185
+ ...prev,
186
+ [sourceContainerId]: sourceItems,
187
+ [destContainerId]: destItems
188
+ };
189
+ });
190
+ };
191
+ return /* @__PURE__ */ jsx(LayoutContext.Provider, { value: {
192
+ designMode,
193
+ designTabs,
194
+ designGroups,
195
+ designFields,
196
+ layoutState,
197
+ selectedItemIds,
198
+ isDraggingActive,
199
+ dragOverId,
200
+ dragSide,
201
+ setLayoutState,
202
+ toggleItemSelection,
203
+ clearItemSelection,
204
+ setDraggingActive,
205
+ setDragOverState,
206
+ loadLayoutState,
207
+ reorderItemWithinContainer,
208
+ moveItemBetweenContainers,
209
+ setDesignMode,
210
+ setDesignTabs,
211
+ setDesignGroups,
212
+ setDesignFields,
213
+ pageCols,
214
+ groupCols,
215
+ setPageCols: setPageColsState,
216
+ setGroupCols: setGroupColsState,
217
+ responsivenessConfig,
218
+ setResponsivenessConfig,
219
+ currentBreakpoint
220
+ }, children });
221
+ };
222
+
223
+ // src/components/BobForm.tsx
224
+ import React2 from "react";
225
+ import { DndContext, closestCenter, pointerWithin, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
226
+ import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
227
+ import { jsx as jsx2 } from "react/jsx-runtime";
228
+ var InnerFormWrapper = ({ children, className }) => {
229
+ const {
230
+ moveItemBetweenContainers,
231
+ reorderItemWithinContainer,
232
+ setDragOverState,
233
+ setDraggingActive,
234
+ clearItemSelection
235
+ } = useLayoutContext();
236
+ const lastEmit = React2.useRef({ id: null, side: null });
237
+ const pointerRef = React2.useRef({ x: 0, y: 0 });
238
+ React2.useEffect(() => {
239
+ const handlePointerMove = (e) => {
240
+ pointerRef.current = { x: e.clientX, y: e.clientY };
241
+ };
242
+ window.addEventListener("pointermove", handlePointerMove);
243
+ return () => window.removeEventListener("pointermove", handlePointerMove);
244
+ }, []);
245
+ const sensors = useSensors(
246
+ useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
247
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
248
+ );
249
+ const handleDragStart = (_event) => {
250
+ setDraggingActive(true);
251
+ };
252
+ const handleDragMove = (event) => {
253
+ const { active, over } = event;
254
+ if (over?.data?.current?.type === "GapDropZone") {
255
+ if (lastEmit.current.id !== null) {
256
+ lastEmit.current = { id: null, side: null };
257
+ setDragOverState(null, null);
258
+ }
259
+ return;
260
+ }
261
+ if (!over || active.id === over.id) {
262
+ if (lastEmit.current.id !== null) {
263
+ lastEmit.current = { id: null, side: null };
264
+ setDragOverState(null, null);
265
+ }
266
+ return;
267
+ }
268
+ const activeType = active.data.current?.type;
269
+ const overType = over.data.current?.type;
270
+ if (activeType === "Panel" && overType === "Item") {
271
+ return;
272
+ }
273
+ const overRect = over.rect;
274
+ const activeRect = active.rect.current.translated;
275
+ if (!overRect || !activeRect) return;
276
+ const overCenterX = overRect.left + overRect.width / 2;
277
+ const pointerX = pointerRef.current.x;
278
+ const dx = pointerX - overCenterX;
279
+ let side;
280
+ side = dx > 0 ? "right" : "left";
281
+ let targetId = over.id;
282
+ let finalSide = side;
283
+ if (over.data.current?.type === "Item" || over.data.current?.type === "Panel") {
284
+ const containerId = over.data.current.sortable.containerId;
285
+ const items = over.data.current.sortable.items;
286
+ const overIndex = items.indexOf(over.id);
287
+ if (side === "left") {
288
+ if (overIndex > 0) {
289
+ targetId = `gap-${containerId}-after-${items[overIndex - 1]}`;
290
+ } else {
291
+ targetId = `gap-${containerId}-start`;
292
+ }
293
+ } else {
294
+ targetId = `gap-${containerId}-after-${over.id}`;
295
+ }
296
+ finalSide = null;
297
+ }
298
+ if (lastEmit.current.id !== targetId || lastEmit.current.side !== side) {
299
+ lastEmit.current = { id: targetId, side };
300
+ setDragOverState(targetId, finalSide);
301
+ }
302
+ };
303
+ const handleDragEnd = (event) => {
304
+ const currentSide = lastEmit.current.side;
305
+ lastEmit.current = { id: null, side: null };
306
+ setDragOverState(null, null);
307
+ setDraggingActive(false);
308
+ const { active, over } = event;
309
+ if (!over) return;
310
+ const overData = over.data.current;
311
+ const activeContainer = active.data.current?.sortable?.containerId;
312
+ const overContainer = over.data.current?.sortable?.containerId || over.id;
313
+ const fallbackItems = active.data.current?.sortable?.items || [];
314
+ if (!activeContainer || !overContainer) return;
315
+ if (overData?.type === "Item" || overData?.type === "Panel") {
316
+ const items = overData.sortable.items;
317
+ const overIndex = items.indexOf(over.id);
318
+ let insertAfterItemId = void 0;
319
+ if (currentSide === "left") {
320
+ if (overIndex > 0) {
321
+ insertAfterItemId = items[overIndex - 1];
322
+ }
323
+ } else {
324
+ insertAfterItemId = over.id;
325
+ }
326
+ if (activeContainer === overContainer) {
327
+ if (active.id !== over.id) {
328
+ reorderItemWithinContainerByGap(activeContainer, active.id, insertAfterItemId, fallbackItems, reorderItemWithinContainer);
329
+ }
330
+ } else {
331
+ moveItemBetweenContainersByGap(activeContainer, overContainer, active.id, insertAfterItemId, fallbackItems, moveItemBetweenContainers);
332
+ }
333
+ return;
334
+ }
335
+ if (overData?.type === "GapDropZone") {
336
+ const destContainerId = overData.containerId;
337
+ const insertAfterItemId = overData.insertAfterItemId;
338
+ if (activeContainer === destContainerId) {
339
+ reorderItemWithinContainerByGap(
340
+ activeContainer,
341
+ active.id,
342
+ insertAfterItemId,
343
+ fallbackItems,
344
+ reorderItemWithinContainer
345
+ );
346
+ } else {
347
+ moveItemBetweenContainersByGap(
348
+ activeContainer,
349
+ destContainerId,
350
+ active.id,
351
+ insertAfterItemId,
352
+ fallbackItems,
353
+ moveItemBetweenContainers
354
+ );
355
+ }
356
+ return;
357
+ }
358
+ const insertAfter = currentSide === "right";
359
+ if (activeContainer === overContainer) {
360
+ if (active.id !== over.id) {
361
+ reorderItemWithinContainer(activeContainer, active.id, over.id, fallbackItems, insertAfter);
362
+ }
363
+ } else {
364
+ let actualDestContainer = overContainer;
365
+ if (event.over?.data?.current?.sortable?.containerId) {
366
+ actualDestContainer = event.over.data.current.sortable.containerId;
367
+ }
368
+ moveItemBetweenContainers(activeContainer, actualDestContainer, active.id, over.id, fallbackItems, insertAfter);
369
+ }
370
+ };
371
+ return /* @__PURE__ */ jsx2(DndContext, { sensors, collisionDetection: customCollisionDetection, onDragStart: handleDragStart, onDragMove: handleDragMove, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsx2("div", { className: `bob-react-form ${className}`, children }) });
372
+ };
373
+ function reorderItemWithinContainerByGap(containerId, activeId, insertAfterItemId, fallbackItems, reorderFn) {
374
+ if (!insertAfterItemId) {
375
+ reorderFn(containerId, activeId, "__start__", fallbackItems, false);
376
+ return;
377
+ }
378
+ reorderFn(
379
+ containerId,
380
+ activeId,
381
+ insertAfterItemId,
382
+ fallbackItems,
383
+ true
384
+ /* insertAfter = true → after insertAfterItemId */
385
+ );
386
+ }
387
+ function moveItemBetweenContainersByGap(sourceContainerId, destContainerId, activeId, insertAfterItemId, fallbackItems, moveFn) {
388
+ if (!insertAfterItemId) {
389
+ moveFn(sourceContainerId, destContainerId, activeId, "__start__", fallbackItems, false);
390
+ return;
391
+ }
392
+ moveFn(sourceContainerId, destContainerId, activeId, insertAfterItemId, fallbackItems, true);
393
+ }
394
+ var customCollisionDetection = (args) => {
395
+ const pointerCollisions = pointerWithin(args);
396
+ if (pointerCollisions.length > 0) {
397
+ return pointerCollisions;
398
+ }
399
+ return closestCenter(args);
400
+ };
401
+ var BobForm = ({
402
+ children,
403
+ className = "",
404
+ designMode = false,
405
+ designTabs = false,
406
+ designGroups = false,
407
+ designFields = false,
408
+ layoutState,
409
+ pageCols,
410
+ groupCols,
411
+ initialResponsivenessConfig,
412
+ onLayoutChange,
413
+ onPageColsChange,
414
+ onGroupColsChange,
415
+ onResponsivenessConfigChange
416
+ }) => {
417
+ return /* @__PURE__ */ jsx2(
418
+ LayoutProvider,
419
+ {
420
+ initialDesignMode: designMode,
421
+ initialDesignTabs: designTabs,
422
+ initialDesignGroups: designGroups,
423
+ initialDesignFields: designFields,
424
+ initialLayoutState: layoutState,
425
+ initialPageCols: pageCols,
426
+ initialGroupCols: groupCols,
427
+ initialResponsivenessConfig,
428
+ onLayoutChange,
429
+ onPageColsChange,
430
+ onGroupColsChange,
431
+ onResponsivenessConfigChange,
432
+ children: /* @__PURE__ */ jsx2(InnerFormWrapper, { className, children })
433
+ }
434
+ );
435
+ };
436
+
437
+ // src/components/BobTabStrip.tsx
438
+ import React3 from "react";
439
+ import { SortableContext, horizontalListSortingStrategy } from "@dnd-kit/sortable";
440
+ import { jsx as jsx3 } from "react/jsx-runtime";
441
+ var BobTabStrip = ({ id, children, className = "" }) => {
442
+ const { layoutState } = useLayoutContext();
443
+ const childrenArray = React3.Children.toArray(children).filter(React3.isValidElement);
444
+ const getId = (c) => c.props.id || c.key;
445
+ const defaultIds = childrenArray.map(getId);
446
+ const itemIds = layoutState[id] || defaultIds;
447
+ const sortedChildren = itemIds.map((itemId) => childrenArray.find((c) => getId(c) === itemId)).filter(Boolean);
448
+ const newChildren = childrenArray.filter((c) => !itemIds.includes(getId(c)));
449
+ const finalChildren = [...sortedChildren, ...newChildren];
450
+ const finalIds = finalChildren.map(getId).filter(Boolean);
451
+ return /* @__PURE__ */ jsx3(SortableContext, { id, items: finalIds, strategy: horizontalListSortingStrategy, children: /* @__PURE__ */ jsx3("div", { className: `bob-react-tab-strip ${className}`, children: finalChildren }) });
452
+ };
453
+
454
+ // src/components/BobTab.tsx
455
+ import { useSortable } from "@dnd-kit/sortable";
456
+ import { CSS } from "@dnd-kit/utilities";
457
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
458
+ var BobTab = ({ id, label, isActive = false, onClick, className = "" }) => {
459
+ const { designTabs } = useLayoutContext();
460
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
461
+ id,
462
+ data: { type: "Tab" }
463
+ });
464
+ const style = {
465
+ transform: CSS.Transform.toString(transform),
466
+ transition,
467
+ opacity: isDragging ? 0.5 : 1,
468
+ zIndex: isDragging ? 2 : 1
469
+ };
470
+ return /* @__PURE__ */ jsxs(
471
+ "button",
472
+ {
473
+ ref: setNodeRef,
474
+ type: "button",
475
+ className: `bob-react-tab ${isActive ? "bob-react-tab-active" : ""} ${className}`,
476
+ style,
477
+ onClick: () => onClick?.(id),
478
+ children: [
479
+ designTabs && /* @__PURE__ */ jsx4("span", { className: "bob-react-tab-drag-handle", ...attributes, ...listeners, style: { marginRight: "8px", cursor: "grab", color: "#94a3b8" }, children: /* @__PURE__ */ jsx4("i", { className: "fa-solid fa-grip-vertical" }) }),
480
+ label
481
+ ]
482
+ }
483
+ );
484
+ };
485
+
486
+ // src/components/BobPage.tsx
487
+ import React4, { useState as useState2 } from "react";
488
+ import { SortableContext as SortableContext2, rectSortingStrategy } from "@dnd-kit/sortable";
489
+ import { useDroppable as useDroppable2 } from "@dnd-kit/core";
490
+
491
+ // src/components/GapDropZone.tsx
492
+ import { useDroppable } from "@dnd-kit/core";
493
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
494
+ var GapDropZone = ({ id, containerId, insertAfterItemId, position, colSpan, newline, onResize, onToggleNewline }) => {
495
+ const { designFields, designGroups, dragOverId } = useLayoutContext();
496
+ const isActive = dragOverId === id;
497
+ const showControls = (designFields || designGroups || !!onResize) && position !== "end";
498
+ const { setNodeRef } = useDroppable({
499
+ id,
500
+ data: { type: "GapDropZone", containerId, insertAfterItemId }
501
+ });
502
+ const className = `bob-react-gap-zone ${position} ${isActive ? "bob-react-gap-zone-active" : ""}`;
503
+ if (!showControls) return /* @__PURE__ */ jsx5("div", { ref: setNodeRef, className });
504
+ return /* @__PURE__ */ jsx5(
505
+ "div",
506
+ {
507
+ ref: setNodeRef,
508
+ className: `bob-react-gap-zone ${position} ${isActive ? "bob-react-gap-zone-active" : ""}`,
509
+ "data-gap-id": id,
510
+ children: showControls && /* @__PURE__ */ jsxs2("div", { className: "bob-react-gap-controls", onPointerDown: (e) => e.stopPropagation(), children: [
511
+ /* @__PURE__ */ jsx5(
512
+ "button",
513
+ {
514
+ className: "bob-react-gap-btn",
515
+ title: "Increase column span",
516
+ onPointerDown: (e) => {
517
+ e.preventDefault();
518
+ e.stopPropagation();
519
+ onResize(1);
520
+ },
521
+ children: "+"
522
+ }
523
+ ),
524
+ /* @__PURE__ */ jsx5("span", { className: "bob-react-gap-span-label", children: colSpan }),
525
+ /* @__PURE__ */ jsx5(
526
+ "button",
527
+ {
528
+ className: "bob-react-gap-btn",
529
+ title: "Decrease column span",
530
+ onPointerDown: (e) => {
531
+ e.preventDefault();
532
+ e.stopPropagation();
533
+ onResize(-1);
534
+ },
535
+ children: "-"
536
+ }
537
+ ),
538
+ onToggleNewline && /* @__PURE__ */ jsx5(
539
+ "button",
540
+ {
541
+ className: "bob-react-gap-btn",
542
+ title: newline ? "Remove Newline" : "Set Newline",
543
+ style: { marginTop: "2px", color: newline ? "var(--q-primary, #2563eb)" : "var(--q-text-muted, #94a3b8)" },
544
+ onPointerDown: (e) => {
545
+ e.preventDefault();
546
+ e.stopPropagation();
547
+ onToggleNewline();
548
+ },
549
+ children: /* @__PURE__ */ jsx5("i", { className: newline ? "fa-solid fa-arrow-turn-down" : "fa-solid fa-arrow-right" })
550
+ }
551
+ )
552
+ ] })
553
+ }
554
+ );
555
+ };
556
+
557
+ // src/components/BobPage.tsx
558
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
559
+ var BobPage = ({ id, children, columns, className = "" }) => {
560
+ const { layoutState, designGroups, pageCols } = useLayoutContext();
561
+ const activeColumns = columns ?? pageCols;
562
+ const { setNodeRef } = useDroppable2({ id });
563
+ const [panelSpanOverrides, setPanelSpanOverrides] = useState2({});
564
+ const [panelRowSpanOverrides, setPanelRowSpanOverrides] = useState2({});
565
+ const [panelNewlineOverrides, setPanelNewlineOverrides] = useState2({});
566
+ const childrenArray = React4.Children.toArray(children).filter(React4.isValidElement);
567
+ const getId = (c) => c.props.id || c.key;
568
+ const defaultIds = childrenArray.map(getId);
569
+ const itemIds = layoutState[id] || defaultIds;
570
+ const sortedChildren = itemIds.map((itemId) => childrenArray.find((c) => getId(c) === itemId)).filter(Boolean);
571
+ const newChildren = childrenArray.filter((c) => !itemIds.includes(getId(c)));
572
+ const finalChildren = [...sortedChildren, ...newChildren];
573
+ const finalIds = finalChildren.map(getId).filter(Boolean);
574
+ const styledPanels = finalChildren.map((child) => {
575
+ const panelId = getId(child);
576
+ const requestedColSpan = panelSpanOverrides[panelId] ?? child.props.colSpan ?? activeColumns;
577
+ const panelColSpan = Math.min(requestedColSpan, activeColumns);
578
+ const panelRowSpan = panelRowSpanOverrides[panelId] ?? child.props.rowSpan ?? 1;
579
+ const panelNewline = panelNewlineOverrides[panelId] ?? child.props.newline ?? false;
580
+ return React4.cloneElement(child, {
581
+ key: panelId,
582
+ colSpan: panelColSpan,
583
+ rowSpan: panelRowSpan,
584
+ newline: panelNewline,
585
+ onRowResize: (delta) => {
586
+ setPanelRowSpanOverrides((prev) => ({
587
+ ...prev,
588
+ [panelId]: Math.max(1, (prev[panelId] ?? child.props.rowSpan ?? 1) + delta)
589
+ }));
590
+ }
591
+ });
592
+ });
593
+ const buildPanelsWithGaps = () => {
594
+ if (!designGroups) return styledPanels;
595
+ const result = [];
596
+ styledPanels.forEach((panel, idx) => {
597
+ const panelId = getId(panel);
598
+ const panelColSpan = panel.props.colSpan ?? activeColumns;
599
+ const panelRowSpan = panel.props.rowSpan ?? 1;
600
+ const panelNewline = panel.props.newline ?? false;
601
+ const isFirst = idx === 0;
602
+ const isLast = idx === styledPanels.length - 1;
603
+ const gapId = isFirst ? `gap-${id}-start` : `gap-${id}-after-${getId(styledPanels[idx - 1])}`;
604
+ const insertAfterItemId = isFirst ? void 0 : getId(styledPanels[idx - 1]);
605
+ result.push(
606
+ /* @__PURE__ */ jsxs3(
607
+ "div",
608
+ {
609
+ style: {
610
+ display: "flex",
611
+ flexDirection: "row",
612
+ alignItems: "stretch",
613
+ gridColumn: panelNewline ? `1 / span ${panelColSpan}` : `span ${panelColSpan}`,
614
+ gridRow: `span ${panelRowSpan}`,
615
+ minWidth: 0,
616
+ boxSizing: "border-box"
617
+ },
618
+ children: [
619
+ /* @__PURE__ */ jsx6(
620
+ GapDropZone,
621
+ {
622
+ id: gapId,
623
+ containerId: id,
624
+ insertAfterItemId,
625
+ colSpan: panelColSpan,
626
+ newline: panelNewline,
627
+ onResize: (delta) => {
628
+ setPanelSpanOverrides((prev) => {
629
+ const current = prev[panelId] ?? panel.props.colSpan ?? activeColumns;
630
+ return { ...prev, [panelId]: Math.max(1, current + delta) };
631
+ });
632
+ const el = document.querySelector(`[data-bob-item-id="${panelId}"], [id="${panelId}"]`);
633
+ if (el) {
634
+ const event = new CustomEvent("bob-resize", { detail: { delta }, bubbles: false });
635
+ el.dispatchEvent(event);
636
+ }
637
+ },
638
+ onToggleNewline: () => {
639
+ const newVal = !panelNewline;
640
+ setPanelNewlineOverrides((prev) => ({ ...prev, [panelId]: newVal }));
641
+ const el = document.querySelector(`[data-bob-item-id="${panelId}"], [id="${panelId}"]`);
642
+ if (el) {
643
+ const event = new CustomEvent("bob-toggle-newline", { detail: { newline: newVal }, bubbles: false });
644
+ el.dispatchEvent(event);
645
+ }
646
+ },
647
+ position: isFirst ? "start" : "between"
648
+ }
649
+ ),
650
+ panel,
651
+ isLast && /* @__PURE__ */ jsx6(
652
+ GapDropZone,
653
+ {
654
+ id: `gap-${id}-after-${panelId}`,
655
+ containerId: id,
656
+ insertAfterItemId: panelId,
657
+ position: "end"
658
+ },
659
+ `gap-${id}-end`
660
+ )
661
+ ]
662
+ },
663
+ `pair-${id}-${panelId}`
664
+ )
665
+ );
666
+ });
667
+ if (styledPanels.length === 0) {
668
+ result.push(
669
+ /* @__PURE__ */ jsx6(
670
+ GapDropZone,
671
+ {
672
+ id: `gap-${id}-start`,
673
+ containerId: id,
674
+ insertAfterItemId: void 0,
675
+ position: "start"
676
+ },
677
+ `gap-${id}-empty`
678
+ )
679
+ );
680
+ }
681
+ return result;
682
+ };
683
+ const pageStyle = {
684
+ gridTemplateColumns: `repeat(${activeColumns}, 1fr)`,
685
+ display: "grid",
686
+ gap: 0
687
+ };
688
+ return /* @__PURE__ */ jsx6(SortableContext2, { id, items: finalIds, strategy: rectSortingStrategy, children: /* @__PURE__ */ jsx6(
689
+ "div",
690
+ {
691
+ ref: setNodeRef,
692
+ className: `bob-react-page ${className}`,
693
+ style: pageStyle,
694
+ children: designGroups ? buildPanelsWithGaps() : styledPanels
695
+ }
696
+ ) });
697
+ };
698
+
699
+ // src/components/BobTabPage.tsx
700
+ import { jsx as jsx7 } from "react/jsx-runtime";
701
+ var BobTabPage = ({
702
+ id,
703
+ children,
704
+ isActive = false,
705
+ columns = 12,
706
+ className = "",
707
+ pageClassName = ""
708
+ }) => {
709
+ return /* @__PURE__ */ jsx7("div", { className: `bob-react-tab-page-wrapper ${className}`, style: { display: isActive ? "block" : "none" }, children: /* @__PURE__ */ jsx7(BobPage, { id: id ?? "tab-page", columns, className: pageClassName, children }) });
710
+ };
711
+
712
+ // src/components/BobPanel.tsx
713
+ import React5, { useState as useState3 } from "react";
714
+ import { SortableContext as SortableContext3, rectSortingStrategy as rectSortingStrategy2, useSortable as useSortable2 } from "@dnd-kit/sortable";
715
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
716
+ var BobPanel = ({
717
+ id,
718
+ children,
719
+ colSpan = 12,
720
+ rowSpan = 1,
721
+ columns,
722
+ className = "",
723
+ caption,
724
+ newline = false,
725
+ onRowResize,
726
+ style: customStyle
727
+ }) => {
728
+ const { designGroups, designFields, layoutState, dragOverId, selectedItemIds, toggleItemSelection, groupCols } = useLayoutContext();
729
+ const activeColumns = columns ?? groupCols;
730
+ const isDragOver = dragOverId === id;
731
+ const isSelected = selectedItemIds.includes(id);
732
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable2({
733
+ id,
734
+ data: { type: "Panel" }
735
+ });
736
+ const [overrideColSpan, setOverrideColSpan] = useState3(null);
737
+ const activeColSpan = overrideColSpan ?? colSpan;
738
+ const [overrideRowSpan, setOverrideRowSpan] = useState3(null);
739
+ const activeRowSpan = overrideRowSpan ?? rowSpan;
740
+ const [overrideNewline, setOverrideNewline] = useState3(null);
741
+ const activeNewline = overrideNewline ?? newline;
742
+ const [itemSpanOverrides, setItemSpanOverrides] = useState3({});
743
+ const [itemRowSpanOverrides, setItemRowSpanOverrides] = useState3({});
744
+ const [itemNewlineOverrides, setItemNewlineOverrides] = useState3({});
745
+ const childrenArray = React5.Children.toArray(children).filter(React5.isValidElement);
746
+ const getId = (c) => c.props.id || c.key;
747
+ const defaultIds = childrenArray.map(getId);
748
+ const itemIds = layoutState[id] || defaultIds;
749
+ const sortedChildren = itemIds.map((itemId) => childrenArray.find((c) => getId(c) === itemId)).filter(Boolean);
750
+ const newChildren = childrenArray.filter((c) => !itemIds.includes(getId(c)));
751
+ const finalChildren = [...sortedChildren, ...newChildren];
752
+ const finalIds = finalChildren.map(getId).filter(Boolean);
753
+ const styledChildren = finalChildren.map((child) => {
754
+ const childId = getId(child);
755
+ const requestedColSpan = itemSpanOverrides[childId] ?? child.props.colSpan ?? 1;
756
+ const childColSpan = Math.min(requestedColSpan, activeColumns);
757
+ const childRowSpan = itemRowSpanOverrides[childId] ?? child.props.rowSpan ?? 1;
758
+ const childNewline = itemNewlineOverrides[childId] ?? child.props.newline ?? false;
759
+ return React5.cloneElement(child, {
760
+ key: childId,
761
+ colSpan: childColSpan,
762
+ rowSpan: childRowSpan,
763
+ newline: childNewline,
764
+ onRowResize: (delta) => {
765
+ setItemRowSpanOverrides((prev) => ({
766
+ ...prev,
767
+ [childId]: Math.max(1, (prev[childId] ?? child.props.rowSpan ?? 1) + delta)
768
+ }));
769
+ }
770
+ });
771
+ });
772
+ const baseStyle = {
773
+ gridColumn: activeNewline ? `1 / span ${activeColSpan}` : `span ${activeColSpan}`,
774
+ gridRow: `span ${activeRowSpan}`,
775
+ gridTemplateColumns: `repeat(${activeColumns}, 1fr)`,
776
+ ["--panel-columns"]: activeColumns,
777
+ transition,
778
+ opacity: isDragging ? 0.5 : 1,
779
+ zIndex: isDragging ? 2 : 1,
780
+ flex: 1,
781
+ // Expand to fill pair div in flex mode
782
+ width: "100%",
783
+ boxSizing: "border-box"
784
+ };
785
+ const nodeRef = React5.useRef(null);
786
+ React5.useEffect(() => {
787
+ const el = nodeRef.current;
788
+ if (!el) return;
789
+ const resizeHandler = (e) => {
790
+ const delta = e.detail?.delta;
791
+ if (typeof delta === "number") {
792
+ setOverrideColSpan((prev) => Math.max(1, (prev ?? activeColSpan) + delta));
793
+ }
794
+ };
795
+ const newlineHandler = (e) => {
796
+ const val = e.detail?.newline;
797
+ if (typeof val === "boolean") {
798
+ setOverrideNewline(val);
799
+ }
800
+ };
801
+ el.addEventListener("bob-resize", resizeHandler);
802
+ el.addEventListener("bob-toggle-newline", newlineHandler);
803
+ return () => {
804
+ el.removeEventListener("bob-resize", resizeHandler);
805
+ el.removeEventListener("bob-toggle-newline", newlineHandler);
806
+ };
807
+ }, [activeColSpan, activeNewline]);
808
+ const setRefs = (el) => {
809
+ nodeRef.current = el;
810
+ setNodeRef(el);
811
+ };
812
+ const buildChildrenWithGaps = () => {
813
+ if (!designFields) return styledChildren;
814
+ const result = [];
815
+ styledChildren.forEach((child, idx) => {
816
+ const childId = getId(child);
817
+ const childColSpan = child.props.colSpan ?? 1;
818
+ const childRowSpan = child.props.rowSpan ?? 1;
819
+ const childNewline = child.props.newline ?? false;
820
+ const isFirst = idx === 0;
821
+ const isLast = idx === styledChildren.length - 1;
822
+ const gapId = isFirst ? `gap-${id}-start` : `gap-${id}-after-${getId(styledChildren[idx - 1])}`;
823
+ const insertAfterItemId = isFirst ? void 0 : getId(styledChildren[idx - 1]);
824
+ result.push(
825
+ /* @__PURE__ */ jsxs4(
826
+ "div",
827
+ {
828
+ style: {
829
+ display: "flex",
830
+ flexDirection: "row",
831
+ alignItems: "stretch",
832
+ gridColumn: childNewline ? `1 / span ${childColSpan}` : `span ${childColSpan}`,
833
+ gridRow: `span ${childRowSpan}`,
834
+ minWidth: 0,
835
+ boxSizing: "border-box"
836
+ },
837
+ children: [
838
+ /* @__PURE__ */ jsx8(
839
+ GapDropZone,
840
+ {
841
+ id: gapId,
842
+ containerId: id,
843
+ insertAfterItemId,
844
+ colSpan: childColSpan,
845
+ newline: childNewline,
846
+ onResize: (delta) => {
847
+ setItemSpanOverrides((prev) => {
848
+ const current = prev[childId] ?? child.props.colSpan ?? 1;
849
+ return { ...prev, [childId]: Math.max(1, current + delta) };
850
+ });
851
+ const el = document.querySelector(`[data-bob-item-id="${childId}"]`);
852
+ if (el) {
853
+ const event = new CustomEvent("bob-resize", { detail: { delta }, bubbles: false });
854
+ el.dispatchEvent(event);
855
+ }
856
+ },
857
+ onToggleNewline: () => {
858
+ const newVal = !childNewline;
859
+ setItemNewlineOverrides((prev) => ({ ...prev, [childId]: newVal }));
860
+ const el = document.querySelector(`[data-bob-item-id="${childId}"]`);
861
+ if (el) {
862
+ const event = new CustomEvent("bob-toggle-newline", { detail: { newline: newVal }, bubbles: false });
863
+ el.dispatchEvent(event);
864
+ }
865
+ },
866
+ position: isFirst ? "start" : "between"
867
+ }
868
+ ),
869
+ child,
870
+ isLast && /* @__PURE__ */ jsx8(
871
+ GapDropZone,
872
+ {
873
+ id: `gap-${id}-after-${childId}`,
874
+ containerId: id,
875
+ insertAfterItemId: childId,
876
+ position: "end"
877
+ },
878
+ `gap-${id}-end`
879
+ )
880
+ ]
881
+ },
882
+ `pair-${id}-${childId}`
883
+ )
884
+ );
885
+ });
886
+ if (styledChildren.length === 0) {
887
+ result.push(
888
+ /* @__PURE__ */ jsx8(
889
+ GapDropZone,
890
+ {
891
+ id: `gap-${id}-start`,
892
+ containerId: id,
893
+ insertAfterItemId: void 0,
894
+ position: "start"
895
+ },
896
+ `gap-${id}-empty`
897
+ )
898
+ );
899
+ }
900
+ return result;
901
+ };
902
+ return /* @__PURE__ */ jsx8(SortableContext3, { id, items: finalIds, strategy: rectSortingStrategy2, children: /* @__PURE__ */ jsxs4(
903
+ "div",
904
+ {
905
+ ref: setRefs,
906
+ className: `bob-react-panel ${designGroups ? "bob-react-panel-design-mode" : ""} ${isSelected ? "bob-react-item-selected" : ""} ${isDragOver && !designFields && !designGroups ? "bob-react-panel-dragover" : ""} ${className}`,
907
+ style: { ...baseStyle, ...customStyle },
908
+ children: [
909
+ designGroups && /* @__PURE__ */ jsxs4("div", { className: "bob-react-panel-row-controls", onPointerDown: (e) => e.stopPropagation(), children: [
910
+ /* @__PURE__ */ jsx8(
911
+ "button",
912
+ {
913
+ className: "bob-react-gap-btn",
914
+ title: "Increase row span",
915
+ onPointerDown: (e) => {
916
+ e.preventDefault();
917
+ e.stopPropagation();
918
+ onRowResize ? onRowResize(1) : setOverrideRowSpan((prev) => (prev ?? activeRowSpan) + 1);
919
+ },
920
+ children: "+"
921
+ }
922
+ ),
923
+ /* @__PURE__ */ jsx8("span", { className: "bob-react-gap-span-label", children: activeRowSpan }),
924
+ /* @__PURE__ */ jsx8(
925
+ "button",
926
+ {
927
+ className: "bob-react-gap-btn",
928
+ title: "Decrease row span",
929
+ onPointerDown: (e) => {
930
+ e.preventDefault();
931
+ e.stopPropagation();
932
+ onRowResize ? onRowResize(-1) : setOverrideRowSpan((prev) => Math.max(1, (prev ?? activeRowSpan) - 1));
933
+ },
934
+ children: "-"
935
+ }
936
+ )
937
+ ] }),
938
+ caption && /* @__PURE__ */ jsx8("div", { className: "bob-react-panel-header", children: /* @__PURE__ */ jsx8("h2", { className: "bob-react-panel-caption", children: caption }) }),
939
+ designFields ? /* @__PURE__ */ jsx8("div", { className: "bob-react-panel-fields-row", children: buildChildrenWithGaps() }) : styledChildren,
940
+ designGroups && /* @__PURE__ */ jsx8(
941
+ "div",
942
+ {
943
+ className: "bob-react-overlay",
944
+ ...attributes,
945
+ ...listeners,
946
+ onClick: (e) => {
947
+ toggleItemSelection(id, e.ctrlKey || e.metaKey);
948
+ }
949
+ }
950
+ )
951
+ ]
952
+ }
953
+ ) });
954
+ };
955
+
956
+ // src/components/BobItem.tsx
957
+ import React6, { useState as useState4, useEffect as useEffect2 } from "react";
958
+ import { useSortable as useSortable3 } from "@dnd-kit/sortable";
959
+ import { Fragment, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
960
+ var BobItem = ({
961
+ id,
962
+ children,
963
+ colSpan = 1,
964
+ rowSpan = 1,
965
+ className = "",
966
+ expandVertical = false,
967
+ caption,
968
+ hideCaption = false,
969
+ newline = false,
970
+ onRowResize,
971
+ style: customStyle,
972
+ horizontal = false,
973
+ labelWidth = "120px"
974
+ }) => {
975
+ const { designFields, designGroups, selectedItemIds, toggleItemSelection } = useLayoutContext();
976
+ const isSelected = selectedItemIds.includes(id);
977
+ const [overrideColSpan, setOverrideColSpan] = useState4(null);
978
+ const [overrideNewline, setOverrideNewline] = useState4(null);
979
+ const activeColSpan = overrideColSpan ?? colSpan;
980
+ const activeNewline = overrideNewline ?? newline;
981
+ const {
982
+ attributes,
983
+ listeners,
984
+ setNodeRef,
985
+ transform,
986
+ transition,
987
+ isDragging
988
+ } = useSortable3({
989
+ id,
990
+ data: { type: "Item" },
991
+ disabled: !designFields
992
+ });
993
+ const style = designFields ? {
994
+ // In design mode the wrapper pair-div owns the column width;
995
+ // this item just fills the remaining space after the 20px gap.
996
+ gridColumn: `span ${activeColSpan}`,
997
+ gridRow: `span ${rowSpan}`,
998
+ flex: "1 1 0",
999
+ minWidth: 0,
1000
+ transition,
1001
+ opacity: isDragging ? 0.4 : 1,
1002
+ zIndex: isDragging ? 2 : 1
1003
+ } : {
1004
+ // Normal CSS-grid mode
1005
+ gridColumn: activeNewline ? `1 / span ${activeColSpan}` : `span ${activeColSpan}`,
1006
+ gridRow: `span ${rowSpan}`,
1007
+ transition,
1008
+ opacity: isDragging ? 0.4 : 1,
1009
+ zIndex: isDragging ? 2 : 1,
1010
+ pointerEvents: designGroups ? "none" : void 0
1011
+ };
1012
+ const nodeRef = React6.useRef(null);
1013
+ useEffect2(() => {
1014
+ const el = nodeRef.current;
1015
+ if (!el) return;
1016
+ const resizeHandler = (e) => {
1017
+ const delta = e.detail?.delta;
1018
+ if (typeof delta === "number") {
1019
+ setOverrideColSpan((prev) => Math.max(1, (prev ?? activeColSpan) + delta));
1020
+ }
1021
+ };
1022
+ const newlineHandler = (e) => {
1023
+ const val = e.detail?.newline;
1024
+ if (typeof val === "boolean") {
1025
+ setOverrideNewline(val);
1026
+ }
1027
+ };
1028
+ el.addEventListener("bob-resize", resizeHandler);
1029
+ el.addEventListener("bob-toggle-newline", newlineHandler);
1030
+ return () => {
1031
+ el.removeEventListener("bob-resize", resizeHandler);
1032
+ el.removeEventListener("bob-toggle-newline", newlineHandler);
1033
+ };
1034
+ }, [activeColSpan, activeNewline]);
1035
+ const setRefs = (el) => {
1036
+ nodeRef.current = el;
1037
+ setNodeRef(el);
1038
+ };
1039
+ return /* @__PURE__ */ jsxs5(
1040
+ "div",
1041
+ {
1042
+ ref: setRefs,
1043
+ "data-bob-item-id": id,
1044
+ className: `bob-react-item ${expandVertical ? "bob-react-item-expand-vertical" : ""} ${isSelected ? "bob-react-item-selected" : ""} ${className}`,
1045
+ style: { ...style, ...customStyle },
1046
+ children: [
1047
+ caption && !hideCaption && !horizontal && /* @__PURE__ */ jsx9("div", { className: "bob-react-item-caption", children: caption }),
1048
+ horizontal ? /* @__PURE__ */ jsxs5(
1049
+ "div",
1050
+ {
1051
+ className: "bob-react-item-horizontal",
1052
+ style: { gridTemplateColumns: `${labelWidth} 1fr` },
1053
+ children: [
1054
+ caption && !hideCaption && /* @__PURE__ */ jsx9("div", { className: "bob-react-item-horizontal-caption", children: caption }),
1055
+ /* @__PURE__ */ jsx9("div", { className: "bob-react-item-horizontal-content", children })
1056
+ ]
1057
+ }
1058
+ ) : children,
1059
+ designFields && /* @__PURE__ */ jsxs5(Fragment, { children: [
1060
+ /* @__PURE__ */ jsxs5("div", { className: "bob-react-item-row-controls", onPointerDown: (e) => e.stopPropagation(), children: [
1061
+ /* @__PURE__ */ jsx9(
1062
+ "button",
1063
+ {
1064
+ className: "bob-react-button bob-react-item-row-btn",
1065
+ title: "Increase row span",
1066
+ onPointerDown: (e) => {
1067
+ e.preventDefault();
1068
+ e.stopPropagation();
1069
+ onRowResize?.(1);
1070
+ },
1071
+ children: "+"
1072
+ }
1073
+ ),
1074
+ /* @__PURE__ */ jsx9("span", { className: "bob-react-gap-span-label", children: rowSpan }),
1075
+ /* @__PURE__ */ jsx9(
1076
+ "button",
1077
+ {
1078
+ className: "bob-react-button bob-react-item-row-btn",
1079
+ title: "Decrease row span",
1080
+ onPointerDown: (e) => {
1081
+ e.preventDefault();
1082
+ e.stopPropagation();
1083
+ onRowResize?.(-1);
1084
+ },
1085
+ children: "-"
1086
+ }
1087
+ )
1088
+ ] }),
1089
+ /* @__PURE__ */ jsx9(
1090
+ "div",
1091
+ {
1092
+ className: "bob-react-overlay",
1093
+ ...attributes,
1094
+ ...listeners,
1095
+ onClick: (e) => {
1096
+ toggleItemSelection(id, e.ctrlKey || e.metaKey);
1097
+ }
1098
+ }
1099
+ )
1100
+ ] })
1101
+ ]
1102
+ }
1103
+ );
1104
+ };
1105
+
1106
+ // src/components/BobResponsiveness.tsx
1107
+ import React7 from "react";
1108
+ import { BobBoxInteger, BobBoxTextPopupGrid } from "@qiiqa/bob-ui-react-components";
1109
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1110
+ var BobResponsiveness = ({
1111
+ className = ""
1112
+ }) => {
1113
+ const {
1114
+ responsivenessConfig: config,
1115
+ setResponsivenessConfig,
1116
+ currentBreakpoint
1117
+ } = useLayoutContext();
1118
+ const handleConfigChange = (tier, type, value) => {
1119
+ const newConfig = {
1120
+ ...config,
1121
+ [tier]: {
1122
+ ...config[tier],
1123
+ [type]: value ?? 0
1124
+ }
1125
+ };
1126
+ setResponsivenessConfig(newConfig);
1127
+ };
1128
+ const currentTier = config[currentBreakpoint];
1129
+ const displayValue = currentTier ? `P:${currentTier.page}, G:${currentTier.group}` : "...";
1130
+ const tiers = [
1131
+ { key: "small", label: "Small" },
1132
+ { key: "medium", label: "Medium" },
1133
+ { key: "large", label: "Large" },
1134
+ { key: "extraLarge", label: "Extra Large" }
1135
+ ];
1136
+ return /* @__PURE__ */ jsx10("div", { className: `bob-react-responsiveness ${className}`, children: /* @__PURE__ */ jsx10(
1137
+ BobBoxTextPopupGrid,
1138
+ {
1139
+ id: "bob-responsiveness-popup",
1140
+ className: "bob-react-responsiveness-display",
1141
+ value: displayValue,
1142
+ icon: /* @__PURE__ */ jsx10("span", { children: currentBreakpoint.toUpperCase().charAt(0) }),
1143
+ isReadOnly: true,
1144
+ rowCaptions: ["Small", "Medium", "Large", "Extra Large"],
1145
+ colCaptions: ["Page", "Group"],
1146
+ colWidths: ["max-content", "50px", "50px"],
1147
+ popupWidth: "auto",
1148
+ children: tiers.map((tier, rowIndex) => /* @__PURE__ */ jsxs6(React7.Fragment, { children: [
1149
+ /* @__PURE__ */ jsx10(
1150
+ BobBoxInteger,
1151
+ {
1152
+ id: `resp-${tier.key}-page`,
1153
+ row: rowIndex,
1154
+ col: 0,
1155
+ value: config[tier.key].page,
1156
+ onChange: (v) => handleConfigChange(tier.key, "page", v),
1157
+ minValue: 1,
1158
+ maxValue: 24
1159
+ }
1160
+ ),
1161
+ /* @__PURE__ */ jsx10(
1162
+ BobBoxInteger,
1163
+ {
1164
+ id: `resp-${tier.key}-group`,
1165
+ row: rowIndex,
1166
+ col: 1,
1167
+ value: config[tier.key].group,
1168
+ onChange: (v) => handleConfigChange(tier.key, "group", v),
1169
+ minValue: 1,
1170
+ maxValue: 24
1171
+ }
1172
+ )
1173
+ ] }, tier.key))
1174
+ }
1175
+ ) });
1176
+ };
1177
+ export {
1178
+ BobForm,
1179
+ BobItem,
1180
+ BobPage,
1181
+ BobPanel,
1182
+ BobResponsiveness,
1183
+ BobTab,
1184
+ BobTabPage,
1185
+ BobTabStrip,
1186
+ DEFAULT_RESPONSIVENESS_CONFIG,
1187
+ GapDropZone,
1188
+ LayoutProvider,
1189
+ useLayoutContext
1190
+ };