playbook_ui 16.11.0.pre.rc.2 → 16.11.0.pre.rc.3
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_draggable/_draggable.scss +3 -0
- data/app/pb_kits/playbook/pb_draggable/subcomponents/DraggableItem.tsx +53 -29
- data/app/pb_kits/playbook/pb_draggable/touchDrag.test.js +46 -0
- data/app/pb_kits/playbook/pb_draggable/utilities/touchDrag.ts +272 -54
- data/app/pb_kits/playbook/pb_nav/_item.tsx +3 -2
- data/app/pb_kits/playbook/pb_nav/_nav.tsx +1 -1
- data/app/pb_kits/playbook/pb_nav/docs/_playground.json +117 -213
- data/app/pb_kits/playbook/pb_nav/docs/_playground.overrides.json +127 -319
- data/app/pb_kits/playbook/pb_nav/kit.schema.json +2 -1
- data/app/pb_kits/playbook/pb_nav/navTypes.ts +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.scss +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/_tiptap_styles.scss +2 -2
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +45 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +144 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +25 -1
- data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +29 -85
- data/app/pb_kits/playbook/pb_typeahead/components/PillRenderer.tsx +187 -0
- data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +236 -10
- data/app/pb_kits/playbook/pb_typeahead/docs/_playground.json +8 -2
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.html.erb +39 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.jsx +63 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills.md +7 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_draggable_pills_rails.md +7 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_typeahead/kit.schema.json +24 -0
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +9 -0
- data/dist/chunks/{_typeahead-COksV0jV.js → _typeahead-DjVWQJpA.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/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ac0205b50ee49eadbe6eace5e060cf97a7cb52ad2187adfd0f367a69de229d7
|
|
4
|
+
data.tar.gz: af1a7209ac89983dd1aebf1fb746f119e7251301417f9c857319e7ef09a28f72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf7787a91d5395c55f3b86afc10a59ab765cc90d005a536672d75842d989ace47bb840f35bf783ba6765f00d71e3111f4fd85dab554a3194b00a7a031ddd8953
|
|
7
|
+
data.tar.gz: 004b1e4061a164048d880c73918a9292f43588c1e48656c667ae68878faade0114d588b69a033816b676dbfca704a947edcd03c3b175405a6bc526933a75ae2e
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { globalProps } from "../../utilities/globalProps";
|
|
10
10
|
import { DraggableContext } from "../context";
|
|
11
11
|
import { noop } from '../../utilities/object'
|
|
12
|
-
import { bindTouchDrag, isTouchDragDevice } from "../utilities/touchDrag";
|
|
12
|
+
import { bindMousePointerDrag, bindTouchDrag, isTouchDragDevice } from "../utilities/touchDrag";
|
|
13
13
|
|
|
14
14
|
type DraggableItemProps = {
|
|
15
15
|
aria?: { [key: string]: string };
|
|
@@ -27,6 +27,10 @@ type DraggableItemProps = {
|
|
|
27
27
|
onDragStart?: () => void,
|
|
28
28
|
onDrop?: () => void,
|
|
29
29
|
dragId?: string;
|
|
30
|
+
/** Whole-item pointer drag (e.g. Typeahead pills without a grip). Disables HTML5 drag. */
|
|
31
|
+
pointerDrag?: boolean;
|
|
32
|
+
/** Handle-only pointer drag (e.g. Typeahead pills with a grip). Disables HTML5 drag. */
|
|
33
|
+
handlePointerDrag?: boolean;
|
|
30
34
|
tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div' | 'tr' | 'th' | 'td' | 'thead' | 'col' | 'tbody',
|
|
31
35
|
};
|
|
32
36
|
|
|
@@ -48,6 +52,8 @@ const DraggableItem = (props: DraggableItemProps) => {
|
|
|
48
52
|
onDragOver = noop,
|
|
49
53
|
onDragStart = noop,
|
|
50
54
|
onDrop = noop,
|
|
55
|
+
pointerDrag = false,
|
|
56
|
+
handlePointerDrag = false,
|
|
51
57
|
} = props;
|
|
52
58
|
|
|
53
59
|
const {
|
|
@@ -59,11 +65,11 @@ const DraggableItem = (props: DraggableItemProps) => {
|
|
|
59
65
|
handleDragOver,
|
|
60
66
|
dropZone = 'ghost',
|
|
61
67
|
dropZoneColor = 'neutral',
|
|
62
|
-
direction = 'horizontal'
|
|
63
68
|
} = DraggableContext();
|
|
64
69
|
|
|
65
70
|
const itemRef = React.useRef<HTMLElement>(null);
|
|
66
71
|
const [useTouchDrag, setUseTouchDrag] = React.useState(false);
|
|
72
|
+
const [usePointerMouseDrag, setUsePointerMouseDrag] = React.useState(false);
|
|
67
73
|
const handlersRef = React.useRef({
|
|
68
74
|
handleDragStart,
|
|
69
75
|
handleDragEnter,
|
|
@@ -90,9 +96,36 @@ const DraggableItem = (props: DraggableItemProps) => {
|
|
|
90
96
|
onDragEnd,
|
|
91
97
|
};
|
|
92
98
|
|
|
93
|
-
React.
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
const dragHandlers = React.useMemo(() => ({
|
|
100
|
+
onDragStart: (id: string, itemContainer?: string) => {
|
|
101
|
+
handlersRef.current.handleDragStart(id, itemContainer);
|
|
102
|
+
handlersRef.current.onDragStart();
|
|
103
|
+
},
|
|
104
|
+
onDragEnter: (targetDragId: string, targetContainer?: string) => {
|
|
105
|
+
handlersRef.current.handleDragEnter(targetDragId, targetContainer);
|
|
106
|
+
handlersRef.current.onDragEnter();
|
|
107
|
+
},
|
|
108
|
+
onDragOver: (event: Event, targetContainer?: string) => {
|
|
109
|
+
handlersRef.current.handleDragOver(event, targetContainer);
|
|
110
|
+
handlersRef.current.onDragOver();
|
|
111
|
+
},
|
|
112
|
+
onDrop: (dropContainer?: string) => {
|
|
113
|
+
handlersRef.current.handleDrop(dropContainer);
|
|
114
|
+
handlersRef.current.onDrop();
|
|
115
|
+
},
|
|
116
|
+
onDragEnd: () => {
|
|
117
|
+
handlersRef.current.handleDragEnd();
|
|
118
|
+
handlersRef.current.onDragEnd();
|
|
119
|
+
},
|
|
120
|
+
}), []);
|
|
121
|
+
|
|
122
|
+
React.useLayoutEffect(() => {
|
|
123
|
+
const touchMode = isTouchDragDevice();
|
|
124
|
+
|
|
125
|
+
setUseTouchDrag(touchMode);
|
|
126
|
+
// Pointer drag is opt-in only — regular Draggable items keep HTML5 drag on the whole item.
|
|
127
|
+
setUsePointerMouseDrag(!touchMode && (pointerDrag || handlePointerDrag));
|
|
128
|
+
}, [dragId, container, children, pointerDrag, handlePointerDrag]);
|
|
96
129
|
|
|
97
130
|
React.useEffect(() => {
|
|
98
131
|
if (!useTouchDrag || !itemRef.current || !dragId) return;
|
|
@@ -101,30 +134,21 @@ const DraggableItem = (props: DraggableItemProps) => {
|
|
|
101
134
|
dragId,
|
|
102
135
|
container,
|
|
103
136
|
itemElement: itemRef.current,
|
|
104
|
-
handlers:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
onDrop: (dropContainer) => {
|
|
118
|
-
handlersRef.current.handleDrop(dropContainer);
|
|
119
|
-
handlersRef.current.onDrop();
|
|
120
|
-
},
|
|
121
|
-
onDragEnd: () => {
|
|
122
|
-
handlersRef.current.handleDragEnd();
|
|
123
|
-
handlersRef.current.onDragEnd();
|
|
124
|
-
},
|
|
125
|
-
},
|
|
137
|
+
handlers: dragHandlers,
|
|
138
|
+
});
|
|
139
|
+
}, [useTouchDrag, dragId, container, dragHandlers]);
|
|
140
|
+
|
|
141
|
+
React.useEffect(() => {
|
|
142
|
+
if (!usePointerMouseDrag || !itemRef.current || !dragId) return;
|
|
143
|
+
|
|
144
|
+
return bindMousePointerDrag({
|
|
145
|
+
dragId,
|
|
146
|
+
container,
|
|
147
|
+
itemElement: itemRef.current,
|
|
148
|
+
handlers: dragHandlers,
|
|
149
|
+
allowWholeItem: pointerDrag,
|
|
126
150
|
});
|
|
127
|
-
}, [
|
|
151
|
+
}, [usePointerMouseDrag, dragId, container, dragHandlers, pointerDrag]);
|
|
128
152
|
|
|
129
153
|
const ariaProps = buildAriaProps(aria);
|
|
130
154
|
const dataProps = buildDataProps(data);
|
|
@@ -188,7 +212,7 @@ const DraggableItem = (props: DraggableItemProps) => {
|
|
|
188
212
|
{...htmlProps}
|
|
189
213
|
className={classes}
|
|
190
214
|
data-pb-drag-id={dragId}
|
|
191
|
-
draggable={!useTouchDrag}
|
|
215
|
+
draggable={!useTouchDrag && !usePointerMouseDrag}
|
|
192
216
|
id={id}
|
|
193
217
|
key={dragId}
|
|
194
218
|
onDrag={onDrag}
|
|
@@ -35,4 +35,50 @@ describe("touchDrag utilities", () => {
|
|
|
35
35
|
const item = document.querySelector(".pb_draggable_item");
|
|
36
36
|
expect(getContainerFromElement(item)).toBe("To Do");
|
|
37
37
|
});
|
|
38
|
+
|
|
39
|
+
test("resolvePointerDragTarget uses sibling slots when pointer stays on dragged pill", () => {
|
|
40
|
+
document.body.innerHTML = `
|
|
41
|
+
<div class="pb_draggable_container" data-pb-drag-container="typeahead-pills">
|
|
42
|
+
<div class="pb_draggable_item" data-pb-drag-id="b"></div>
|
|
43
|
+
<div class="pb_draggable_item" data-pb-drag-id="c"></div>
|
|
44
|
+
<div class="pb_draggable_item" data-pb-drag-id="a"></div>
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const rects = {
|
|
49
|
+
b: { left: 0, width: 100 },
|
|
50
|
+
c: { left: 100, width: 100 },
|
|
51
|
+
a: { left: 200, width: 100 },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
document.querySelectorAll(".pb_draggable_item").forEach((item) => {
|
|
55
|
+
const id = item.getAttribute("data-pb-drag-id");
|
|
56
|
+
item.getBoundingClientRect = () => ({
|
|
57
|
+
left: rects[id].left,
|
|
58
|
+
width: rects[id].width,
|
|
59
|
+
right: rects[id].left + rects[id].width,
|
|
60
|
+
top: 0,
|
|
61
|
+
bottom: 20,
|
|
62
|
+
height: 20,
|
|
63
|
+
x: rects[id].left,
|
|
64
|
+
y: 0,
|
|
65
|
+
toJSON: () => ({}),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const { resolvePointerDragTarget } = require("./utilities/touchDrag");
|
|
70
|
+
|
|
71
|
+
document.elementFromPoint = () => document.querySelector('[data-pb-drag-id="a"]');
|
|
72
|
+
document.elementsFromPoint = () => [document.querySelector('[data-pb-drag-id="a"]')];
|
|
73
|
+
|
|
74
|
+
expect(resolvePointerDragTarget(250, 10, "a")).toEqual({
|
|
75
|
+
targetDragId: null,
|
|
76
|
+
targetContainer: "typeahead-pills",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(resolvePointerDragTarget(120, 10, "a")).toEqual({
|
|
80
|
+
targetDragId: "c",
|
|
81
|
+
targetContainer: "typeahead-pills",
|
|
82
|
+
});
|
|
83
|
+
});
|
|
38
84
|
});
|
|
@@ -5,12 +5,14 @@ const DRAG_THRESHOLD_PX = 5;
|
|
|
5
5
|
export const isTouchDragDevice = (): boolean => {
|
|
6
6
|
if (typeof window === 'undefined') return false;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Only treat true touch-primary devices as touch-drag targets.
|
|
9
|
+
// `ontouchstart` alone is true on many desktops (e.g. macOS trackpads) and
|
|
10
|
+
// must not disable HTML5 mouse dragging.
|
|
9
11
|
const hasCoarsePointer =
|
|
10
12
|
typeof window.matchMedia === 'function' &&
|
|
11
13
|
window.matchMedia('(hover: none) and (pointer: coarse)').matches;
|
|
12
14
|
|
|
13
|
-
return
|
|
15
|
+
return hasCoarsePointer;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export const getDragIdFromElement = (element: Element | null): string | null => {
|
|
@@ -27,6 +29,99 @@ export const getContainerFromElement = (element: Element | null): string | undef
|
|
|
27
29
|
return container.getAttribute('data-pb-drag-container') || undefined;
|
|
28
30
|
};
|
|
29
31
|
|
|
32
|
+
type SortedDragItem = {
|
|
33
|
+
id: string;
|
|
34
|
+
rect: DOMRect;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getSortedDragItems = (containerElement: Element): SortedDragItem[] => (
|
|
38
|
+
Array.from(containerElement.querySelectorAll('.pb_draggable_item[data-pb-drag-id]'))
|
|
39
|
+
.map((element) => ({
|
|
40
|
+
id: element.getAttribute('data-pb-drag-id') as string,
|
|
41
|
+
rect: element.getBoundingClientRect(),
|
|
42
|
+
}))
|
|
43
|
+
.sort((a, b) => a.rect.left - b.rect.left)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const getDragIdFromElementsAtPoint = (
|
|
47
|
+
clientX: number,
|
|
48
|
+
clientY: number,
|
|
49
|
+
dragId: string,
|
|
50
|
+
): string | null => {
|
|
51
|
+
if (typeof document.elementsFromPoint !== 'function') return null;
|
|
52
|
+
|
|
53
|
+
return document.elementsFromPoint(clientX, clientY)
|
|
54
|
+
.map((element) => getDragIdFromElement(element))
|
|
55
|
+
.find((id) => id && id !== dragId) ?? null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// When the pointer stays on the dragged item, use sibling midpoints (not the
|
|
59
|
+
// dragged pill's own rect) to decide which slot the cursor is in.
|
|
60
|
+
const getTargetWhenPointerOnDraggedItem = (
|
|
61
|
+
containerElement: Element,
|
|
62
|
+
clientX: number,
|
|
63
|
+
dragId: string,
|
|
64
|
+
): string | null => {
|
|
65
|
+
const items = getSortedDragItems(containerElement);
|
|
66
|
+
const currentIndex = items.findIndex((item) => item.id === dragId);
|
|
67
|
+
if (currentIndex === -1) return null;
|
|
68
|
+
|
|
69
|
+
const others = items.filter((item) => item.id !== dragId);
|
|
70
|
+
if (!others.length) return null;
|
|
71
|
+
|
|
72
|
+
let slot = 0;
|
|
73
|
+
others.forEach((item, index) => {
|
|
74
|
+
const midpoint = item.rect.left + item.rect.width / 2;
|
|
75
|
+
if (clientX > midpoint) {
|
|
76
|
+
slot = index + 1;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const desiredIndex = Math.min(slot, items.length - 1);
|
|
81
|
+
if (desiredIndex === currentIndex) return null;
|
|
82
|
+
|
|
83
|
+
const targetItem = items[desiredIndex];
|
|
84
|
+
if (!targetItem || targetItem.id === dragId) return null;
|
|
85
|
+
|
|
86
|
+
return targetItem.id;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const resolvePointerDragTarget = (
|
|
90
|
+
clientX: number,
|
|
91
|
+
clientY: number,
|
|
92
|
+
dragId: string,
|
|
93
|
+
): { targetDragId: string | null; targetContainer: string | undefined } => {
|
|
94
|
+
const elementBelow = typeof document.elementFromPoint === 'function'
|
|
95
|
+
? document.elementFromPoint(clientX, clientY)
|
|
96
|
+
: null;
|
|
97
|
+
const hitId = getDragIdFromElement(elementBelow);
|
|
98
|
+
|
|
99
|
+
if (hitId && hitId !== dragId) {
|
|
100
|
+
return {
|
|
101
|
+
targetDragId: hitId,
|
|
102
|
+
targetContainer: getContainerFromElement(elementBelow),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const stackId = getDragIdFromElementsAtPoint(clientX, clientY, dragId);
|
|
107
|
+
if (stackId) {
|
|
108
|
+
return {
|
|
109
|
+
targetDragId: stackId,
|
|
110
|
+
targetContainer: getContainerFromElement(elementBelow),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const containerElement = elementBelow?.closest('.pb_draggable_container');
|
|
115
|
+
if (!containerElement) {
|
|
116
|
+
return { targetDragId: null, targetContainer: undefined };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
targetDragId: getTargetWhenPointerOnDraggedItem(containerElement, clientX, dragId),
|
|
121
|
+
targetContainer: getContainerFromElement(containerElement),
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
30
125
|
type TouchDragHandlers = {
|
|
31
126
|
onDragStart: (dragId: string, container?: string) => void;
|
|
32
127
|
onDragEnter: (targetDragId: string, container?: string) => void;
|
|
@@ -42,7 +137,7 @@ type TouchDragOptions = {
|
|
|
42
137
|
handlers: TouchDragHandlers;
|
|
43
138
|
};
|
|
44
139
|
|
|
45
|
-
type
|
|
140
|
+
type DragPointerState = {
|
|
46
141
|
active: boolean;
|
|
47
142
|
dragging: boolean;
|
|
48
143
|
startX: number;
|
|
@@ -55,18 +150,85 @@ const createPreventableEvent = (): Event => ({
|
|
|
55
150
|
preventDefault: () => undefined,
|
|
56
151
|
} as Event);
|
|
57
152
|
|
|
153
|
+
const runDragPointerMove = (
|
|
154
|
+
state: DragPointerState,
|
|
155
|
+
clientX: number,
|
|
156
|
+
clientY: number,
|
|
157
|
+
dragId: string,
|
|
158
|
+
container: string | undefined,
|
|
159
|
+
handlers: TouchDragHandlers,
|
|
160
|
+
) => {
|
|
161
|
+
if (!state.dragging) {
|
|
162
|
+
const deltaX = clientX - state.startX;
|
|
163
|
+
const deltaY = clientY - state.startY;
|
|
164
|
+
|
|
165
|
+
if (Math.hypot(deltaX, deltaY) < DRAG_THRESHOLD_PX) return false;
|
|
166
|
+
|
|
167
|
+
state.dragging = true;
|
|
168
|
+
handlers.onDragStart(dragId, container);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { targetDragId, targetContainer } = resolvePointerDragTarget(clientX, clientY, dragId);
|
|
172
|
+
|
|
173
|
+
if (!targetDragId || targetDragId === dragId) {
|
|
174
|
+
state.lastTargetDragId = null;
|
|
175
|
+
} else if (targetDragId !== state.lastTargetDragId) {
|
|
176
|
+
state.lastTargetDragId = targetDragId;
|
|
177
|
+
handlers.onDragEnter(targetDragId, targetContainer ?? container);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (targetContainer && targetContainer !== state.lastContainer) {
|
|
181
|
+
state.lastContainer = targetContainer;
|
|
182
|
+
handlers.onDragOver(createPreventableEvent(), targetContainer);
|
|
183
|
+
} else if (targetContainer) {
|
|
184
|
+
handlers.onDragOver(createPreventableEvent(), targetContainer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return true;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const finishDragPointer = (
|
|
191
|
+
state: DragPointerState,
|
|
192
|
+
clientX: number | null,
|
|
193
|
+
clientY: number | null,
|
|
194
|
+
container: string | undefined,
|
|
195
|
+
handlers: TouchDragHandlers,
|
|
196
|
+
itemElement: HTMLElement,
|
|
197
|
+
) => {
|
|
198
|
+
if (!state.active) return;
|
|
199
|
+
|
|
200
|
+
if (state.dragging && clientX !== null && clientY !== null) {
|
|
201
|
+
const elementBelow = document.elementFromPoint(clientX, clientY);
|
|
202
|
+
const dropContainer = getContainerFromElement(elementBelow) ?? container;
|
|
203
|
+
|
|
204
|
+
if (dropContainer) {
|
|
205
|
+
handlers.onDrop(dropContainer);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
handlers.onDragEnd();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
state.active = false;
|
|
212
|
+
state.dragging = false;
|
|
213
|
+
state.lastTargetDragId = null;
|
|
214
|
+
state.lastContainer = undefined;
|
|
215
|
+
itemElement.classList.remove('is_touch_active');
|
|
216
|
+
};
|
|
217
|
+
|
|
58
218
|
export const bindTouchDrag = ({
|
|
59
219
|
dragId,
|
|
60
220
|
container,
|
|
61
221
|
itemElement,
|
|
62
222
|
handlers,
|
|
63
223
|
}: TouchDragOptions): (() => void) => {
|
|
64
|
-
if (!isTouchDragDevice())
|
|
224
|
+
if (!isTouchDragDevice()) {
|
|
225
|
+
return () => undefined;
|
|
226
|
+
}
|
|
65
227
|
|
|
66
228
|
const handle = itemElement.querySelector(DRAG_HANDLE_SELECTOR) as HTMLElement | null;
|
|
67
229
|
const dragTarget = handle || itemElement;
|
|
68
230
|
|
|
69
|
-
const state:
|
|
231
|
+
const state: DragPointerState = {
|
|
70
232
|
active: false,
|
|
71
233
|
dragging: false,
|
|
72
234
|
startX: 0,
|
|
@@ -75,16 +237,10 @@ export const bindTouchDrag = ({
|
|
|
75
237
|
lastContainer: undefined,
|
|
76
238
|
};
|
|
77
239
|
|
|
78
|
-
const resetState = () => {
|
|
79
|
-
state.active = false;
|
|
80
|
-
state.dragging = false;
|
|
81
|
-
state.lastTargetDragId = null;
|
|
82
|
-
state.lastContainer = undefined;
|
|
83
|
-
itemElement.classList.remove('is_touch_active');
|
|
84
|
-
};
|
|
85
|
-
|
|
86
240
|
const handleTouchStart = (event: TouchEvent) => {
|
|
87
|
-
if (handle && !handle.contains(event.target as Node))
|
|
241
|
+
if (handle && !handle.contains(event.target as Node)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
88
244
|
|
|
89
245
|
const touch = event.touches[0];
|
|
90
246
|
if (!touch) return;
|
|
@@ -101,50 +257,20 @@ export const bindTouchDrag = ({
|
|
|
101
257
|
const touch = event.touches[0];
|
|
102
258
|
if (!touch) return;
|
|
103
259
|
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
const deltaY = touch.clientY - state.startY;
|
|
107
|
-
|
|
108
|
-
if (Math.hypot(deltaX, deltaY) < DRAG_THRESHOLD_PX) return;
|
|
109
|
-
|
|
110
|
-
state.dragging = true;
|
|
111
|
-
handlers.onDragStart(dragId, container);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
event.preventDefault();
|
|
115
|
-
|
|
116
|
-
const elementBelow = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
117
|
-
const targetDragId = getDragIdFromElement(elementBelow);
|
|
118
|
-
const targetContainer = getContainerFromElement(elementBelow);
|
|
119
|
-
|
|
120
|
-
if (targetDragId && targetDragId !== dragId && targetDragId !== state.lastTargetDragId) {
|
|
121
|
-
state.lastTargetDragId = targetDragId;
|
|
122
|
-
handlers.onDragEnter(targetDragId, targetContainer ?? container);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (targetContainer && targetContainer !== state.lastContainer) {
|
|
126
|
-
state.lastContainer = targetContainer;
|
|
127
|
-
handlers.onDragOver(createPreventableEvent(), targetContainer);
|
|
128
|
-
} else if (targetContainer) {
|
|
129
|
-
handlers.onDragOver(createPreventableEvent(), targetContainer);
|
|
260
|
+
if (runDragPointerMove(state, touch.clientX, touch.clientY, dragId, container, handlers)) {
|
|
261
|
+
event.preventDefault();
|
|
130
262
|
}
|
|
131
263
|
};
|
|
132
264
|
|
|
133
265
|
const finishDrag = (touch: Touch | null) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
handlers.onDragEnd();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
resetState();
|
|
266
|
+
finishDragPointer(
|
|
267
|
+
state,
|
|
268
|
+
touch?.clientX ?? null,
|
|
269
|
+
touch?.clientY ?? null,
|
|
270
|
+
container,
|
|
271
|
+
handlers,
|
|
272
|
+
itemElement,
|
|
273
|
+
);
|
|
148
274
|
};
|
|
149
275
|
|
|
150
276
|
const handleTouchEnd = (event: TouchEvent) => {
|
|
@@ -171,3 +297,95 @@ export const bindTouchDrag = ({
|
|
|
171
297
|
dragTarget.removeEventListener('touchcancel', handleTouchCancel);
|
|
172
298
|
};
|
|
173
299
|
};
|
|
300
|
+
|
|
301
|
+
const DRAG_INTERACTION_EXCLUDE_SELECTOR = '.pb_form_pill_close';
|
|
302
|
+
|
|
303
|
+
const isHandleEventTarget = (target: EventTarget | null): boolean => {
|
|
304
|
+
return Boolean((target as Element | null)?.closest(DRAG_HANDLE_SELECTOR));
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const isExcludedDragTarget = (target: EventTarget | null): boolean => {
|
|
308
|
+
return Boolean((target as Element | null)?.closest(DRAG_INTERACTION_EXCLUDE_SELECTOR));
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
type MousePointerDragOptions = TouchDragOptions & {
|
|
312
|
+
allowWholeItem?: boolean;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export const bindMousePointerDrag = ({
|
|
316
|
+
dragId,
|
|
317
|
+
container,
|
|
318
|
+
itemElement,
|
|
319
|
+
handlers,
|
|
320
|
+
allowWholeItem = false,
|
|
321
|
+
}: MousePointerDragOptions): (() => void) => {
|
|
322
|
+
const hasHandle = Boolean(itemElement.querySelector(DRAG_HANDLE_SELECTOR));
|
|
323
|
+
|
|
324
|
+
if (!hasHandle && !allowWholeItem) {
|
|
325
|
+
return () => undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const state: DragPointerState = {
|
|
329
|
+
active: false,
|
|
330
|
+
dragging: false,
|
|
331
|
+
startX: 0,
|
|
332
|
+
startY: 0,
|
|
333
|
+
lastTargetDragId: null,
|
|
334
|
+
lastContainer: undefined,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const handleMouseMove = (event: MouseEvent) => {
|
|
338
|
+
if (!state.active) return;
|
|
339
|
+
|
|
340
|
+
if (runDragPointerMove(state, event.clientX, event.clientY, dragId, container, handlers)) {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const handleMouseUp = (event: MouseEvent) => {
|
|
346
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
347
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
348
|
+
|
|
349
|
+
finishDragPointer(
|
|
350
|
+
state,
|
|
351
|
+
event.clientX,
|
|
352
|
+
event.clientY,
|
|
353
|
+
container,
|
|
354
|
+
handlers,
|
|
355
|
+
itemElement,
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const handleMouseDown = (event: MouseEvent) => {
|
|
360
|
+
if (event.button !== 0) return;
|
|
361
|
+
if (isExcludedDragTarget(event.target)) return;
|
|
362
|
+
|
|
363
|
+
if (hasHandle) {
|
|
364
|
+
if (!isHandleEventTarget(event.target)) return;
|
|
365
|
+
} else if (!itemElement.contains(event.target as Node)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Pointer drag only — HTML5 drag is disabled on these items. preventDefault stops text selection.
|
|
370
|
+
event.preventDefault();
|
|
371
|
+
event.stopPropagation();
|
|
372
|
+
|
|
373
|
+
state.active = true;
|
|
374
|
+
state.startX = event.clientX;
|
|
375
|
+
state.startY = event.clientY;
|
|
376
|
+
itemElement.classList.add('is_touch_active');
|
|
377
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
378
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// Delegate on the stable draggable item element so re-rendered handles stay wired
|
|
382
|
+
itemElement.addEventListener('mousedown', handleMouseDown);
|
|
383
|
+
|
|
384
|
+
return () => {
|
|
385
|
+
itemElement.removeEventListener('mousedown', handleMouseDown);
|
|
386
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
387
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const bindMouseHandleDrag = bindMousePointerDrag;
|
|
@@ -7,7 +7,7 @@ import { globalProps, GlobalProps } from "../utilities/globalProps";
|
|
|
7
7
|
import Icon from "../pb_icon/_icon";
|
|
8
8
|
import Image from "../pb_image/_image";
|
|
9
9
|
import Collapsible from "../pb_collapsible/_collapsible";
|
|
10
|
-
import { NavChildProps } from "./navTypes";
|
|
10
|
+
import { NavChildProps, SpacingObject } from "./navTypes";
|
|
11
11
|
import { Spacing } from "../types";
|
|
12
12
|
|
|
13
13
|
type NavItemProps = {
|
|
@@ -35,7 +35,7 @@ type NavItemProps = {
|
|
|
35
35
|
collapsibleTrail?: boolean;
|
|
36
36
|
collapsed?: boolean;
|
|
37
37
|
orientation?: "vertical" | "horizontal";
|
|
38
|
-
variant?: "normal" | "subtle";
|
|
38
|
+
variant?: "normal" | "subtle" | "bold";
|
|
39
39
|
margin?: Spacing;
|
|
40
40
|
marginBottom?: Spacing;
|
|
41
41
|
marginTop?: Spacing;
|
|
@@ -44,6 +44,7 @@ type NavItemProps = {
|
|
|
44
44
|
marginX?: Spacing;
|
|
45
45
|
marginY?: Spacing;
|
|
46
46
|
disabled?: boolean;
|
|
47
|
+
itemSpacing?: SpacingObject;
|
|
47
48
|
} & GlobalProps;
|
|
48
49
|
|
|
49
50
|
const NavItem = (props: NavItemProps) => {
|