playbook_ui 15.5.0.pre.alpha.draggablefix12577 → 15.5.0.pre.alpha.typeaheadfix12605
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props.html.erb +1 -163
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_table_props.jsx +0 -190
- data/app/pb_kits/playbook/pb_dialog/docs/_dialog_compound_components.html.erb +31 -0
- data/app/pb_kits/playbook/pb_dialog/index.js +15 -10
- data/app/pb_kits/playbook/pb_draggable/context/index.tsx +121 -114
- data/app/pb_kits/playbook/pb_draggable/context/types.ts +4 -2
- data/app/pb_kits/playbook/pb_draggable/docs/_draggable_multiple_containers_dropzone.jsx +1 -0
- data/app/pb_kits/playbook/pb_draggable/docs/_draggable_multiple_containers_dropzone.md +3 -1
- data/app/pb_kits/playbook/pb_typeahead/components/ClearIndicator.tsx +13 -2
- data/dist/chunks/{_typeahead-CdVpaHUH.js → _typeahead-CkkCTRLh.js} +2 -2
- data/dist/chunks/vendor.js +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +3 -3
|
@@ -39,56 +39,59 @@ const reducer = (state: InitialStateType, action: ActionType) => {
|
|
|
39
39
|
|
|
40
40
|
return { ...state, items: newItems };
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
// Used only when enableCrossContainerPreview is true
|
|
44
|
+
case "REORDER_ITEMS_CROSS_CONTAINER": {
|
|
43
45
|
const { dragId, targetId, newContainer } = action.payload;
|
|
44
46
|
const newItems = [...state.items];
|
|
45
|
-
const draggedItem = newItems.find(item => item && item.id === dragId);
|
|
47
|
+
const draggedItem = newItems.find((item) => item && item.id === dragId);
|
|
48
|
+
|
|
49
|
+
if (!draggedItem) return state;
|
|
50
|
+
|
|
46
51
|
const draggedIndex = newItems.indexOf(draggedItem);
|
|
47
|
-
const targetIndex = newItems.findIndex(
|
|
52
|
+
const targetIndex = newItems.findIndex(
|
|
53
|
+
(item) => item && item.id === targetId
|
|
54
|
+
);
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
const updatedItem = { ...draggedItem, container: newContainer };
|
|
56
|
+
if (draggedIndex === -1 || targetIndex === -1) return state;
|
|
51
57
|
|
|
58
|
+
const updatedItem = { ...draggedItem, container: newContainer };
|
|
52
59
|
newItems.splice(draggedIndex, 1);
|
|
53
60
|
newItems.splice(targetIndex, 0, updatedItem);
|
|
54
61
|
|
|
55
62
|
return { ...state, items: newItems };
|
|
56
63
|
}
|
|
57
|
-
|
|
64
|
+
|
|
65
|
+
// Used only when enableCrossContainerPreview is true
|
|
66
|
+
case "MOVE_TO_CONTAINER_END": {
|
|
58
67
|
const { dragId, newContainer } = action.payload;
|
|
59
68
|
const newItems = [...state.items];
|
|
60
|
-
const draggedItem = newItems.find(item => item && item.id === dragId);
|
|
69
|
+
const draggedItem = newItems.find((item) => item && item.id === dragId);
|
|
70
|
+
|
|
71
|
+
if (!draggedItem) return state;
|
|
72
|
+
|
|
61
73
|
const draggedIndex = newItems.indexOf(draggedItem);
|
|
74
|
+
if (draggedIndex === -1) return state;
|
|
62
75
|
|
|
63
|
-
// Update container temporarily so dropzone preview works correctly
|
|
64
76
|
const updatedItem = { ...draggedItem, container: newContainer };
|
|
65
77
|
|
|
66
78
|
// Remove from current position
|
|
67
79
|
newItems.splice(draggedIndex, 1);
|
|
68
80
|
|
|
69
|
-
//
|
|
70
|
-
const lastIndexInContainer = newItems
|
|
81
|
+
// Insert at end of target container
|
|
82
|
+
const lastIndexInContainer = newItems
|
|
83
|
+
.map((item) => item && item.container)
|
|
84
|
+
.lastIndexOf(newContainer);
|
|
85
|
+
|
|
71
86
|
if (lastIndexInContainer === -1) {
|
|
72
|
-
// Container is empty, add to end
|
|
73
87
|
newItems.push(updatedItem);
|
|
74
88
|
} else {
|
|
75
|
-
// Insert after last item in container
|
|
76
89
|
newItems.splice(lastIndexInContainer + 1, 0, updatedItem);
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
return { ...state, items: newItems };
|
|
80
93
|
}
|
|
81
|
-
|
|
82
|
-
const { itemId, originalContainer } = action.payload;
|
|
83
|
-
return {
|
|
84
|
-
...state,
|
|
85
|
-
items: state.items.map(item =>
|
|
86
|
-
item.id === itemId
|
|
87
|
-
? { ...item, container: originalContainer }
|
|
88
|
-
: item
|
|
89
|
-
)
|
|
90
|
-
};
|
|
91
|
-
}
|
|
94
|
+
|
|
92
95
|
default:
|
|
93
96
|
return state;
|
|
94
97
|
}
|
|
@@ -111,7 +114,9 @@ export const DraggableProvider = ({
|
|
|
111
114
|
onDrop,
|
|
112
115
|
onDragOver,
|
|
113
116
|
dropZone = { type: 'ghost', color: 'neutral', direction: 'vertical' },
|
|
114
|
-
providerId = 'default', // fallback provided for backward compatibility
|
|
117
|
+
providerId = 'default', // fallback provided for backward compatibility
|
|
118
|
+
// Opt-in flag for cross-container preview
|
|
119
|
+
enableCrossContainerPreview = false,
|
|
115
120
|
}: DraggableProviderType) => {
|
|
116
121
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
117
122
|
|
|
@@ -144,7 +149,6 @@ export const DraggableProvider = ({
|
|
|
144
149
|
}, [state.items]);
|
|
145
150
|
|
|
146
151
|
const handleDragStart = (id: string, container: string) => {
|
|
147
|
-
console.log('[Draggable] handleDragStart:', { id, container, providerId });
|
|
148
152
|
dispatch({ type: 'SET_DRAG_DATA', payload: { id: id, initialGroup: container, originId: providerId } });
|
|
149
153
|
dispatch({ type: 'SET_IS_DRAGGING', payload: id });
|
|
150
154
|
if (onDragStart) onDragStart(id, container);
|
|
@@ -154,18 +158,42 @@ export const DraggableProvider = ({
|
|
|
154
158
|
if (state.dragData.originId !== providerId) return; // Ignore drag events from other providers
|
|
155
159
|
|
|
156
160
|
if (state.dragData.id !== id) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
if (enableCrossContainerPreview) {
|
|
162
|
+
// Used only when enableCrossContainerPreview is true
|
|
163
|
+
const draggedItem = state.items.find(
|
|
164
|
+
(item) => item && item.id === state.dragData.id
|
|
165
|
+
);
|
|
166
|
+
const currentContainer =
|
|
167
|
+
draggedItem && draggedItem.container
|
|
168
|
+
? draggedItem.container
|
|
169
|
+
: state.dragData.initialGroup;
|
|
170
|
+
|
|
171
|
+
const isCrossContainer =
|
|
172
|
+
currentContainer !== container &&
|
|
173
|
+
(currentContainer !== undefined || container !== undefined);
|
|
174
|
+
|
|
175
|
+
if (isCrossContainer) {
|
|
176
|
+
dispatch({
|
|
177
|
+
type: "REORDER_ITEMS_CROSS_CONTAINER",
|
|
178
|
+
payload: {
|
|
179
|
+
dragId: state.dragData.id,
|
|
180
|
+
targetId: id,
|
|
181
|
+
newContainer: container,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
} else {
|
|
185
|
+
// Same container: keep original behavior
|
|
186
|
+
dispatch({
|
|
187
|
+
type: "REORDER_ITEMS",
|
|
188
|
+
payload: { dragId: state.dragData.id, targetId: id },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
164
191
|
} else {
|
|
165
|
-
//
|
|
166
|
-
dispatch({
|
|
192
|
+
// Original behavior (no preview across containers)
|
|
193
|
+
dispatch({type: "REORDER_ITEMS", payload: { dragId: state.dragData.id, targetId: id }});
|
|
167
194
|
}
|
|
168
|
-
|
|
195
|
+
|
|
196
|
+
dispatch({type: "SET_DRAG_DATA",payload: {id: state.dragData.id, initialGroup: container, originId: providerId}});
|
|
169
197
|
}
|
|
170
198
|
if (onDragEnter) onDragEnter(id, container);
|
|
171
199
|
};
|
|
@@ -174,33 +202,30 @@ export const DraggableProvider = ({
|
|
|
174
202
|
const draggedItemId = state.dragData.id;
|
|
175
203
|
const originalContainer = state.dragData.initialGroup;
|
|
176
204
|
|
|
177
|
-
console.log('[Draggable] handleDragEnd:', { draggedItemId, originalContainer });
|
|
178
|
-
|
|
179
|
-
if (!draggedItemId) {
|
|
180
|
-
console.warn('[Draggable] handleDragEnd: No draggedItemId found');
|
|
181
|
-
dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
|
|
182
|
-
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
|
|
183
|
-
dispatch({ type: 'SET_DRAG_DATA', payload: { id: "", initialGroup: "", originId: "" } });
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const draggedItem = state.items.find(item => item && item.id === draggedItemId);
|
|
188
|
-
const finalContainer = draggedItem ? draggedItem.container : originalContainer;
|
|
189
|
-
|
|
190
|
-
console.log('[Draggable] handleDragEnd item found:', { draggedItem: !!draggedItem, finalContainer });
|
|
191
|
-
|
|
192
|
-
// Find items above and below in the same container
|
|
193
|
-
const itemsInContainer = state.items.filter(item => item && item.container === finalContainer);
|
|
194
|
-
const indexInContainer = itemsInContainer.findIndex(item => item && item.id === draggedItemId);
|
|
195
|
-
const itemAbove = indexInContainer > 0 ? itemsInContainer[indexInContainer - 1] : null;
|
|
196
|
-
const itemBelow = indexInContainer < itemsInContainer.length - 1 ? itemsInContainer[indexInContainer + 1] : null;
|
|
197
|
-
|
|
198
205
|
dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
|
|
199
206
|
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
|
|
200
207
|
dispatch({ type: 'SET_DRAG_DATA', payload: { id: "", initialGroup: "", originId: "" } });
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
if (onDragEnd) {
|
|
209
|
+
if (!enableCrossContainerPreview) {
|
|
210
|
+
onDragEnd();
|
|
211
|
+
} else {
|
|
212
|
+
const draggedItem = state.items.find(item => item && item.id === draggedItemId);
|
|
213
|
+
const finalContainer = draggedItem ? draggedItem.container : originalContainer;
|
|
214
|
+
|
|
215
|
+
const itemsInContainer = state.items.filter(item => item && item.container === finalContainer);
|
|
216
|
+
const indexInContainer = itemsInContainer.findIndex(item => item && item.id === draggedItemId);
|
|
217
|
+
const itemAbove = indexInContainer > 0 ? itemsInContainer[indexInContainer - 1] : null;
|
|
218
|
+
const itemBelow = indexInContainer < itemsInContainer.length - 1 ? itemsInContainer[indexInContainer + 1] : null;
|
|
219
|
+
|
|
220
|
+
onDragEnd(
|
|
221
|
+
draggedItemId,
|
|
222
|
+
finalContainer,
|
|
223
|
+
originalContainer,
|
|
224
|
+
itemAbove,
|
|
225
|
+
itemBelow
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
204
229
|
};
|
|
205
230
|
|
|
206
231
|
const changeCategory = (itemId: string, container: string) => {
|
|
@@ -212,46 +237,31 @@ export const DraggableProvider = ({
|
|
|
212
237
|
|
|
213
238
|
const draggedItemId = state.dragData.id;
|
|
214
239
|
const originalContainer = state.dragData.initialGroup;
|
|
215
|
-
|
|
216
|
-
console.log('[Draggable] handleDrop:', { draggedItemId, container, originalContainer });
|
|
217
|
-
|
|
218
|
-
if (!draggedItemId) {
|
|
219
|
-
console.warn('[Draggable] handleDrop: No draggedItemId found');
|
|
220
|
-
return; // Guard against missing drag data when dropping too quickly
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const draggedItem = state.items.find(item => item && item.id === draggedItemId);
|
|
224
|
-
|
|
225
|
-
if (!draggedItem) {
|
|
226
|
-
console.error('[Draggable] handleDrop: Item not found in state', { draggedItemId, itemsCount: state.items.length });
|
|
227
|
-
// Item not found in state: clear drag state and exit
|
|
228
|
-
dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
|
|
229
|
-
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
|
|
230
|
-
dispatch({ type: 'SET_DRAG_DATA', payload: { id: "", initialGroup: "", originId: "" } });
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// If dropping in a different container and item hasn't been moved there yet, move to end
|
|
235
|
-
if (container !== originalContainer && draggedItem.container !== container) {
|
|
236
|
-
console.log('[Draggable] handleDrop: Moving to container end');
|
|
237
|
-
dispatch({ type: 'MOVE_TO_CONTAINER_END', payload: { dragId: draggedItemId, newContainer: container } });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Find items above and below in the same container
|
|
241
|
-
const itemsInContainer = state.items.filter(item => item && item.container === container);
|
|
242
|
-
const indexInContainer = itemsInContainer.findIndex(item => item && item.id === draggedItemId);
|
|
243
|
-
const itemAbove = indexInContainer > 0 ? itemsInContainer[indexInContainer - 1] : null;
|
|
244
|
-
const itemBelow = indexInContainer < itemsInContainer.length - 1 ? itemsInContainer[indexInContainer + 1] : null;
|
|
245
|
-
|
|
240
|
+
|
|
246
241
|
dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
|
|
247
242
|
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
changeCategory(state.dragData.id, container);
|
|
244
|
+
if (onDrop) {
|
|
245
|
+
if (!enableCrossContainerPreview) {
|
|
246
|
+
onDrop(container);
|
|
247
|
+
} else {
|
|
248
|
+
const draggedItem = state.items.find(item => item && item.id === draggedItemId);
|
|
249
|
+
const updatedItem = draggedItem ? { ...draggedItem, container } : null;
|
|
250
|
+
|
|
251
|
+
const itemsInContainer = state.items.filter(item => item && item.container === container);
|
|
252
|
+
const indexInContainer = itemsInContainer.findIndex(item => item && item.id === draggedItemId);
|
|
253
|
+
const itemAbove = indexInContainer > 0 ? itemsInContainer[indexInContainer - 1] : null;
|
|
254
|
+
const itemBelow = indexInContainer < itemsInContainer.length - 1 ? itemsInContainer[indexInContainer + 1] : null;
|
|
255
|
+
|
|
256
|
+
onDrop(
|
|
257
|
+
draggedItemId,
|
|
258
|
+
container,
|
|
259
|
+
originalContainer,
|
|
260
|
+
updatedItem,
|
|
261
|
+
itemAbove,
|
|
262
|
+
itemBelow
|
|
263
|
+
);
|
|
264
|
+
}
|
|
255
265
|
}
|
|
256
266
|
};
|
|
257
267
|
|
|
@@ -260,23 +270,20 @@ export const DraggableProvider = ({
|
|
|
260
270
|
|
|
261
271
|
e.preventDefault();
|
|
262
272
|
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: container });
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
dispatch({ type: 'SET_DRAG_DATA', payload: { id: state.dragData.id, initialGroup: container, originId: providerId } });
|
|
276
|
-
} else if (!draggedItem) {
|
|
277
|
-
console.error('[Draggable] handleDragOver: Item not found', { dragId: state.dragData.id, itemsCount: state.items.length });
|
|
273
|
+
|
|
274
|
+
if (enableCrossContainerPreview && state.dragData.id) {
|
|
275
|
+
// Only when enableCrossContainerPreview is true: when hovering over a different container, move item to end
|
|
276
|
+
const draggedItem = state.items.find(
|
|
277
|
+
(item) => item && item.id === state.dragData.id
|
|
278
|
+
);
|
|
279
|
+
if (draggedItem && draggedItem.container !== container) {
|
|
280
|
+
dispatch({
|
|
281
|
+
type: "MOVE_TO_CONTAINER_END",
|
|
282
|
+
payload: { dragId: state.dragData.id, newContainer: container },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
278
285
|
}
|
|
279
|
-
|
|
286
|
+
|
|
280
287
|
if (onDragOver) onDragOver(e, container);
|
|
281
288
|
};
|
|
282
289
|
|
|
@@ -300,4 +307,4 @@ export const DraggableProvider = ({
|
|
|
300
307
|
return (
|
|
301
308
|
<DragContext.Provider value={contextValue}>{children}</DragContext.Provider>
|
|
302
309
|
);
|
|
303
|
-
};
|
|
310
|
+
};
|
|
@@ -18,6 +18,7 @@ export type ActionType =
|
|
|
18
18
|
} }
|
|
19
19
|
| { type: 'SET_IS_DRAGGING'; payload: string }
|
|
20
20
|
| { type: 'SET_ACTIVE_CONTAINER'; payload: string }
|
|
21
|
+
| { type: 'SET_CROSS_CONTAINER_PREVIEW'; payload: boolean }
|
|
21
22
|
| { type: 'CHANGE_CATEGORY'; payload: { itemId: string; container: string } }
|
|
22
23
|
| { type: 'REORDER_ITEMS'; payload: { dragId: string; targetId: string } }
|
|
23
24
|
| { type: 'REORDER_ITEMS_CROSS_CONTAINER'; payload: { dragId: string; targetId: string; newContainer: string } }
|
|
@@ -36,9 +37,10 @@ export type ActionType =
|
|
|
36
37
|
onReorder: (items: ItemType[]) => void;
|
|
37
38
|
onDragStart?: (id: string, container: string) => void;
|
|
38
39
|
onDragEnter?: (id: string, container: string) => void;
|
|
39
|
-
onDragEnd?: (
|
|
40
|
-
onDrop?: (
|
|
40
|
+
onDragEnd?: (...args: any[]) => void;
|
|
41
|
+
onDrop?: (...args: any[]) => void;
|
|
41
42
|
onDragOver?: (e: Event, container: string) => void;
|
|
42
43
|
dropZone?: DropZoneConfig | string; // Can accept string for backward compatibility
|
|
43
44
|
providerId?: string;
|
|
45
|
+
enableCrossContainerPreview?: boolean;
|
|
44
46
|
}
|
|
@@ -83,6 +83,7 @@ const DraggableMultipleContainersDropzone = (props) => {
|
|
|
83
83
|
return (
|
|
84
84
|
<DraggableProvider
|
|
85
85
|
dropZone={{type: "outline"}}
|
|
86
|
+
enableCrossContainerPreview
|
|
86
87
|
initialItems={data}
|
|
87
88
|
onDragEnd={(draggedItemId, finalContainer, originalContainer, itemAbove, itemBelow) => {
|
|
88
89
|
console.log(`Dragged Item ID: ${draggedItemId}`);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
The multiple container functionality also supports customized dropzone styling as shown here.
|
|
2
2
|
|
|
3
|
-
In addition to this, the `
|
|
3
|
+
In addition to this, the `enableCrossContainerPreview` prop can be used on the `DraggableProvider` as shown here to enable dropzone preview for cross-container dragging.
|
|
4
|
+
|
|
5
|
+
With `enableCrossContainerPreview`, the `onDrop` and `onDragEnd` event listeners will also provide several arguments to allow developers more context from the drag event.
|
|
4
6
|
|
|
5
7
|
The `onDrop` callback is triggered when an item is successfully dropped into a container. It provides the following arguments:
|
|
6
8
|
|
|
@@ -7,18 +7,29 @@ type ClearContainerProps = {
|
|
|
7
7
|
id: string,
|
|
8
8
|
},
|
|
9
9
|
clearValue: () => void,
|
|
10
|
+
innerProps?: any,
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const ClearContainer = (props: ClearContainerProps): React.ReactElement => {
|
|
13
|
-
const { selectProps, clearValue } = props
|
|
13
|
+
const ClearContainer = (props: ClearContainerProps | any): React.ReactElement => {
|
|
14
|
+
const { selectProps, clearValue, innerProps } = props
|
|
14
15
|
useEffect(() => {
|
|
15
16
|
document.addEventListener(`pb-typeahead-kit-${selectProps.id}:clear`, clearValue)
|
|
16
17
|
}, [clearValue, selectProps.id])
|
|
17
18
|
|
|
19
|
+
// To stop this from bubbling up when inside a dialog or other modal
|
|
20
|
+
const handleMouseDown = (event: React.MouseEvent) => {
|
|
21
|
+
event.stopPropagation()
|
|
22
|
+
innerProps?.onMouseDown?.(event)
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
return (
|
|
19
26
|
<components.ClearIndicator
|
|
20
27
|
className="clear_indicator"
|
|
21
28
|
{...props}
|
|
29
|
+
innerProps={{
|
|
30
|
+
...innerProps,
|
|
31
|
+
onMouseDown: handleMouseDown,
|
|
32
|
+
}}
|
|
22
33
|
/>
|
|
23
34
|
)
|
|
24
35
|
}
|