@vuu-ui/grid-layout 4.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/package.json +40 -0
  4. package/src/GridCommand.js +395 -0
  5. package/src/GridComponentSettings.js +163 -0
  6. package/src/GridController.js +359 -0
  7. package/src/GridDragCoordinator.js +293 -0
  8. package/src/GridGeometry.js +1298 -0
  9. package/src/GridLayout.css.js +119 -0
  10. package/src/GridLayout.js +120 -0
  11. package/src/GridLayoutContext.js +58 -0
  12. package/src/GridLayoutDocument.js +307 -0
  13. package/src/GridLayoutDocumentIds.js +110 -0
  14. package/src/GridLayoutItem.css.js +46 -0
  15. package/src/GridLayoutItem.js +147 -0
  16. package/src/GridLayoutLegacyCompatibility.js +66 -0
  17. package/src/GridLayoutModel.js +138 -0
  18. package/src/GridLayoutProvider.js +263 -0
  19. package/src/GridLayoutStackedItem.css.js +26 -0
  20. package/src/GridLayoutStackedtem.js +140 -0
  21. package/src/GridModel.js +1289 -0
  22. package/src/GridPlaceholder.css.js +9 -0
  23. package/src/GridPlaceholder.js +24 -0
  24. package/src/GridSnapshot.js +11 -0
  25. package/src/GridSplitter.css.js +62 -0
  26. package/src/GridSplitter.js +26 -0
  27. package/src/GridStack.js +277 -0
  28. package/src/Icon.css.js +8 -0
  29. package/src/Icon.js +26 -0
  30. package/src/IconButton.css.js +9 -0
  31. package/src/IconButton.js +28 -0
  32. package/src/TabDialog.css.js +10 -0
  33. package/src/TabDialog.js +77 -0
  34. package/src/TabMenu.css.js +8 -0
  35. package/src/TabMenu.js +78 -0
  36. package/src/componentToJson.js +41 -0
  37. package/src/drag-drop-next/DragContextNext.js +173 -0
  38. package/src/drag-drop-next/DragDropProviderNext.css.js +72 -0
  39. package/src/drag-drop-next/DragDropProviderNext.js +91 -0
  40. package/src/drag-drop-next/SpaceMan.js +256 -0
  41. package/src/drag-drop-next/TemplateDragSession.js +36 -0
  42. package/src/drag-drop-next/tabstrip-drag-drop.js +251 -0
  43. package/src/grid-dom-utils.js +57 -0
  44. package/src/grid-layout-utils.js +39 -0
  45. package/src/grid-snapshot-adapters.js +435 -0
  46. package/src/index.js +24 -0
  47. package/src/json-value.js +56 -0
  48. package/src/layoutFromJson.js +15 -0
  49. package/src/layoutToJson.js +5 -0
  50. package/src/propUtils.js +15 -0
  51. package/src/react-element-utils.js +15 -0
  52. package/src/typeOf.js +15 -0
  53. package/src/useAsDropTarget.js +229 -0
  54. package/src/useDraggable.js +46 -0
  55. package/src/useEditTabName.js +70 -0
  56. package/src/useGridChildProps.js +47 -0
  57. package/src/useGridControllerSnapshot.js +21 -0
  58. package/src/useGridLayout.js +727 -0
  59. package/src/useGridSplitterResizing.js +262 -0
  60. package/src/useNotDropTarget.js +2 -0
  61. package/types/AddTabDialog.d.ts +5 -0
  62. package/types/GridCommand.d.ts +146 -0
  63. package/types/GridComponentSettings.d.ts +71 -0
  64. package/types/GridController.d.ts +50 -0
  65. package/types/GridDragCoordinator.d.ts +80 -0
  66. package/types/GridGeometry.d.ts +331 -0
  67. package/types/GridLayout.d.ts +20 -0
  68. package/types/GridLayoutContext.d.ts +141 -0
  69. package/types/GridLayoutDocument.d.ts +61 -0
  70. package/types/GridLayoutDocumentIds.d.ts +15 -0
  71. package/types/GridLayoutItem.d.ts +28 -0
  72. package/types/GridLayoutLegacyCompatibility.d.ts +57 -0
  73. package/types/GridLayoutModel.d.ts +72 -0
  74. package/types/GridLayoutProvider.d.ts +59 -0
  75. package/types/GridLayoutStackedtem.d.ts +10 -0
  76. package/types/GridModel.d.ts +504 -0
  77. package/types/GridPlaceholder.d.ts +5 -0
  78. package/types/GridSnapshot.d.ts +49 -0
  79. package/types/GridSplitter.d.ts +10 -0
  80. package/types/GridStack.d.ts +160 -0
  81. package/types/Icon.d.ts +6 -0
  82. package/types/IconButton.d.ts +6 -0
  83. package/types/TabDialog.d.ts +9 -0
  84. package/types/TabMenu.d.ts +7 -0
  85. package/types/componentToJson.d.ts +38 -0
  86. package/types/drag-drop-next/DragContextNext.d.ts +92 -0
  87. package/types/drag-drop-next/DragDropProviderNext.d.ts +23 -0
  88. package/types/drag-drop-next/SpaceMan.d.ts +35 -0
  89. package/types/drag-drop-next/TemplateDragSession.d.ts +21 -0
  90. package/types/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
  91. package/types/grid-dom-utils.d.ts +17 -0
  92. package/types/grid-layout-utils.d.ts +18 -0
  93. package/types/grid-matrix.d.ts +5 -0
  94. package/types/grid-snapshot-adapters.d.ts +22 -0
  95. package/types/index.d.ts +24 -0
  96. package/types/json-value.d.ts +23 -0
  97. package/types/layoutFromJson.d.ts +4 -0
  98. package/types/layoutToJson.d.ts +3 -0
  99. package/types/propUtils.d.ts +5 -0
  100. package/types/react-element-utils.d.ts +3 -0
  101. package/types/src/AddTabDialog.d.ts +5 -0
  102. package/types/src/GridLayout.d.ts +14 -0
  103. package/types/src/GridLayoutContext.d.ts +88 -0
  104. package/types/src/GridLayoutItem.d.ts +28 -0
  105. package/types/src/GridLayoutModel.d.ts +59 -0
  106. package/types/src/GridLayoutProvider.d.ts +37 -0
  107. package/types/src/GridLayoutStackedtem.d.ts +10 -0
  108. package/types/src/GridModel.d.ts +350 -0
  109. package/types/src/GridPlaceholder.d.ts +5 -0
  110. package/types/src/GridSplitter.d.ts +9 -0
  111. package/types/src/Icon.d.ts +6 -0
  112. package/types/src/IconButton.d.ts +6 -0
  113. package/types/src/TabMenu.d.ts +6 -0
  114. package/types/src/componentToJson.d.ts +32 -0
  115. package/types/src/drag-drop-next/DragContextNext.d.ts +73 -0
  116. package/types/src/drag-drop-next/DragDropProviderNext.d.ts +20 -0
  117. package/types/src/drag-drop-next/SpaceMan.d.ts +35 -0
  118. package/types/src/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
  119. package/types/src/grid-dom-utils.d.ts +17 -0
  120. package/types/src/grid-layout-utils.d.ts +80 -0
  121. package/types/src/grid-matrix.d.ts +5 -0
  122. package/types/src/index.d.ts +10 -0
  123. package/types/src/layoutFromJson.d.ts +3 -0
  124. package/types/src/layoutToJson.d.ts +2 -0
  125. package/types/src/propUtils.d.ts +5 -0
  126. package/types/src/react-element-utils.d.ts +3 -0
  127. package/types/src/typeOf.d.ts +6 -0
  128. package/types/src/useAsDropTarget.d.ts +7 -0
  129. package/types/src/useDraggable.d.ts +23 -0
  130. package/types/src/useGridChildProps.d.ts +13 -0
  131. package/types/src/useGridLayout.d.ts +43 -0
  132. package/types/src/useGridSplitterResizing.d.ts +12 -0
  133. package/types/src/useNotDropTarget.d.ts +1 -0
  134. package/types/typeOf.d.ts +6 -0
  135. package/types/useAsDropTarget.d.ts +7 -0
  136. package/types/useDraggable.d.ts +24 -0
  137. package/types/useEditTabName.d.ts +11 -0
  138. package/types/useGridChildProps.d.ts +13 -0
  139. package/types/useGridControllerSnapshot.d.ts +7 -0
  140. package/types/useGridLayout.d.ts +53 -0
  141. package/types/useGridSplitterResizing.d.ts +14 -0
  142. package/types/useNotDropTarget.d.ts +1 -0
@@ -0,0 +1,9 @@
1
+ const css = `
2
+ .vuuGridPlaceholder {
3
+ background-color: #ff0c0c1a;
4
+ position: relative;
5
+ }
6
+
7
+
8
+ `;
9
+ export default css;
@@ -0,0 +1,24 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useComponentCssInjection } from "@salt-ds/styles";
3
+ import { useWindow } from "@salt-ds/window";
4
+ import { useAsDropTarget } from "./useAsDropTarget.js";
5
+ import GridPlaceholder from "./GridPlaceholder.css";
6
+ import { registerComponent } from "@vuu-ui/vuu-utils";
7
+ const classBase = "vuuGridPlaceholder";
8
+ const GridPlaceholder_GridPlaceholder = ({ ...htmlAttributes })=>{
9
+ const targetWindow = useWindow();
10
+ useComponentCssInjection({
11
+ testId: "vuu-grid-layout-placeholder",
12
+ css: GridPlaceholder,
13
+ window: targetWindow
14
+ });
15
+ const dragDropHandlers = useAsDropTarget();
16
+ return /*#__PURE__*/ jsx("div", {
17
+ ...htmlAttributes,
18
+ ...dragDropHandlers,
19
+ className: `${classBase} vuuGridLayoutItem`,
20
+ "data-drop-target": true
21
+ });
22
+ };
23
+ registerComponent("GridPlaceholder", GridPlaceholder_GridPlaceholder, "component");
24
+ export { GridPlaceholder_GridPlaceholder as GridPlaceholder };
@@ -0,0 +1,11 @@
1
+ class GridSnapshotValidationError extends Error {
2
+ issues;
3
+ constructor(issues){
4
+ super(issues.map(({ message, path })=>`${path}: ${message}`).join("; "));
5
+ this.name = "GridSnapshotValidationError";
6
+ this.issues = [
7
+ ...issues
8
+ ];
9
+ }
10
+ }
11
+ export { GridSnapshotValidationError };
@@ -0,0 +1,62 @@
1
+ const css = `
2
+ .vuuGridSplitter {
3
+ --splitter-background: var(--salt-separable-secondary-borderColor);
4
+ --splitter-borderColor: white;
5
+ --splitter-borderStyle: none;
6
+ --splitter-borderWidth: 3px;
7
+ --splitter-size: 7px;
8
+ background-color: var(--splitter-background);
9
+ border-color: var(--splitter-borderColor);
10
+ border-style: var(--splitter-borderStyle);
11
+ border-width: var(--splitter-borderWidth);
12
+ z-index: 1;
13
+ position: relative;
14
+
15
+ & .vuu-grab-zone {
16
+ cursor: inherit;
17
+ background-color: #ffffff03;
18
+ position: absolute;
19
+ }
20
+ }
21
+
22
+ .vuuGridSplitter:hover, .vuuGridSplitter-active {
23
+ --splitter-background: var(--salt-separable-primary-borderColor);
24
+ }
25
+
26
+ .vuuGridSplitter[aria-orientation="vertical"] {
27
+ --splitter-borderStyle: none solid;
28
+ cursor: ew-resize;
29
+ width: var(--splitter-size);
30
+
31
+ & .vuu-grab-zone {
32
+ inset: -5px 0;
33
+ }
34
+ }
35
+
36
+ .vuuGridSplitter-offset-start[aria-orientation="vertical"] {
37
+ margin-block-start: var(--splitter-size);
38
+
39
+ & .vuu-grab-zone {
40
+ top: 0;
41
+ }
42
+ }
43
+
44
+ .vuuGridSplitter[aria-orientation="horizontal"] {
45
+ --splitter-borderStyle: solid none;
46
+ cursor: ns-resize;
47
+ height: var(--splitter-size);
48
+
49
+ & .vuu-grab-zone {
50
+ inset: 0 -5px;
51
+ }
52
+ }
53
+
54
+ .vuuGridSplitter-active[aria-orientation="vertical"] {
55
+ & .vuu-grab-zone {
56
+ inset: 0 -150px;
57
+ }
58
+ }
59
+
60
+
61
+ `;
62
+ export default css;
@@ -0,0 +1,26 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useComponentCssInjection } from "@salt-ds/styles";
3
+ import { useWindow } from "@salt-ds/window";
4
+ import GridSplitter from "./GridSplitter.css";
5
+ const GridSplitter_GridSplitter = ({ "aria-controls": ariaControls, ariaOrientation, offsetStart, orientation, ...htmlAttributes })=>{
6
+ const targetWindow = useWindow();
7
+ useComponentCssInjection({
8
+ testId: "vuu-grid-splitter",
9
+ css: GridSplitter,
10
+ window: targetWindow
11
+ });
12
+ const id = `${ariaControls}-splitter-${orientation[0]}`;
13
+ return /*#__PURE__*/ jsx("div", {
14
+ ...htmlAttributes,
15
+ "aria-controls": ariaControls,
16
+ "aria-orientation": ariaOrientation,
17
+ className: `vuuGridSplitter${offsetStart ? " vuuGridSplitter-offset-start" : ""}`,
18
+ draggable: true,
19
+ id: id,
20
+ role: "separator",
21
+ children: /*#__PURE__*/ jsx("div", {
22
+ className: "vuu-grab-zone"
23
+ })
24
+ });
25
+ };
26
+ export { GridSplitter_GridSplitter as GridSplitter };
@@ -0,0 +1,277 @@
1
+ import { GridSnapshotValidationError } from "./GridSnapshot.js";
2
+ const MIN_GRID_STACK_MEMBERS = 2;
3
+ const ok = (value)=>({
4
+ ok: true,
5
+ value
6
+ });
7
+ const failure = (code, message)=>({
8
+ error: {
9
+ code,
10
+ message
11
+ },
12
+ ok: false
13
+ });
14
+ const cloneSpan = ({ span, start })=>({
15
+ span,
16
+ start
17
+ });
18
+ const cloneArea = ({ column, row })=>({
19
+ column: cloneSpan(column),
20
+ row: cloneSpan(row)
21
+ });
22
+ const cloneMember = ({ id, label, title })=>({
23
+ id,
24
+ label,
25
+ title
26
+ });
27
+ const cloneMetadata = ({ minHeight, minWidth, resizeable })=>({
28
+ minHeight,
29
+ minWidth,
30
+ resizeable
31
+ });
32
+ const cloneGridStackState = (state)=>({
33
+ area: cloneArea(state.area),
34
+ id: state.id,
35
+ members: state.members.map(cloneMember),
36
+ metadata: cloneMetadata(state.metadata),
37
+ selectedItemId: state.selectedItemId
38
+ });
39
+ const gridStackItemIds = (state)=>state.members.map(({ id })=>id);
40
+ const findGridStackMember = (state, itemId)=>state.members.find(({ id })=>id === itemId);
41
+ const gridStackMemberIndex = (state, itemId)=>state.members.findIndex(({ id })=>id === itemId);
42
+ const gridStackSelectedIndex = (state)=>gridStackMemberIndex(state, state.selectedItemId);
43
+ const toGridStackSnapshot = (state)=>({
44
+ id: state.id,
45
+ itemIds: [
46
+ ...gridStackItemIds(state)
47
+ ],
48
+ selectedItemId: state.selectedItemId
49
+ });
50
+ const sameMembers = (left, right)=>left.length === right.length && left.every((member, index)=>member.id === right[index].id && member.label === right[index].label && member.title === right[index].title);
51
+ const sameOrder = (left, right)=>left.length === right.length && left.every((member, index)=>member.id === right[index].id);
52
+ const sameSpan = (left, right)=>left.span === right.span && left.start === right.start;
53
+ const sameArea = (left, right)=>sameSpan(left.column, right.column) && sameSpan(left.row, right.row);
54
+ const sameMetadata = (left, right)=>left.minHeight === right.minHeight && left.minWidth === right.minWidth && left.resizeable === right.resizeable;
55
+ const isSameGridStackState = (left, right)=>left.id === right.id && left.selectedItemId === right.selectedItemId && sameArea(left.area, right.area) && sameMetadata(left.metadata, right.metadata) && sameMembers(left.members, right.members);
56
+ const normalizeGridStackState = (state)=>{
57
+ const members = [];
58
+ const seen = new Set();
59
+ for (const member of state.members)if (member.id && !seen.has(member.id)) {
60
+ seen.add(member.id);
61
+ members.push(cloneMember(member));
62
+ }
63
+ return {
64
+ area: cloneArea(state.area),
65
+ id: state.id,
66
+ members,
67
+ metadata: cloneMetadata(state.metadata),
68
+ selectedItemId: seen.has(state.selectedItemId) ? state.selectedItemId : members[0]?.id ?? ""
69
+ };
70
+ };
71
+ const validateSpan = (span, path, issues)=>{
72
+ if (!span || !Number.isInteger(span.start) || !Number.isInteger(span.span) || span.start < 1 || span.span < 1) issues.push({
73
+ code: "INVALID_SPAN",
74
+ message: "stack area must use positive integer grid lines",
75
+ path
76
+ });
77
+ };
78
+ const validateGridStackState = (state, { itemAreas, path = "$" } = {})=>{
79
+ const issues = [];
80
+ if (!state.id) issues.push({
81
+ code: "EMPTY_ID",
82
+ message: "stack id must not be empty",
83
+ path: `${path}.id`
84
+ });
85
+ validateSpan(state.area?.column, `${path}.area.column`, issues);
86
+ validateSpan(state.area?.row, `${path}.area.row`, issues);
87
+ if (state.members.length < MIN_GRID_STACK_MEMBERS) issues.push({
88
+ code: "INVALID_STACK_MEMBERSHIP",
89
+ message: `a stack must contain at least ${MIN_GRID_STACK_MEMBERS} items`,
90
+ path: `${path}.members`
91
+ });
92
+ const seen = new Set();
93
+ state.members.forEach((member, index)=>{
94
+ const memberPath = `${path}.members[${index}]`;
95
+ if (!member.id) return void issues.push({
96
+ code: "EMPTY_ID",
97
+ message: "stack member id must not be empty",
98
+ path: `${memberPath}.id`
99
+ });
100
+ if (seen.has(member.id)) issues.push({
101
+ code: "DUPLICATE_ID",
102
+ message: `item "${member.id}" appears more than once in stack "${state.id}"`,
103
+ path: `${memberPath}.id`
104
+ });
105
+ seen.add(member.id);
106
+ if ("string" != typeof member.label) issues.push({
107
+ code: "INVALID_FIELD",
108
+ message: "stack member label must be a string",
109
+ path: `${memberPath}.label`
110
+ });
111
+ if (itemAreas) {
112
+ const area = itemAreas.get(member.id);
113
+ if (area) {
114
+ if (state.area && !sameArea(area, state.area)) issues.push({
115
+ code: "INVALID_STACK_POSITION",
116
+ message: `item "${member.id}" does not occupy the stack area`,
117
+ path: `${memberPath}.id`
118
+ });
119
+ } else issues.push({
120
+ code: "INVALID_STACK_MEMBERSHIP",
121
+ message: `item "${member.id}" is not a grid item`,
122
+ path: `${memberPath}.id`
123
+ });
124
+ }
125
+ });
126
+ if (!state.selectedItemId || !seen.has(state.selectedItemId)) issues.push({
127
+ code: "INVALID_STACK_SELECTION",
128
+ message: `selected item "${state.selectedItemId}" is not a stack member`,
129
+ path: `${path}.selectedItemId`
130
+ });
131
+ return issues;
132
+ };
133
+ const assertValidGridStackState = (state, options)=>{
134
+ const issues = validateGridStackState(state, options);
135
+ if (issues.length > 0) throw new GridSnapshotValidationError(issues);
136
+ return state;
137
+ };
138
+ const buildTransition = ({ added = [], dissolved = false, operation, positioned = false, previous, removed = [], state })=>{
139
+ const orderChanged = void 0 === previous || !sameOrder(previous.members, state.members);
140
+ const selectionChanged = void 0 === previous || previous.selectedItemId !== state.selectedItemId;
141
+ return {
142
+ added: added.map(cloneMember),
143
+ changed: void 0 === previous || dissolved || !isSameGridStackState(previous, state),
144
+ dissolved,
145
+ operation,
146
+ orderChanged,
147
+ positioned,
148
+ previous,
149
+ removed: removed.map(cloneMember),
150
+ selectionChanged,
151
+ stackId: state.id,
152
+ state
153
+ };
154
+ };
155
+ const createGridStack = (request)=>{
156
+ if (!request.id) return failure("INVALID_ITEM", "A grid stack requires a stack id");
157
+ const ids = new Set();
158
+ for (const { id } of request.members){
159
+ if (!id) return failure("INVALID_ITEM", "A stack member requires an item id");
160
+ if (ids.has(id)) return failure("DUPLICATE_ITEM_ID", `Grid item #${id} cannot be stacked twice`);
161
+ ids.add(id);
162
+ }
163
+ if (ids.size < MIN_GRID_STACK_MEMBERS) return failure("INVALID_TARGET", `A new stack requires at least ${MIN_GRID_STACK_MEMBERS} items`);
164
+ if (request.selectedItemId && !ids.has(request.selectedItemId)) return failure("TAB_NOT_FOUND", `Grid tab #${request.selectedItemId} is not a member of stack #${request.id}`);
165
+ const state = normalizeGridStackState({
166
+ area: request.area,
167
+ id: request.id,
168
+ members: request.members,
169
+ metadata: request.metadata ?? {},
170
+ selectedItemId: request.selectedItemId ?? request.members[0].id
171
+ });
172
+ return ok(buildTransition({
173
+ added: state.members,
174
+ operation: "create",
175
+ previous: void 0,
176
+ state
177
+ }));
178
+ };
179
+ const addGridStackItem = (state, { member, position, select })=>{
180
+ if (!member.id) return failure("INVALID_ITEM", "A stack member requires an item id");
181
+ if (findGridStackMember(state, member.id)) return failure("DUPLICATE_ITEM_ID", `Grid item #${member.id} is already a member of stack #${state.id}`);
182
+ const members = state.members.map(cloneMember);
183
+ const added = cloneMember(member);
184
+ let index = members.length;
185
+ if (position) {
186
+ const targetIndex = members.findIndex(({ id })=>id === position.targetItemId);
187
+ if (-1 === targetIndex) return failure("TAB_NOT_FOUND", `Grid tab #${position.targetItemId} not found in stack #${state.id}`);
188
+ index = "after" === position.placement ? targetIndex + 1 : targetIndex;
189
+ }
190
+ members.splice(index, 0, added);
191
+ const selectAdded = select ?? void 0 !== position;
192
+ const next = normalizeGridStackState({
193
+ ...state,
194
+ members,
195
+ selectedItemId: selectAdded ? added.id : state.selectedItemId
196
+ });
197
+ return ok(buildTransition({
198
+ added: [
199
+ added
200
+ ],
201
+ operation: "add",
202
+ positioned: void 0 !== position,
203
+ previous: state,
204
+ state: next
205
+ }));
206
+ };
207
+ const removeGridStackItem = (state, { itemId })=>{
208
+ const index = gridStackMemberIndex(state, itemId);
209
+ if (-1 === index) return failure("TAB_NOT_FOUND", `Grid tab #${itemId} not found in stack #${state.id}`);
210
+ const removed = state.members[index];
211
+ const members = state.members.filter(({ id })=>id !== itemId).map(cloneMember);
212
+ const selectedItemId = state.selectedItemId === itemId ? members[Math.min(index, members.length - 1)]?.id ?? "" : state.selectedItemId;
213
+ const next = normalizeGridStackState({
214
+ ...state,
215
+ members,
216
+ selectedItemId
217
+ });
218
+ return ok(buildTransition({
219
+ dissolved: members.length < MIN_GRID_STACK_MEMBERS,
220
+ operation: "remove",
221
+ previous: state,
222
+ removed: [
223
+ removed
224
+ ],
225
+ state: next
226
+ }));
227
+ };
228
+ const selectGridStackItem = (state, { itemId })=>{
229
+ if (!findGridStackMember(state, itemId)) return failure("TAB_NOT_FOUND", `Grid tab #${itemId} not found in stack #${state.id}`);
230
+ const next = state.selectedItemId === itemId ? state : cloneGridStackState({
231
+ ...state,
232
+ selectedItemId: itemId
233
+ });
234
+ return ok(buildTransition({
235
+ operation: "select",
236
+ previous: state,
237
+ state: next
238
+ }));
239
+ };
240
+ const reorderGridStackItem = (state, { activate = false, itemId, placement, targetItemId })=>{
241
+ const index = gridStackMemberIndex(state, itemId);
242
+ if (-1 === index) return failure("TAB_NOT_FOUND", `Grid tab #${itemId} not found in stack #${state.id}`);
243
+ if (-1 === gridStackMemberIndex(state, targetItemId)) return failure("TAB_NOT_FOUND", `Grid tab #${targetItemId} not found in stack #${state.id}`);
244
+ if (itemId === targetItemId) return failure("INVALID_TARGET", "A tab cannot be reordered relative to itself");
245
+ const members = state.members.map(cloneMember);
246
+ const [moved] = members.splice(index, 1);
247
+ const targetIndex = members.findIndex(({ id })=>id === targetItemId);
248
+ members.splice("after" === placement ? targetIndex + 1 : targetIndex, 0, moved);
249
+ const next = normalizeGridStackState({
250
+ ...state,
251
+ members,
252
+ selectedItemId: activate ? itemId : state.selectedItemId
253
+ });
254
+ return ok(buildTransition({
255
+ operation: "reorder",
256
+ previous: state,
257
+ state: next
258
+ }));
259
+ };
260
+ const renameGridStackItem = (state, { itemId, title })=>{
261
+ const member = findGridStackMember(state, itemId);
262
+ if (!member) return failure("TAB_NOT_FOUND", `Grid tab #${itemId} not found in stack #${state.id}`);
263
+ const next = cloneGridStackState({
264
+ ...state,
265
+ members: state.members.map((current)=>current.id === itemId ? {
266
+ ...current,
267
+ label: title,
268
+ title
269
+ } : current)
270
+ });
271
+ return ok(buildTransition({
272
+ operation: "rename",
273
+ previous: state,
274
+ state: next
275
+ }));
276
+ };
277
+ export { MIN_GRID_STACK_MEMBERS, addGridStackItem, assertValidGridStackState, cloneGridStackState, createGridStack, findGridStackMember, gridStackItemIds, gridStackMemberIndex, gridStackSelectedIndex, isSameGridStackState, normalizeGridStackState, removeGridStackItem, renameGridStackItem, reorderGridStackItem, selectGridStackItem, toGridStackSnapshot, validateGridStackState };
@@ -0,0 +1,8 @@
1
+ const css = `
2
+ .vuuIcon {
3
+ --vuu-icon-left: 0;
4
+ }
5
+
6
+
7
+ `;
8
+ export default css;
package/src/Icon.js ADDED
@@ -0,0 +1,26 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import Icon from "./Icon.css";
6
+ const classBase = "vuuIcon";
7
+ const Icon_Icon = ({ className, name, size, style: styleProp, ...htmlAttributes })=>{
8
+ const targetWindow = useWindow();
9
+ useComponentCssInjection({
10
+ testId: "vuu-icon",
11
+ css: Icon,
12
+ window: targetWindow
13
+ });
14
+ const style = "number" == typeof size ? {
15
+ ...styleProp,
16
+ "--vuu-icon-size": `${size}px`
17
+ } : styleProp;
18
+ return /*#__PURE__*/ jsx("span", {
19
+ ...htmlAttributes,
20
+ className: clsx(classBase, className),
21
+ "data-icon": name,
22
+ role: "img",
23
+ style: style
24
+ });
25
+ };
26
+ export { Icon_Icon as Icon };
@@ -0,0 +1,9 @@
1
+ const css = `
2
+ .vuuIconButton {
3
+ --saltButton-padding: 0;
4
+ --saltButton-minWidth: var(--salt-size-base);
5
+ }
6
+
7
+
8
+ `;
9
+ export default css;
@@ -0,0 +1,28 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { Button } from "@salt-ds/core";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import { Icon } from "./Icon.js";
7
+ import { forwardRef } from "react";
8
+ import IconButton_0 from "./IconButton.css";
9
+ const classBase = "vuuIconButton";
10
+ const IconButton_IconButton = /*#__PURE__*/ forwardRef(function({ "aria-label": ariaLabel, className, icon, size, ...buttonProps }, ref) {
11
+ const targetWindow = useWindow();
12
+ useComponentCssInjection({
13
+ testId: "vuu-icon-button",
14
+ css: IconButton_0,
15
+ window: targetWindow
16
+ });
17
+ return /*#__PURE__*/ jsx(Button, {
18
+ ...buttonProps,
19
+ className: clsx(classBase, className),
20
+ ref: ref,
21
+ children: /*#__PURE__*/ jsx(Icon, {
22
+ "aria-label": ariaLabel,
23
+ name: icon,
24
+ size: size
25
+ })
26
+ });
27
+ });
28
+ export { IconButton_IconButton as IconButton };
@@ -0,0 +1,10 @@
1
+ const css = `
2
+ .TabDialog.saltDialog {
3
+ & .TabDialog-content {
4
+ padding: 3px;
5
+ }
6
+ }
7
+
8
+
9
+ `;
10
+ export default css;
@@ -0,0 +1,77 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button, Dialog, DialogActions, DialogContent, DialogHeader, FormField, FormFieldLabel, Input } from "@salt-ds/core";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import { useCallback, useState } from "react";
6
+ import TabDialog from "./TabDialog.css";
7
+ const TabDialog_TabDialog = ({ open, onConfirm, onCancel, style, tabLabel = "", title = "Add new tab" })=>{
8
+ const targetWindow = useWindow();
9
+ useComponentCssInjection({
10
+ testId: "vuu-tab-dialog",
11
+ css: TabDialog,
12
+ window: targetWindow
13
+ });
14
+ const [value, setValue] = useState(tabLabel);
15
+ const inputCallbackRef = useCallback((el)=>{
16
+ if (el) el.select();
17
+ }, []);
18
+ const handleKeyDown = useCallback((evt)=>{
19
+ if ("Enter" === evt.key && value !== tabLabel) onConfirm(value);
20
+ }, [
21
+ onConfirm,
22
+ tabLabel,
23
+ value
24
+ ]);
25
+ return /*#__PURE__*/ jsxs(Dialog, {
26
+ open: open,
27
+ className: "TabDialog",
28
+ style: style,
29
+ children: [
30
+ /*#__PURE__*/ jsx(DialogHeader, {
31
+ header: title
32
+ }),
33
+ /*#__PURE__*/ jsx(DialogContent, {
34
+ children: /*#__PURE__*/ jsx("div", {
35
+ className: "TabDialog-content",
36
+ children: /*#__PURE__*/ jsxs(FormField, {
37
+ children: [
38
+ /*#__PURE__*/ jsx(FormFieldLabel, {
39
+ children: "New tab name"
40
+ }),
41
+ /*#__PURE__*/ jsx(Input, {
42
+ inputRef: inputCallbackRef,
43
+ value: value,
44
+ onChange: (event)=>{
45
+ setValue(event.target.value);
46
+ },
47
+ inputProps: {
48
+ onKeyDown: handleKeyDown
49
+ }
50
+ })
51
+ ]
52
+ })
53
+ })
54
+ }),
55
+ /*#__PURE__*/ jsxs(DialogActions, {
56
+ children: [
57
+ /*#__PURE__*/ jsx(Button, {
58
+ appearance: "solid",
59
+ sentiment: "negative",
60
+ onClick: onCancel,
61
+ children: "Cancel"
62
+ }),
63
+ /*#__PURE__*/ jsx(Button, {
64
+ disabled: "" === value.trim(),
65
+ appearance: "solid",
66
+ sentiment: "accented",
67
+ onClick: ()=>{
68
+ onConfirm(value);
69
+ },
70
+ children: "Confirm"
71
+ })
72
+ ]
73
+ })
74
+ ]
75
+ });
76
+ };
77
+ export { TabDialog_TabDialog as TabDialog };
@@ -0,0 +1,8 @@
1
+ const css = `
2
+ .TabMenuButton {
3
+ --saltButton-padding: var(--salt-spacing-200);
4
+ }
5
+
6
+
7
+ `;
8
+ export default css;
package/src/TabMenu.js ADDED
@@ -0,0 +1,78 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Icon } from "@vuu-ui/vuu-ui-controls";
3
+ import { Button, Menu, MenuItem, MenuPanel, MenuTrigger } from "@salt-ds/core";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import TabMenu from "./TabMenu.css";
7
+ import { useGridLayoutDispatch } from "./GridLayoutContext.js";
8
+ import { useCallback, useMemo } from "react";
9
+ import { useEditTabName } from "./useEditTabName.js";
10
+ const TabMenu_TabMenu = ({ allowClose = true, allowRename = true, layoutItemId, tabLabel })=>{
11
+ const targetWindow = useWindow();
12
+ useComponentCssInjection({
13
+ testId: "vuu-tab-menu",
14
+ css: TabMenu,
15
+ window: targetWindow
16
+ });
17
+ const { dialog, showTabEditDialog } = useEditTabName({
18
+ id: layoutItemId,
19
+ mode: "edit",
20
+ tabLabel
21
+ });
22
+ const dispatch = useGridLayoutDispatch();
23
+ const closeTab = useCallback(()=>{
24
+ dispatch({
25
+ type: "close",
26
+ id: layoutItemId
27
+ });
28
+ }, [
29
+ dispatch,
30
+ layoutItemId
31
+ ]);
32
+ const renameTab = useCallback(()=>{
33
+ showTabEditDialog();
34
+ }, [
35
+ showTabEditDialog
36
+ ]);
37
+ const menuItems = useMemo(()=>{
38
+ const items = [];
39
+ if (allowClose) items.push(/*#__PURE__*/ jsx(MenuItem, {
40
+ onClick: closeTab,
41
+ children: "Close"
42
+ }, "close"));
43
+ if (allowRename) items.push(/*#__PURE__*/ jsx(MenuItem, {
44
+ onClick: renameTab,
45
+ children: "Rename"
46
+ }, "rename"));
47
+ return items;
48
+ }, [
49
+ allowClose,
50
+ allowRename,
51
+ closeTab,
52
+ renameTab
53
+ ]);
54
+ return /*#__PURE__*/ jsxs(Fragment, {
55
+ children: [
56
+ /*#__PURE__*/ jsxs(Menu, {
57
+ children: [
58
+ /*#__PURE__*/ jsx(MenuTrigger, {
59
+ children: /*#__PURE__*/ jsx(Button, {
60
+ "aria-label": `${tabLabel} Settings`,
61
+ className: "TabMenuButton",
62
+ "data-embedded": true,
63
+ children: /*#__PURE__*/ jsx(Icon, {
64
+ "aria-hidden": true,
65
+ name: "more-vert"
66
+ })
67
+ })
68
+ }),
69
+ /*#__PURE__*/ jsx(MenuPanel, {
70
+ children: menuItems
71
+ })
72
+ ]
73
+ }),
74
+ dialog
75
+ ]
76
+ });
77
+ };
78
+ export { TabMenu_TabMenu as TabMenu };