@k2b/ui 0.19.0 → 0.21.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/browser/actions/Button.js +20 -31
- package/dist/browser/actions/Button.js.map +1 -1
- package/dist/browser/actions/ContextMenu.js +7 -3
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/CopyButton.js +10 -8
- package/dist/browser/actions/CopyButton.js.map +1 -1
- package/dist/browser/actions/GestureMenu.js +471 -0
- package/dist/browser/actions/GestureMenu.js.map +1 -0
- package/dist/browser/actions/index.js +1 -0
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +3 -41
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/ChatPrimitives.js +9 -3
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/chat/ChatTimeline.js +19 -1
- package/dist/browser/chat/ChatTimeline.js.map +1 -1
- package/dist/browser/content/Calendar.js +1951 -565
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/MessageRow.js +2 -2
- package/dist/browser/content/MessageRow.js.map +1 -1
- package/dist/browser/content/calendar-messages.js +83 -0
- package/dist/browser/content/calendar-messages.js.map +1 -0
- package/dist/browser/content/calendar-month-layout.js +108 -0
- package/dist/browser/content/calendar-month-layout.js.map +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/inputs/DatePicker.js +46 -10
- package/dist/browser/inputs/DatePicker.js.map +1 -1
- package/dist/browser/inputs/TextInput.js +13 -1
- package/dist/browser/inputs/TextInput.js.map +1 -1
- package/dist/browser/inputs/date-picker.js +24 -0
- package/dist/browser/inputs/date-picker.js.map +1 -1
- package/dist/browser/inputs/markdown/actions.js +0 -42
- package/dist/browser/inputs/markdown/actions.js.map +1 -1
- package/dist/browser/intl/messages.js +6 -6
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/ssr/actions/Button.js +21 -32
- package/dist/ssr/actions/Button.js.map +1 -1
- package/dist/ssr/actions/ContextMenu.js +7 -3
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/CopyButton.js +9 -7
- package/dist/ssr/actions/CopyButton.js.map +1 -1
- package/dist/ssr/actions/GestureMenu.js +415 -0
- package/dist/ssr/actions/GestureMenu.js.map +1 -0
- package/dist/ssr/actions/index.js +1 -0
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +3 -41
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +9 -3
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/chat/ChatTimeline.js +19 -1
- package/dist/ssr/chat/ChatTimeline.js.map +1 -1
- package/dist/ssr/content/Calendar.js +1223 -105
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/MessageRow.js +2 -2
- package/dist/ssr/content/MessageRow.js.map +1 -1
- package/dist/ssr/content/calendar-messages.js +83 -0
- package/dist/ssr/content/calendar-messages.js.map +1 -0
- package/dist/ssr/content/calendar-month-layout.js +108 -0
- package/dist/ssr/content/calendar-month-layout.js.map +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/inputs/DatePicker.js +34 -4
- package/dist/ssr/inputs/DatePicker.js.map +1 -1
- package/dist/ssr/inputs/TextInput.js +13 -1
- package/dist/ssr/inputs/TextInput.js.map +1 -1
- package/dist/ssr/inputs/date-picker.js +24 -0
- package/dist/ssr/inputs/date-picker.js.map +1 -1
- package/dist/ssr/inputs/markdown/actions.js +0 -42
- package/dist/ssr/inputs/markdown/actions.js.map +1 -1
- package/dist/ssr/intl/messages.js +6 -6
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/GestureMenu.d.ts +37 -0
- package/dist/types/actions/index.d.ts +2 -0
- package/dist/types/chat/ChatComposer.d.ts +4 -1
- package/dist/types/content/Calendar.d.ts +32 -0
- package/dist/types/content/calendar-messages.d.ts +41 -0
- package/dist/types/content/calendar-month-layout.d.ts +52 -0
- package/dist/types/content/index.d.ts +1 -1
- package/dist/types/inputs/date-picker.d.ts +7 -0
- package/dist/types/inputs/markdown/actions.d.ts +0 -2
- package/dist/types/intl/messages.d.ts +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { template as _$template } from "solid-js/web";
|
|
2
|
+
import { delegateEvents as _$delegateEvents } from "solid-js/web";
|
|
3
|
+
import { className as _$className } from "solid-js/web";
|
|
4
|
+
import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
|
|
5
|
+
import { getNextMarker as _$getNextMarker } from "solid-js/web";
|
|
6
|
+
import { memo as _$memo } from "solid-js/web";
|
|
7
|
+
import { setAttribute as _$setAttribute } from "solid-js/web";
|
|
8
|
+
import { effect as _$effect } from "solid-js/web";
|
|
9
|
+
import { getNextElement as _$getNextElement } from "solid-js/web";
|
|
10
|
+
import { runHydrationEvents as _$runHydrationEvents } from "solid-js/web";
|
|
11
|
+
import { insert as _$insert } from "solid-js/web";
|
|
12
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
13
|
+
import { use as _$use } from "solid-js/web";
|
|
14
|
+
var _tmpl$ = /*#__PURE__*/_$template(`<div class=k2b-gesture-menu__sheet-menu role=menu>`),
|
|
15
|
+
_tmpl$2 = /*#__PURE__*/_$template(`<div class=k2b-gesture-menu><!$><!/><div class=k2b-gesture-menu__content>`),
|
|
16
|
+
_tmpl$3 = /*#__PURE__*/_$template(`<span class=k2b-gesture-menu__swipe aria-hidden=true><i>`);
|
|
17
|
+
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
|
18
|
+
import { dialogCore } from "../feedback/dialog-core";
|
|
19
|
+
import { LocaleProvider, useLocale } from "../intl/locale";
|
|
20
|
+
import BottomSheet, { bottomSheetOptions } from "../layout/BottomSheet";
|
|
21
|
+
import { ContextMenu } from "./ContextMenu";
|
|
22
|
+
import { DropdownItems, focusRowSegment } from "./Dropdown";
|
|
23
|
+
|
|
24
|
+
/** A finger held this long without moving opens the menu, as the platforms' own long press does. */
|
|
25
|
+
const LONG_PRESS_MS = 500;
|
|
26
|
+
/** A second tap that starts this soon after the first ended makes a double tap. */
|
|
27
|
+
const DOUBLE_TAP_MS = 300;
|
|
28
|
+
/** The second tap lands at most this far from the first. */
|
|
29
|
+
const DOUBLE_TAP_DISTANCE = 32;
|
|
30
|
+
/** Movement up to this is still a tap or a long press. */
|
|
31
|
+
const SLOP = 10;
|
|
32
|
+
/** A swipe must be this many times wider than tall; anything steeper belongs to the page's scrolling. */
|
|
33
|
+
const SWIPE_RATIO = 1.5;
|
|
34
|
+
/** Travel at which a released swipe runs its action. */
|
|
35
|
+
const SWIPE_THRESHOLD = 64;
|
|
36
|
+
/** The content follows the finger up to the threshold, then at a quarter of its travel, up to this. */
|
|
37
|
+
const SWIPE_MAX = 96;
|
|
38
|
+
const SWIPE_RESISTANCE = 0.25;
|
|
39
|
+
/** Swipes do not start this close to the screen's side edges, where the system's back and forward gestures are. */
|
|
40
|
+
const EDGE = 20;
|
|
41
|
+
/** Duration of the snap back in `.k2b-gesture-menu[data-settling]`. */
|
|
42
|
+
const SETTLE_MS = 160;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gestures never start on these elements: links, controls, fields, code blocks, and anything marked
|
|
46
|
+
* `data-gesture-ignore` keep their own touch and mouse behavior.
|
|
47
|
+
*/
|
|
48
|
+
const IGNORE = ["a[href]", "button", "input", "textarea", "select", "summary", "label", "audio", "video", "pre", "[contenteditable]:not([contenteditable='false'])", "[role='button']", "[role='link']", "[role='checkbox']", "[role='switch']", "[role='slider']", "[role='tab']", "[role^='menuitem']", "[data-gesture-ignore]"].join(", ");
|
|
49
|
+
|
|
50
|
+
/** A menu action that a gesture runs as well. */
|
|
51
|
+
|
|
52
|
+
const gestureAction = (items, kind) => {
|
|
53
|
+
for (const item of items) for (const entry of "items" in item ? item.items : [item]) if ("gesture" in entry && entry.gesture === kind) return entry.disabled ? undefined : entry;
|
|
54
|
+
return undefined;
|
|
55
|
+
};
|
|
56
|
+
const swipeKind = direction => direction === "right" ? "swipe-right" : "swipe-left";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The sheet's items hand their action to `run` instead of running it while the sheet closes: `dialogCore` settles the
|
|
60
|
+
* sheet after it left the history, and only then may an action navigate or open the next dialog. A choice that keeps
|
|
61
|
+
* the menu open runs at once.
|
|
62
|
+
*/
|
|
63
|
+
const afterClose = (items, run) => {
|
|
64
|
+
const entry = item => {
|
|
65
|
+
const action = item.action;
|
|
66
|
+
if (!action || "choice" in item && item.closeOnSelect === false) return item;
|
|
67
|
+
return {
|
|
68
|
+
...item,
|
|
69
|
+
action: () => run(action)
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
return items.map(item => "items" in item ? {
|
|
73
|
+
...item,
|
|
74
|
+
items: item.items.map(entry)
|
|
75
|
+
} : entry(item));
|
|
76
|
+
};
|
|
77
|
+
const reducedMotion = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
78
|
+
const menuItems = menu => Array.from(menu.querySelectorAll("[role^='menuitem']:not([aria-disabled='true'])"));
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The menu inside the long-press sheet: the same items, at touch size, with arrow keys for a connected keyboard. Focus
|
|
82
|
+
* starts on the sheet itself, which shows no ring on a phone; the first arrow key moves it into the menu.
|
|
83
|
+
*/
|
|
84
|
+
function SheetMenu(props) {
|
|
85
|
+
let menu;
|
|
86
|
+
const focus = index => {
|
|
87
|
+
const items = menuItems(menu);
|
|
88
|
+
items[(index + items.length) % items.length]?.focus();
|
|
89
|
+
};
|
|
90
|
+
const onKeyDown = event => {
|
|
91
|
+
const items = menuItems(menu);
|
|
92
|
+
const current = items.indexOf(document.activeElement);
|
|
93
|
+
if (event.key === "ArrowDown") focus(current + 1);else if (event.key === "ArrowUp") focus(current < 0 ? -1 : current - 1);else if (event.key === "Home" || event.key === "End") focus(event.key === "Home" ? 0 : -1);else if (!focusRowSegment(items, event.key)) return;
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
};
|
|
96
|
+
// Keys pressed on the sheet itself, while it is the top level of the shared dialog.
|
|
97
|
+
const onSheetKeyDown = event => {
|
|
98
|
+
if (event.target === props.dialog && props.dialog.lastElementChild?.contains(menu)) onKeyDown(event);
|
|
99
|
+
};
|
|
100
|
+
onMount(() => {
|
|
101
|
+
props.dialog.addEventListener("keydown", onSheetKeyDown);
|
|
102
|
+
onCleanup(() => props.dialog.removeEventListener("keydown", onSheetKeyDown));
|
|
103
|
+
});
|
|
104
|
+
return (() => {
|
|
105
|
+
var _el$ = _$getNextElement(_tmpl$);
|
|
106
|
+
_el$.$$keydown = onKeyDown;
|
|
107
|
+
var _ref$ = menu;
|
|
108
|
+
typeof _ref$ === "function" ? _$use(_ref$, _el$) : menu = _el$;
|
|
109
|
+
_$insert(_el$, _$createComponent(DropdownItems, {
|
|
110
|
+
get items() {
|
|
111
|
+
return props.items;
|
|
112
|
+
},
|
|
113
|
+
close: () => props.close()
|
|
114
|
+
}));
|
|
115
|
+
_$effect(() => _$setAttribute(_el$, "aria-label", props.label));
|
|
116
|
+
_$runHydrationEvents();
|
|
117
|
+
return _el$;
|
|
118
|
+
})();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Touch gestures for an element, each the shortcut of an entry in its menu: a horizontal swipe, a double tap, and a
|
|
123
|
+
* long press that opens the whole menu as a `BottomSheet`. A mouse double-click runs the double-tap action; a
|
|
124
|
+
* right-click and the keyboard open the menu beside the element. Vertical scrolling never turns into a swipe.
|
|
125
|
+
*/
|
|
126
|
+
export function GestureMenu(props) {
|
|
127
|
+
const locale = useLocale();
|
|
128
|
+
const [swipe, setSwipe] = createSignal();
|
|
129
|
+
let surface;
|
|
130
|
+
let press;
|
|
131
|
+
let lastTap;
|
|
132
|
+
let lastPointer = "mouse";
|
|
133
|
+
/**
|
|
134
|
+
* The touch or pen press that just ended did something: its `touchend` is cancelled, so no click or mouse events
|
|
135
|
+
* follow, and a pen's click, which comes without touch events, is cancelled itself.
|
|
136
|
+
*/
|
|
137
|
+
let consumed = false;
|
|
138
|
+
let sheetOpen = false;
|
|
139
|
+
/** Closes an open sheet when the element goes, for example when its message is deleted. */
|
|
140
|
+
const disposed = new AbortController();
|
|
141
|
+
let reduced = false;
|
|
142
|
+
let settleTimer;
|
|
143
|
+
const active = () => props.items.length > 0;
|
|
144
|
+
const action = kind => gestureAction(props.items, kind);
|
|
145
|
+
const touchAction = () => action("swipe-right") || action("swipe-left") ? "swipe" : action("double-tap") ? "tap" : undefined;
|
|
146
|
+
|
|
147
|
+
/** Whether the event started on something with a meaning of its own, inside this element. */
|
|
148
|
+
const ignored = target => {
|
|
149
|
+
const found = target instanceof Element ? target.closest(IGNORE) : null;
|
|
150
|
+
return found !== null && found !== surface && surface.contains(found);
|
|
151
|
+
};
|
|
152
|
+
const openSheet = () => {
|
|
153
|
+
if (sheetOpen || !active()) return;
|
|
154
|
+
sheetOpen = true;
|
|
155
|
+
props.onOpen?.();
|
|
156
|
+
const current = locale();
|
|
157
|
+
let chosen;
|
|
158
|
+
const choose = action => {
|
|
159
|
+
chosen = action;
|
|
160
|
+
};
|
|
161
|
+
void dialogCore.open((close, context) => _$createComponent(LocaleProvider, {
|
|
162
|
+
locale: current,
|
|
163
|
+
get children() {
|
|
164
|
+
return _$createComponent(BottomSheet, {
|
|
165
|
+
get onDismiss() {
|
|
166
|
+
return context.requestDismiss;
|
|
167
|
+
},
|
|
168
|
+
get children() {
|
|
169
|
+
return _$createComponent(BottomSheet.Body, {
|
|
170
|
+
get children() {
|
|
171
|
+
return [_$memo(() => props.sheetTop?.(() => close())), _$createComponent(SheetMenu, {
|
|
172
|
+
get items() {
|
|
173
|
+
return afterClose(props.items, choose);
|
|
174
|
+
},
|
|
175
|
+
get label() {
|
|
176
|
+
return props.label;
|
|
177
|
+
},
|
|
178
|
+
close: () => close(),
|
|
179
|
+
get dialog() {
|
|
180
|
+
return context.dialog;
|
|
181
|
+
}
|
|
182
|
+
})];
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}), {
|
|
189
|
+
...bottomSheetOptions,
|
|
190
|
+
ariaLabel: props.label,
|
|
191
|
+
history: true,
|
|
192
|
+
signal: disposed.signal
|
|
193
|
+
}).then(() => chosen?.()).finally(() => {
|
|
194
|
+
sheetOpen = false;
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** Another pointer that goes down anywhere while a finger presses, as in a pinch, ends the gesture. */
|
|
199
|
+
const onOtherPointer = event => {
|
|
200
|
+
if (!press || event.pointerId === press.id) return;
|
|
201
|
+
if (press.direction) settle();
|
|
202
|
+
endPress();
|
|
203
|
+
};
|
|
204
|
+
const endPress = () => {
|
|
205
|
+
clearTimeout(press?.timer);
|
|
206
|
+
press = undefined;
|
|
207
|
+
document.removeEventListener("pointerdown", onOtherPointer, true);
|
|
208
|
+
delete surface.dataset.pressing;
|
|
209
|
+
};
|
|
210
|
+
const settle = () => {
|
|
211
|
+
const current = swipe();
|
|
212
|
+
if (!current) return;
|
|
213
|
+
clearTimeout(settleTimer);
|
|
214
|
+
if (reduced || current.offset === 0) {
|
|
215
|
+
setSwipe(undefined);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
setSwipe({
|
|
219
|
+
...current,
|
|
220
|
+
offset: 0,
|
|
221
|
+
progress: 0,
|
|
222
|
+
armed: false,
|
|
223
|
+
settling: true
|
|
224
|
+
});
|
|
225
|
+
settleTimer = setTimeout(() => setSwipe(undefined), SETTLE_MS);
|
|
226
|
+
};
|
|
227
|
+
const moveSwipe = (current, direction, dx) => {
|
|
228
|
+
const travel = Math.max(0, direction === "right" ? dx : -dx);
|
|
229
|
+
const moved = travel <= SWIPE_THRESHOLD ? travel : SWIPE_THRESHOLD + (travel - SWIPE_THRESHOLD) * SWIPE_RESISTANCE;
|
|
230
|
+
const offset = reduced ? 0 : Math.min(SWIPE_MAX, moved) * (direction === "right" ? 1 : -1);
|
|
231
|
+
current.direction = direction;
|
|
232
|
+
setSwipe({
|
|
233
|
+
direction,
|
|
234
|
+
icon: action(swipeKind(direction))?.icon,
|
|
235
|
+
offset,
|
|
236
|
+
progress: Math.min(1, travel / SWIPE_THRESHOLD),
|
|
237
|
+
armed: travel >= SWIPE_THRESHOLD,
|
|
238
|
+
settling: false
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
const longPress = () => {
|
|
242
|
+
if (!press) return;
|
|
243
|
+
press.long = true;
|
|
244
|
+
lastTap = undefined;
|
|
245
|
+
openSheet();
|
|
246
|
+
};
|
|
247
|
+
const onPointerDown = event => {
|
|
248
|
+
lastPointer = event.pointerType;
|
|
249
|
+
consumed = false;
|
|
250
|
+
// Any press in between, on a link, with a mouse, or with another finger, ends a pending double tap.
|
|
251
|
+
const previous = lastTap;
|
|
252
|
+
lastTap = undefined;
|
|
253
|
+
// Another finger ends the gesture through `onOtherPointer` and starts none.
|
|
254
|
+
if (event.pointerType === "mouse" || !event.isPrimary) return;
|
|
255
|
+
endPress();
|
|
256
|
+
if (!active() || ignored(event.target)) return;
|
|
257
|
+
const second = previous !== undefined && performance.now() - previous.at <= DOUBLE_TAP_MS && Math.hypot(event.clientX - previous.x, event.clientY - previous.y) <= DOUBLE_TAP_DISTANCE;
|
|
258
|
+
press = {
|
|
259
|
+
id: event.pointerId,
|
|
260
|
+
x: event.clientX,
|
|
261
|
+
y: event.clientY,
|
|
262
|
+
moved: false,
|
|
263
|
+
long: false,
|
|
264
|
+
second,
|
|
265
|
+
edge: event.clientX < EDGE || event.clientX > window.innerWidth - EDGE,
|
|
266
|
+
timer: setTimeout(longPress, LONG_PRESS_MS)
|
|
267
|
+
};
|
|
268
|
+
document.addEventListener("pointerdown", onOtherPointer, true);
|
|
269
|
+
// Hybrid devices keep text selection for the mouse; a finger on the element selects nothing.
|
|
270
|
+
surface.dataset.pressing = "";
|
|
271
|
+
};
|
|
272
|
+
const onPointerMove = event => {
|
|
273
|
+
const current = press;
|
|
274
|
+
if (!current || event.pointerId !== current.id || current.long) return;
|
|
275
|
+
const dx = event.clientX - current.x;
|
|
276
|
+
const dy = event.clientY - current.y;
|
|
277
|
+
if (current.direction) {
|
|
278
|
+
moveSwipe(current, current.direction, dx);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (current.moved || Math.hypot(dx, dy) < SLOP) return;
|
|
282
|
+
current.moved = true;
|
|
283
|
+
clearTimeout(current.timer);
|
|
284
|
+
if (current.edge || Math.abs(dx) < Math.abs(dy) * SWIPE_RATIO) return;
|
|
285
|
+
const direction = dx > 0 ? "right" : "left";
|
|
286
|
+
if (!action(swipeKind(direction))) return;
|
|
287
|
+
clearTimeout(settleTimer);
|
|
288
|
+
reduced = reducedMotion();
|
|
289
|
+
moveSwipe(current, direction, dx);
|
|
290
|
+
};
|
|
291
|
+
const onPointerUp = event => {
|
|
292
|
+
const current = press;
|
|
293
|
+
if (!current || event.pointerId !== current.id) return;
|
|
294
|
+
endPress();
|
|
295
|
+
if (current.long) {
|
|
296
|
+
consumed = true;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (current.direction) {
|
|
300
|
+
// Where the finger lifts decides, and the item as it is now.
|
|
301
|
+
moveSwipe(current, current.direction, event.clientX - current.x);
|
|
302
|
+
const swipeAction = swipe()?.armed ? action(swipeKind(current.direction)) : undefined;
|
|
303
|
+
settle();
|
|
304
|
+
if (swipeAction) {
|
|
305
|
+
consumed = true;
|
|
306
|
+
swipeAction.action();
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (current.moved) return;
|
|
311
|
+
const doubleTap = action("double-tap");
|
|
312
|
+
if (!doubleTap) return;
|
|
313
|
+
if (current.second) {
|
|
314
|
+
consumed = true;
|
|
315
|
+
doubleTap.action();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
lastTap = {
|
|
319
|
+
x: event.clientX,
|
|
320
|
+
y: event.clientY,
|
|
321
|
+
at: performance.now()
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// The browser took the touch, for example to scroll the page.
|
|
326
|
+
const onPointerCancel = event => {
|
|
327
|
+
if (!press || event.pointerId !== press.id) return;
|
|
328
|
+
if (press.direction) settle();
|
|
329
|
+
endPress();
|
|
330
|
+
lastTap = undefined;
|
|
331
|
+
};
|
|
332
|
+
const onTouchEnd = event => {
|
|
333
|
+
if (!consumed) return;
|
|
334
|
+
consumed = false;
|
|
335
|
+
if (event.cancelable) event.preventDefault();
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// A pen sends no touch events; the click after its gesture is cancelled here. A keyboard's click has no detail.
|
|
339
|
+
const onClick = event => {
|
|
340
|
+
if (!consumed || event.detail === 0) return;
|
|
341
|
+
consumed = false;
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
event.stopPropagation();
|
|
344
|
+
};
|
|
345
|
+
const onContextMenu = event => {
|
|
346
|
+
// Without items the element has no menu, and the browser keeps its own.
|
|
347
|
+
if (!active()) return;
|
|
348
|
+
// Links, fields, and code keep the browser's own menu.
|
|
349
|
+
if (ignored(event.target)) {
|
|
350
|
+
event.stopPropagation();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
// A mouse or the keyboard opens the menu beside the element through ContextMenu.
|
|
354
|
+
if (!press && !("pointerType" in event && (event.pointerType === "touch" || event.pointerType === "pen"))) return;
|
|
355
|
+
// Android and touch screens report a long press as a context-menu event, sometimes before the timer.
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
event.stopPropagation();
|
|
358
|
+
if (press && !press.long && !press.moved) {
|
|
359
|
+
clearTimeout(press.timer);
|
|
360
|
+
longPress();
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// The second press of a mouse double-click would select a word; in this element it runs the double-tap action.
|
|
365
|
+
const onMouseDown = event => {
|
|
366
|
+
if (event.button === 0 && event.detail >= 2 && lastPointer === "mouse" && action("double-tap") && !ignored(event.target)) event.preventDefault();
|
|
367
|
+
};
|
|
368
|
+
const onDoubleClick = event => {
|
|
369
|
+
if (lastPointer !== "mouse" || ignored(event.target)) return;
|
|
370
|
+
const doubleTap = action("double-tap");
|
|
371
|
+
if (!doubleTap) return;
|
|
372
|
+
event.preventDefault();
|
|
373
|
+
doubleTap.action();
|
|
374
|
+
};
|
|
375
|
+
onMount(() => {
|
|
376
|
+
surface.addEventListener("pointerdown", onPointerDown);
|
|
377
|
+
surface.addEventListener("pointermove", onPointerMove);
|
|
378
|
+
surface.addEventListener("pointerup", onPointerUp);
|
|
379
|
+
surface.addEventListener("pointercancel", onPointerCancel);
|
|
380
|
+
surface.addEventListener("touchend", onTouchEnd, {
|
|
381
|
+
passive: false
|
|
382
|
+
});
|
|
383
|
+
surface.addEventListener("click", onClick, true);
|
|
384
|
+
surface.addEventListener("contextmenu", onContextMenu);
|
|
385
|
+
surface.addEventListener("mousedown", onMouseDown);
|
|
386
|
+
surface.addEventListener("dblclick", onDoubleClick);
|
|
387
|
+
onCleanup(() => {
|
|
388
|
+
disposed.abort();
|
|
389
|
+
endPress();
|
|
390
|
+
clearTimeout(settleTimer);
|
|
391
|
+
surface.removeEventListener("pointerdown", onPointerDown);
|
|
392
|
+
surface.removeEventListener("pointermove", onPointerMove);
|
|
393
|
+
surface.removeEventListener("pointerup", onPointerUp);
|
|
394
|
+
surface.removeEventListener("pointercancel", onPointerCancel);
|
|
395
|
+
surface.removeEventListener("touchend", onTouchEnd);
|
|
396
|
+
surface.removeEventListener("click", onClick, true);
|
|
397
|
+
surface.removeEventListener("contextmenu", onContextMenu);
|
|
398
|
+
surface.removeEventListener("mousedown", onMouseDown);
|
|
399
|
+
surface.removeEventListener("dblclick", onDoubleClick);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
return _$createComponent(ContextMenu, {
|
|
403
|
+
get items() {
|
|
404
|
+
return props.items;
|
|
405
|
+
},
|
|
406
|
+
get label() {
|
|
407
|
+
return props.label;
|
|
408
|
+
},
|
|
409
|
+
get tabIndex() {
|
|
410
|
+
return props.tabIndex;
|
|
411
|
+
},
|
|
412
|
+
get disabled() {
|
|
413
|
+
return !active();
|
|
414
|
+
},
|
|
415
|
+
get onOpen() {
|
|
416
|
+
return props.onOpen;
|
|
417
|
+
},
|
|
418
|
+
get ["class"]() {
|
|
419
|
+
return props.class;
|
|
420
|
+
},
|
|
421
|
+
get children() {
|
|
422
|
+
var _el$2 = _$getNextElement(_tmpl$2),
|
|
423
|
+
_el$4 = _el$2.firstChild,
|
|
424
|
+
[_el$5, _co$] = _$getNextMarker(_el$4.nextSibling),
|
|
425
|
+
_el$3 = _el$5.nextSibling;
|
|
426
|
+
var _ref$2 = surface;
|
|
427
|
+
typeof _ref$2 === "function" ? _$use(_ref$2, _el$2) : surface = _el$2;
|
|
428
|
+
_$insert(_el$2, _$createComponent(Show, {
|
|
429
|
+
get when() {
|
|
430
|
+
return swipe();
|
|
431
|
+
},
|
|
432
|
+
children: current => (() => {
|
|
433
|
+
var _el$6 = _$getNextElement(_tmpl$3),
|
|
434
|
+
_el$7 = _el$6.firstChild;
|
|
435
|
+
_$effect(() => _$className(_el$7, current().icon ?? (current().direction === "right" ? "ti ti-arrow-right" : "ti ti-arrow-left")));
|
|
436
|
+
return _el$6;
|
|
437
|
+
})()
|
|
438
|
+
}), _el$5, _co$);
|
|
439
|
+
_$insert(_el$3, () => props.children);
|
|
440
|
+
_$effect(_p$ => {
|
|
441
|
+
var _v$ = active() ? "" : undefined,
|
|
442
|
+
_v$2 = touchAction(),
|
|
443
|
+
_v$3 = swipe()?.direction,
|
|
444
|
+
_v$4 = swipe()?.armed ? "" : undefined,
|
|
445
|
+
_v$5 = swipe()?.settling ? "" : undefined,
|
|
446
|
+
_v$6 = `${swipe()?.offset ?? 0}px`,
|
|
447
|
+
_v$7 = String(swipe()?.progress ?? 0);
|
|
448
|
+
_v$ !== _p$.e && _$setAttribute(_el$2, "data-active", _p$.e = _v$);
|
|
449
|
+
_v$2 !== _p$.t && _$setAttribute(_el$2, "data-touch", _p$.t = _v$2);
|
|
450
|
+
_v$3 !== _p$.a && _$setAttribute(_el$2, "data-swipe", _p$.a = _v$3);
|
|
451
|
+
_v$4 !== _p$.o && _$setAttribute(_el$2, "data-armed", _p$.o = _v$4);
|
|
452
|
+
_v$5 !== _p$.i && _$setAttribute(_el$2, "data-settling", _p$.i = _v$5);
|
|
453
|
+
_v$6 !== _p$.n && _$setStyleProperty(_el$2, "--k2b-gesture-menu-offset", _p$.n = _v$6);
|
|
454
|
+
_v$7 !== _p$.s && _$setStyleProperty(_el$2, "--k2b-gesture-menu-progress", _p$.s = _v$7);
|
|
455
|
+
return _p$;
|
|
456
|
+
}, {
|
|
457
|
+
e: undefined,
|
|
458
|
+
t: undefined,
|
|
459
|
+
a: undefined,
|
|
460
|
+
o: undefined,
|
|
461
|
+
i: undefined,
|
|
462
|
+
n: undefined,
|
|
463
|
+
s: undefined
|
|
464
|
+
});
|
|
465
|
+
return _el$2;
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
export default GestureMenu;
|
|
470
|
+
_$delegateEvents(["keydown"]);
|
|
471
|
+
//# sourceMappingURL=GestureMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["actions/GestureMenu.tsx"],"sourcesContent":["import { createSignal, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { dialogCore } from \"../feedback/dialog-core\";\nimport { LocaleProvider, useLocale } from \"../intl/locale\";\nimport BottomSheet, { bottomSheetOptions } from \"../layout/BottomSheet\";\nimport { ContextMenu } from \"./ContextMenu\";\nimport {\n type DropdownAction,\n type DropdownActionBase,\n type DropdownChoice,\n DropdownItems,\n type DropdownSection,\n focusRowSegment,\n} from \"./Dropdown\";\n\n/** A finger held this long without moving opens the menu, as the platforms' own long press does. */\nconst LONG_PRESS_MS = 500;\n/** A second tap that starts this soon after the first ended makes a double tap. */\nconst DOUBLE_TAP_MS = 300;\n/** The second tap lands at most this far from the first. */\nconst DOUBLE_TAP_DISTANCE = 32;\n/** Movement up to this is still a tap or a long press. */\nconst SLOP = 10;\n/** A swipe must be this many times wider than tall; anything steeper belongs to the page's scrolling. */\nconst SWIPE_RATIO = 1.5;\n/** Travel at which a released swipe runs its action. */\nconst SWIPE_THRESHOLD = 64;\n/** The content follows the finger up to the threshold, then at a quarter of its travel, up to this. */\nconst SWIPE_MAX = 96;\nconst SWIPE_RESISTANCE = 0.25;\n/** Swipes do not start this close to the screen's side edges, where the system's back and forward gestures are. */\nconst EDGE = 20;\n/** Duration of the snap back in `.k2b-gesture-menu[data-settling]`. */\nconst SETTLE_MS = 160;\n\n/**\n * Gestures never start on these elements: links, controls, fields, code blocks, and anything marked\n * `data-gesture-ignore` keep their own touch and mouse behavior.\n */\nconst IGNORE = [\n \"a[href]\",\n \"button\",\n \"input\",\n \"textarea\",\n \"select\",\n \"summary\",\n \"label\",\n \"audio\",\n \"video\",\n \"pre\",\n \"[contenteditable]:not([contenteditable='false'])\",\n \"[role='button']\",\n \"[role='link']\",\n \"[role='checkbox']\",\n \"[role='switch']\",\n \"[role='slider']\",\n \"[role='tab']\",\n \"[role^='menuitem']\",\n \"[data-gesture-ignore]\",\n].join(\", \");\n\nexport type GestureKind = \"swipe-right\" | \"swipe-left\" | \"double-tap\";\n\n/** A menu action that a gesture runs as well. */\nexport type GestureMenuAction = DropdownActionBase & {\n action: () => void;\n /** The gesture that runs this action. Give each gesture to at most one item; a disabled item disables its gesture. */\n gesture?: GestureKind;\n};\n\nexport type GestureMenuSection = Omit<DropdownSection, \"items\"> & {\n items: readonly (GestureMenuAction | DropdownAction | DropdownChoice)[];\n};\n\nexport type GestureMenuItem = GestureMenuAction | DropdownAction | DropdownChoice | GestureMenuSection;\n\nexport type GestureMenuProps = {\n /**\n * The menu. A long press opens it as a bottom sheet, a right-click, the Context Menu key, or Shift+F10 as a menu.\n * An item with `gesture` is also run by that gesture, so every gesture has its entry here.\n */\n items: readonly GestureMenuItem[];\n /** Names the element and its menu, for example \"Message from Nora, 10:42\". */\n label: string;\n children: JSX.Element;\n /** Shown above the menu in the long-press sheet, for example a row of quick reactions. `close` closes the sheet. */\n sheetTop?: (close: () => void) => JSX.Element;\n /** Tab order of the element. A list that moves focus between its rows itself passes -1. */\n tabIndex?: number;\n /** Runs before the menu or the sheet shows its items, for example to select what was pressed. */\n onOpen?: () => void;\n class?: string;\n};\n\ntype SwipeDirection = \"right\" | \"left\";\n\ntype Swipe = {\n direction: SwipeDirection;\n icon?: string;\n /** How far the content is moved, in pixels; always 0 with reduced motion. */\n offset: number;\n /** Travel relative to the threshold, from 0 to 1. */\n progress: number;\n armed: boolean;\n settling: boolean;\n};\n\ntype Press = {\n id: number;\n x: number;\n y: number;\n timer?: ReturnType<typeof setTimeout>;\n /** The finger left the slop: no tap or long press any more. */\n moved: boolean;\n /** The long press opened the sheet. */\n long: boolean;\n /** The press started soon after a tap nearby. */\n second: boolean;\n /** The press started at a side edge of the screen. */\n edge: boolean;\n direction?: SwipeDirection;\n};\n\ntype Entry = GestureMenuAction | DropdownAction | DropdownChoice;\n\nconst gestureAction = (items: readonly GestureMenuItem[], kind: GestureKind): GestureMenuAction | undefined => {\n for (const item of items)\n for (const entry of \"items\" in item ? item.items : [item])\n if (\"gesture\" in entry && entry.gesture === kind) return entry.disabled ? undefined : entry;\n return undefined;\n};\n\nconst swipeKind = (direction: SwipeDirection): GestureKind => (direction === \"right\" ? \"swipe-right\" : \"swipe-left\");\n\n/**\n * The sheet's items hand their action to `run` instead of running it while the sheet closes: `dialogCore` settles the\n * sheet after it left the history, and only then may an action navigate or open the next dialog. A choice that keeps\n * the menu open runs at once.\n */\nconst afterClose = (items: readonly GestureMenuItem[], run: (action: () => void) => void): GestureMenuItem[] => {\n const entry = (item: Entry): Entry => {\n const action = item.action;\n if (!action || (\"choice\" in item && item.closeOnSelect === false)) return item;\n return { ...item, action: () => run(action) };\n };\n return items.map((item) => (\"items\" in item ? { ...item, items: item.items.map(entry) } : entry(item)));\n};\n\nconst reducedMotion = () => typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\nconst menuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(menu.querySelectorAll<HTMLElement>(\"[role^='menuitem']:not([aria-disabled='true'])\"));\n\n/**\n * The menu inside the long-press sheet: the same items, at touch size, with arrow keys for a connected keyboard. Focus\n * starts on the sheet itself, which shows no ring on a phone; the first arrow key moves it into the menu.\n */\nfunction SheetMenu(props: { items: readonly GestureMenuItem[]; label: string; close: () => void; dialog: HTMLDialogElement }): JSX.Element {\n let menu!: HTMLDivElement;\n const focus = (index: number) => {\n const items = menuItems(menu);\n items[(index + items.length) % items.length]?.focus();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n const items = menuItems(menu);\n const current = items.indexOf(document.activeElement as HTMLElement);\n if (event.key === \"ArrowDown\") focus(current + 1);\n else if (event.key === \"ArrowUp\") focus(current < 0 ? -1 : current - 1);\n else if (event.key === \"Home\" || event.key === \"End\") focus(event.key === \"Home\" ? 0 : -1);\n else if (!focusRowSegment(items, event.key)) return;\n event.preventDefault();\n };\n // Keys pressed on the sheet itself, while it is the top level of the shared dialog.\n const onSheetKeyDown = (event: KeyboardEvent) => {\n if (event.target === props.dialog && props.dialog.lastElementChild?.contains(menu)) onKeyDown(event);\n };\n onMount(() => {\n props.dialog.addEventListener(\"keydown\", onSheetKeyDown);\n onCleanup(() => props.dialog.removeEventListener(\"keydown\", onSheetKeyDown));\n });\n return (\n <div ref={menu} class=\"k2b-gesture-menu__sheet-menu\" role=\"menu\" aria-label={props.label} onKeyDown={onKeyDown}>\n <DropdownItems items={props.items} close={() => props.close()} />\n </div>\n );\n}\n\n/**\n * Touch gestures for an element, each the shortcut of an entry in its menu: a horizontal swipe, a double tap, and a\n * long press that opens the whole menu as a `BottomSheet`. A mouse double-click runs the double-tap action; a\n * right-click and the keyboard open the menu beside the element. Vertical scrolling never turns into a swipe.\n */\nexport function GestureMenu(props: GestureMenuProps): JSX.Element {\n const locale = useLocale();\n const [swipe, setSwipe] = createSignal<Swipe>();\n let surface!: HTMLDivElement;\n let press: Press | undefined;\n let lastTap: { x: number; y: number; at: number } | undefined;\n let lastPointer = \"mouse\";\n /**\n * The touch or pen press that just ended did something: its `touchend` is cancelled, so no click or mouse events\n * follow, and a pen's click, which comes without touch events, is cancelled itself.\n */\n let consumed = false;\n let sheetOpen = false;\n /** Closes an open sheet when the element goes, for example when its message is deleted. */\n const disposed = new AbortController();\n let reduced = false;\n let settleTimer: ReturnType<typeof setTimeout> | undefined;\n\n const active = () => props.items.length > 0;\n const action = (kind: GestureKind) => gestureAction(props.items, kind);\n const touchAction = () => (action(\"swipe-right\") || action(\"swipe-left\") ? \"swipe\" : action(\"double-tap\") ? \"tap\" : undefined);\n\n /** Whether the event started on something with a meaning of its own, inside this element. */\n const ignored = (target: EventTarget | null) => {\n const found = target instanceof Element ? target.closest(IGNORE) : null;\n return found !== null && found !== surface && surface.contains(found);\n };\n\n const openSheet = () => {\n if (sheetOpen || !active()) return;\n sheetOpen = true;\n props.onOpen?.();\n const current = locale();\n let chosen: (() => void) | undefined;\n const choose = (action: () => void) => {\n chosen = action;\n };\n void dialogCore\n .open<void>(\n (close, context) => (\n <LocaleProvider locale={current}>\n <BottomSheet onDismiss={context.requestDismiss}>\n <BottomSheet.Body>\n {props.sheetTop?.(() => close())}\n <SheetMenu items={afterClose(props.items, choose)} label={props.label} close={() => close()} dialog={context.dialog} />\n </BottomSheet.Body>\n </BottomSheet>\n </LocaleProvider>\n ),\n { ...bottomSheetOptions, ariaLabel: props.label, history: true, signal: disposed.signal },\n )\n .then(() => chosen?.())\n .finally(() => {\n sheetOpen = false;\n });\n };\n\n /** Another pointer that goes down anywhere while a finger presses, as in a pinch, ends the gesture. */\n const onOtherPointer = (event: PointerEvent) => {\n if (!press || event.pointerId === press.id) return;\n if (press.direction) settle();\n endPress();\n };\n\n const endPress = () => {\n clearTimeout(press?.timer);\n press = undefined;\n document.removeEventListener(\"pointerdown\", onOtherPointer, true);\n delete surface.dataset.pressing;\n };\n\n const settle = () => {\n const current = swipe();\n if (!current) return;\n clearTimeout(settleTimer);\n if (reduced || current.offset === 0) {\n setSwipe(undefined);\n return;\n }\n setSwipe({ ...current, offset: 0, progress: 0, armed: false, settling: true });\n settleTimer = setTimeout(() => setSwipe(undefined), SETTLE_MS);\n };\n\n const moveSwipe = (current: Press, direction: SwipeDirection, dx: number) => {\n const travel = Math.max(0, direction === \"right\" ? dx : -dx);\n const moved = travel <= SWIPE_THRESHOLD ? travel : SWIPE_THRESHOLD + (travel - SWIPE_THRESHOLD) * SWIPE_RESISTANCE;\n const offset = reduced ? 0 : Math.min(SWIPE_MAX, moved) * (direction === \"right\" ? 1 : -1);\n current.direction = direction;\n setSwipe({\n direction,\n icon: action(swipeKind(direction))?.icon,\n offset,\n progress: Math.min(1, travel / SWIPE_THRESHOLD),\n armed: travel >= SWIPE_THRESHOLD,\n settling: false,\n });\n };\n\n const longPress = () => {\n if (!press) return;\n press.long = true;\n lastTap = undefined;\n openSheet();\n };\n\n const onPointerDown = (event: PointerEvent) => {\n lastPointer = event.pointerType;\n consumed = false;\n // Any press in between, on a link, with a mouse, or with another finger, ends a pending double tap.\n const previous = lastTap;\n lastTap = undefined;\n // Another finger ends the gesture through `onOtherPointer` and starts none.\n if (event.pointerType === \"mouse\" || !event.isPrimary) return;\n endPress();\n if (!active() || ignored(event.target)) return;\n const second =\n previous !== undefined &&\n performance.now() - previous.at <= DOUBLE_TAP_MS &&\n Math.hypot(event.clientX - previous.x, event.clientY - previous.y) <= DOUBLE_TAP_DISTANCE;\n press = {\n id: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n moved: false,\n long: false,\n second,\n edge: event.clientX < EDGE || event.clientX > window.innerWidth - EDGE,\n timer: setTimeout(longPress, LONG_PRESS_MS),\n };\n document.addEventListener(\"pointerdown\", onOtherPointer, true);\n // Hybrid devices keep text selection for the mouse; a finger on the element selects nothing.\n surface.dataset.pressing = \"\";\n };\n\n const onPointerMove = (event: PointerEvent) => {\n const current = press;\n if (!current || event.pointerId !== current.id || current.long) return;\n const dx = event.clientX - current.x;\n const dy = event.clientY - current.y;\n if (current.direction) {\n moveSwipe(current, current.direction, dx);\n return;\n }\n if (current.moved || Math.hypot(dx, dy) < SLOP) return;\n current.moved = true;\n clearTimeout(current.timer);\n if (current.edge || Math.abs(dx) < Math.abs(dy) * SWIPE_RATIO) return;\n const direction: SwipeDirection = dx > 0 ? \"right\" : \"left\";\n if (!action(swipeKind(direction))) return;\n clearTimeout(settleTimer);\n reduced = reducedMotion();\n moveSwipe(current, direction, dx);\n };\n\n const onPointerUp = (event: PointerEvent) => {\n const current = press;\n if (!current || event.pointerId !== current.id) return;\n endPress();\n if (current.long) {\n consumed = true;\n return;\n }\n if (current.direction) {\n // Where the finger lifts decides, and the item as it is now.\n moveSwipe(current, current.direction, event.clientX - current.x);\n const swipeAction = swipe()?.armed ? action(swipeKind(current.direction)) : undefined;\n settle();\n if (swipeAction) {\n consumed = true;\n swipeAction.action();\n }\n return;\n }\n if (current.moved) return;\n const doubleTap = action(\"double-tap\");\n if (!doubleTap) return;\n if (current.second) {\n consumed = true;\n doubleTap.action();\n return;\n }\n lastTap = { x: event.clientX, y: event.clientY, at: performance.now() };\n };\n\n // The browser took the touch, for example to scroll the page.\n const onPointerCancel = (event: PointerEvent) => {\n if (!press || event.pointerId !== press.id) return;\n if (press.direction) settle();\n endPress();\n lastTap = undefined;\n };\n\n const onTouchEnd = (event: TouchEvent) => {\n if (!consumed) return;\n consumed = false;\n if (event.cancelable) event.preventDefault();\n };\n\n // A pen sends no touch events; the click after its gesture is cancelled here. A keyboard's click has no detail.\n const onClick = (event: MouseEvent) => {\n if (!consumed || event.detail === 0) return;\n consumed = false;\n event.preventDefault();\n event.stopPropagation();\n };\n\n const onContextMenu = (event: MouseEvent) => {\n // Without items the element has no menu, and the browser keeps its own.\n if (!active()) return;\n // Links, fields, and code keep the browser's own menu.\n if (ignored(event.target)) {\n event.stopPropagation();\n return;\n }\n // A mouse or the keyboard opens the menu beside the element through ContextMenu.\n if (!press && !(\"pointerType\" in event && (event.pointerType === \"touch\" || event.pointerType === \"pen\"))) return;\n // Android and touch screens report a long press as a context-menu event, sometimes before the timer.\n event.preventDefault();\n event.stopPropagation();\n if (press && !press.long && !press.moved) {\n clearTimeout(press.timer);\n longPress();\n }\n };\n\n // The second press of a mouse double-click would select a word; in this element it runs the double-tap action.\n const onMouseDown = (event: MouseEvent) => {\n if (event.button === 0 && event.detail >= 2 && lastPointer === \"mouse\" && action(\"double-tap\") && !ignored(event.target))\n event.preventDefault();\n };\n\n const onDoubleClick = (event: MouseEvent) => {\n if (lastPointer !== \"mouse\" || ignored(event.target)) return;\n const doubleTap = action(\"double-tap\");\n if (!doubleTap) return;\n event.preventDefault();\n doubleTap.action();\n };\n\n onMount(() => {\n surface.addEventListener(\"pointerdown\", onPointerDown);\n surface.addEventListener(\"pointermove\", onPointerMove);\n surface.addEventListener(\"pointerup\", onPointerUp);\n surface.addEventListener(\"pointercancel\", onPointerCancel);\n surface.addEventListener(\"touchend\", onTouchEnd, { passive: false });\n surface.addEventListener(\"click\", onClick, true);\n surface.addEventListener(\"contextmenu\", onContextMenu);\n surface.addEventListener(\"mousedown\", onMouseDown);\n surface.addEventListener(\"dblclick\", onDoubleClick);\n onCleanup(() => {\n disposed.abort();\n endPress();\n clearTimeout(settleTimer);\n surface.removeEventListener(\"pointerdown\", onPointerDown);\n surface.removeEventListener(\"pointermove\", onPointerMove);\n surface.removeEventListener(\"pointerup\", onPointerUp);\n surface.removeEventListener(\"pointercancel\", onPointerCancel);\n surface.removeEventListener(\"touchend\", onTouchEnd);\n surface.removeEventListener(\"click\", onClick, true);\n surface.removeEventListener(\"contextmenu\", onContextMenu);\n surface.removeEventListener(\"mousedown\", onMouseDown);\n surface.removeEventListener(\"dblclick\", onDoubleClick);\n });\n });\n\n return (\n <ContextMenu\n items={props.items}\n label={props.label}\n tabIndex={props.tabIndex}\n disabled={!active()}\n onOpen={props.onOpen}\n class={props.class}\n >\n <div\n ref={surface}\n class=\"k2b-gesture-menu\"\n data-active={active() ? \"\" : undefined}\n data-touch={touchAction()}\n data-swipe={swipe()?.direction}\n data-armed={swipe()?.armed ? \"\" : undefined}\n data-settling={swipe()?.settling ? \"\" : undefined}\n style={{\n \"--k2b-gesture-menu-offset\": `${swipe()?.offset ?? 0}px`,\n \"--k2b-gesture-menu-progress\": String(swipe()?.progress ?? 0),\n }}\n >\n <Show when={swipe()}>\n {(current) => (\n <span class=\"k2b-gesture-menu__swipe\" aria-hidden=\"true\">\n <i class={current().icon ?? (current().direction === \"right\" ? \"ti ti-arrow-right\" : \"ti ti-arrow-left\")} />\n </span>\n )}\n </Show>\n <div class=\"k2b-gesture-menu__content\">{props.children}</div>\n </div>\n </ContextMenu>\n );\n}\n\nexport default GestureMenu;\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,EAAY,SAAS,EAAE,OAAO,EAAE,IAAI,QAAQ,UAAU;AAC3E,SAAS,UAAU,QAAQ,yBAAyB;AACpD,SAAS,cAAc,EAAE,SAAS,QAAQ,gBAAgB;AAC1D,OAAO,WAAW,IAAI,kBAAkB,QAAQ,uBAAuB;AACvE,SAAS,WAAW,QAAQ,eAAe;AAC3C,SAIE,aAAa,EAEb,eAAe,QACV,YAAY;;AAEnB;AACA,MAAM,aAAa,GAAG,GAAG;AACzB;AACA,MAAM,aAAa,GAAG,GAAG;AACzB;AACA,MAAM,mBAAmB,GAAG,EAAE;AAC9B;AACA,MAAM,IAAI,GAAG,EAAE;AACf;AACA,MAAM,WAAW,GAAG,GAAG;AACvB;AACA,MAAM,eAAe,GAAG,EAAE;AAC1B;AACA,MAAM,SAAS,GAAG,EAAE;AACpB,MAAM,gBAAgB,GAAG,IAAI;AAC7B;AACA,MAAM,IAAI,GAAG,EAAE;AACf;AACA,MAAM,SAAS,GAAG,GAAG;;AAErB;AACA;AACA;AACA;AACA,MAAM,MAAM,GAAG,CACb,SAAS,EACT,QAAQ,EACR,OAAO,EACP,UAAU,EACV,QAAQ,EACR,SAAS,EACT,OAAO,EACP,OAAO,EACP,OAAO,EACP,KAAK,EACL,kDAAkD,EAClD,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,uBAAuB,CACxB,CAAC,IAAI,CAAC,IAAI,CAAC;;AAIZ;;AA8DA,MAAM,aAAa,GAAG,CAAC,KAAiC,EAAE,IAAiB,KAAoC;EAC7G,KAAK,MAAM,IAAI,IAAI,KAAK,EACtB,KAAK,MAAM,KAAK,IAAI,OAAO,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,EACvD,IAAI,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,OAAO,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,KAAK;EAC/F,OAAO,SAAS;AAClB,CAAC;AAED,MAAM,SAAS,GAAI,SAAyB,IAAmB,SAAS,KAAK,OAAO,GAAG,aAAa,GAAG,YAAa;;AAEpH;AACA;AACA;AACA;AACA;AACA,MAAM,UAAU,GAAG,CAAC,KAAiC,EAAE,GAAiC,KAAwB;EAC9G,MAAM,KAAK,GAAI,IAAW,IAAY;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM;IAC1B,IAAI,CAAC,MAAM,IAAK,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,aAAa,KAAK,KAAM,EAAE,OAAO,IAAI;IAC9E,OAAO;MAAE,GAAG,IAAI;MAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM;IAAE,CAAC;EAC/C,CAAC;EACD,OAAO,KAAK,CAAC,GAAG,CAAE,IAAI,IAAM,OAAO,IAAI,IAAI,GAAG;IAAE,GAAG,IAAI;IAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK;EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAE,CAAC;AACzG,CAAC;AAED,MAAM,aAAa,GAAG,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO;AAEtH,MAAM,SAAS,GAAI,IAAiB,IAClC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAc,gDAAgD,CAAC,CAAC;;AAElG;AACA;AACA;AACA;AACA,SAAS,SAAS,CAAC,KAAyG,EAAe;EACzI,IAAI,IAAqB;EACzB,MAAM,KAAK,GAAI,KAAa,IAAK;IAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC;IAC7B,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;EACvD,CAAC;EACD,MAAM,SAAS,GAAI,KAAoB,IAAK;IAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,aAA4B,CAAC;IACpE,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,KAC7C,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,KACnE,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KACtF,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE;IAC7C,KAAK,CAAC,cAAc,CAAC,CAAC;EACxB,CAAC;EACD;EACA,MAAM,cAAc,GAAI,KAAoB,IAAK;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,gBAAgB,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC;EACtG,CAAC;EACD,OAAO,CAAC,MAAM;IACZ,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,cAAc,CAAC;IACxD,SAAS,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;EAC9E,CAAC,CAAC;EACF;IAAA;IAAA,iBACuG,SAAS;IAAA,YAApG,IAAI;IAAA,mDAAJ,IAAI;IAAA,iCACX,aAAa;MAAA,IAAC,KAAK;QAAA,OAAE,KAAK,CAAC,KAAK;MAAA;MAAE,KAAK,EAAE,MAAM,KAAK,CAAC,KAAK,CAAC;IAAC;IAAA,kDADc,KAAK,CAAC,KAAK;IAAA;IAAA;EAAA;AAI5F;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS,WAAW,CAAC,KAAuB,EAAe;EAChE,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;EAC1B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,YAAY,CAAQ,CAAC;EAC/C,IAAI,OAAwB;EAC5B,IAAI,KAAwB;EAC5B,IAAI,OAAyD;EAC7D,IAAI,WAAW,GAAG,OAAO;EACzB;AACF;AACA;AACA;EACE,IAAI,QAAQ,GAAG,KAAK;EACpB,IAAI,SAAS,GAAG,KAAK;EACrB;EACA,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,CAAC;EACtC,IAAI,OAAO,GAAG,KAAK;EACnB,IAAI,WAAsD;EAE1D,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;EAC3C,MAAM,MAAM,GAAI,IAAiB,IAAK,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;EACtE,MAAM,WAAW,GAAG,MAAO,MAAM,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,GAAG,SAAU;;EAE9H;EACA,MAAM,OAAO,GAAI,MAA0B,IAAK;IAC9C,MAAM,KAAK,GAAG,MAAM,YAAY,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;IACvE,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;EACvE,CAAC;EAED,MAAM,SAAS,GAAG,MAAM;IACtB,IAAI,SAAS,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE;IAC5B,SAAS,GAAG,IAAI;IAChB,KAAK,CAAC,MAAM,GAAG,CAAC;IAChB,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC;IACxB,IAAI,MAAgC;IACpC,MAAM,MAAM,GAAI,MAAkB,IAAK;MACrC,MAAM,GAAG,MAAM;IACjB,CAAC;IACD,KAAK,UAAU,CACZ,IAAI,CACH,CAAC,KAAK,EAAE,OAAO,uBACZ,cAAc;MAAC,MAAM,EAAE,OAAO;MAAA;QAAA,yBAC5B,WAAW;UAAA,IAAC,SAAS;YAAA,OAAE,OAAO,CAAC,cAAc;UAAA;UAAA;YAAA,yBAC3C,WAAW,CAAC,IAAI;cAAA;gBAAA,qBACd,KAAK,CAAC,QAAQ,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,qBAC/B,SAAS;kBAAA,IAAC,KAAK;oBAAA,OAAE,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC;kBAAA;kBAAA,IAAE,KAAK;oBAAA,OAAE,KAAK,CAAC,KAAK;kBAAA;kBAAE,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;kBAAA,IAAE,MAAM;oBAAA,OAAE,OAAO,CAAC,MAAM;kBAAA;gBAAA;cAAA;YAAA;UAAA;QAAA;MAAA;IAAA,EAI1H,EACD;MAAE,GAAG,kBAAkB;MAAE,SAAS,EAAE,KAAK,CAAC,KAAK;MAAE,OAAO,EAAE,IAAI;MAAE,MAAM,EAAE,QAAQ,CAAC;IAAO,CAC1F,CAAC,CACA,IAAI,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,CACtB,OAAO,CAAC,MAAM;MACb,SAAS,GAAG,KAAK;IACnB,CAAC,CAAC;EACN,CAAC;;EAED;EACA,MAAM,cAAc,GAAI,KAAmB,IAAK;IAC9C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,EAAE;IAC5C,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,CAAC;EACZ,CAAC;EAED,MAAM,QAAQ,GAAG,MAAM;IACrB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;IAC1B,KAAK,GAAG,SAAS;IACjB,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,cAAc,EAAE,IAAI,CAAC;IACjE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ;EACjC,CAAC;EAED,MAAM,MAAM,GAAG,MAAM;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC;IACvB,IAAI,CAAC,OAAO,EAAE;IACd,YAAY,CAAC,WAAW,CAAC;IACzB,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;MACnC,QAAQ,CAAC,SAAS,CAAC;MACnB;IACF;IACA,QAAQ,CAAC;MAAE,GAAG,OAAO;MAAE,MAAM,EAAE,CAAC;MAAE,QAAQ,EAAE,CAAC;MAAE,KAAK,EAAE,KAAK;MAAE,QAAQ,EAAE;IAAK,CAAC,CAAC;IAC9E,WAAW,GAAG,UAAU,CAAC,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;EAChE,CAAC;EAED,MAAM,SAAS,GAAG,CAAC,OAAc,EAAE,SAAyB,EAAE,EAAU,KAAK;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;IAC5D,MAAM,KAAK,GAAG,MAAM,IAAI,eAAe,GAAG,MAAM,GAAG,eAAe,GAAG,CAAC,MAAM,GAAG,eAAe,IAAI,gBAAgB;IAClH,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,SAAS,KAAK,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1F,OAAO,CAAC,SAAS,GAAG,SAAS;IAC7B,QAAQ,CAAC;MACP,SAAS;MACT,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI;MACxC,MAAM;MACN,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;MAC/C,KAAK,EAAE,MAAM,IAAI,eAAe;MAChC,QAAQ,EAAE;IACZ,CAAC,CAAC;EACJ,CAAC;EAED,MAAM,SAAS,GAAG,MAAM;IACtB,IAAI,CAAC,KAAK,EAAE;IACZ,KAAK,CAAC,IAAI,GAAG,IAAI;IACjB,OAAO,GAAG,SAAS;IACnB,SAAS,CAAC,CAAC;EACb,CAAC;EAED,MAAM,aAAa,GAAI,KAAmB,IAAK;IAC7C,WAAW,GAAG,KAAK,CAAC,WAAW;IAC/B,QAAQ,GAAG,KAAK;IAChB;IACA,MAAM,QAAQ,GAAG,OAAO;IACxB,OAAO,GAAG,SAAS;IACnB;IACA,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;IACvD,QAAQ,CAAC,CAAC;IACV,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;IACxC,MAAM,MAAM,GACV,QAAQ,KAAK,SAAS,IACtB,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,IAAI,aAAa,IAChD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,mBAAmB;IAC3F,KAAK,GAAG;MACN,EAAE,EAAE,KAAK,CAAC,SAAS;MACnB,CAAC,EAAE,KAAK,CAAC,OAAO;MAChB,CAAC,EAAE,KAAK,CAAC,OAAO;MAChB,KAAK,EAAE,KAAK;MACZ,IAAI,EAAE,KAAK;MACX,MAAM;MACN,IAAI,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,UAAU,GAAG,IAAI;MACtE,KAAK,EAAE,UAAU,CAAC,SAAS,EAAE,aAAa;IAC5C,CAAC;IACD,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,cAAc,EAAE,IAAI,CAAC;IAC9D;IACA,OAAO,CAAC,OAAO,CAAC,QAAQ,GAAG,EAAE;EAC/B,CAAC;EAED,MAAM,aAAa,GAAI,KAAmB,IAAK;IAC7C,MAAM,OAAO,GAAG,KAAK;IACrB,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE;IAChE,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,SAAS,EAAE;MACrB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;MACzC;IACF;IACA,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE;IAChD,OAAO,CAAC,KAAK,GAAG,IAAI;IACpB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3B,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE;IAC/D,MAAM,SAAyB,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,GAAG,MAAM;IAC3D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE;IACnC,YAAY,CAAC,WAAW,CAAC;IACzB,OAAO,GAAG,aAAa,CAAC,CAAC;IACzB,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC;EACnC,CAAC;EAED,MAAM,WAAW,GAAI,KAAmB,IAAK;IAC3C,MAAM,OAAO,GAAG,KAAK;IACrB,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,EAAE;IAChD,QAAQ,CAAC,CAAC;IACV,IAAI,OAAO,CAAC,IAAI,EAAE;MAChB,QAAQ,GAAG,IAAI;MACf;IACF;IACA,IAAI,OAAO,CAAC,SAAS,EAAE;MACrB;MACA,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;MAChE,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,SAAS;MACrF,MAAM,CAAC,CAAC;MACR,IAAI,WAAW,EAAE;QACf,QAAQ,GAAG,IAAI;QACf,WAAW,CAAC,MAAM,CAAC,CAAC;MACtB;MACA;IACF;IACA,IAAI,OAAO,CAAC,KAAK,EAAE;IACnB,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC;IACtC,IAAI,CAAC,SAAS,EAAE;IAChB,IAAI,OAAO,CAAC,MAAM,EAAE;MAClB,QAAQ,GAAG,IAAI;MACf,SAAS,CAAC,MAAM,CAAC,CAAC;MAClB;IACF;IACA,OAAO,GAAG;MAAE,CAAC,EAAE,KAAK,CAAC,OAAO;MAAE,CAAC,EAAE,KAAK,CAAC,OAAO;MAAE,EAAE,EAAE,WAAW,CAAC,GAAG,CAAC;IAAE,CAAC;EACzE,CAAC;;EAED;EACA,MAAM,eAAe,GAAI,KAAmB,IAAK;IAC/C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,EAAE;IAC5C,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,CAAC;IACV,OAAO,GAAG,SAAS;EACrB,CAAC;EAED,MAAM,UAAU,GAAI,KAAiB,IAAK;IACxC,IAAI,CAAC,QAAQ,EAAE;IACf,QAAQ,GAAG,KAAK;IAChB,IAAI,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;EAC9C,CAAC;;EAED;EACA,MAAM,OAAO,GAAI,KAAiB,IAAK;IACrC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;IACrC,QAAQ,GAAG,KAAK;IAChB,KAAK,CAAC,cAAc,CAAC,CAAC;IACtB,KAAK,CAAC,eAAe,CAAC,CAAC;EACzB,CAAC;EAED,MAAM,aAAa,GAAI,KAAiB,IAAK;IAC3C;IACA,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE;IACf;IACA,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;MACzB,KAAK,CAAC,eAAe,CAAC,CAAC;MACvB;IACF;IACA;IACA,IAAI,CAAC,KAAK,IAAI,EAAE,aAAa,IAAI,KAAK,KAAK,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,EAAE;IAC3G;IACA,KAAK,CAAC,cAAc,CAAC,CAAC;IACtB,KAAK,CAAC,eAAe,CAAC,CAAC;IACvB,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;MACxC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;MACzB,SAAS,CAAC,CAAC;IACb;EACF,CAAC;;EAED;EACA,MAAM,WAAW,GAAI,KAAiB,IAAK;IACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,WAAW,KAAK,OAAO,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EACtH,KAAK,CAAC,cAAc,CAAC,CAAC;EAC1B,CAAC;EAED,MAAM,aAAa,GAAI,KAAiB,IAAK;IAC3C,IAAI,WAAW,KAAK,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC;IACtC,IAAI,CAAC,SAAS,EAAE;IAChB,KAAK,CAAC,cAAc,CAAC,CAAC;IACtB,SAAS,CAAC,MAAM,CAAC,CAAC;EACpB,CAAC;EAED,OAAO,CAAC,MAAM;IACZ,OAAO,CAAC,gBAAgB,CAAC,aAAa,EAAE,aAAa,CAAC;IACtD,OAAO,CAAC,gBAAgB,CAAC,aAAa,EAAE,aAAa,CAAC;IACtD,OAAO,CAAC,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC;IAClD,OAAO,CAAC,gBAAgB,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1D,OAAO,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE;MAAE,OAAO,EAAE;IAAM,CAAC,CAAC;IACpE,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;IAChD,OAAO,CAAC,gBAAgB,CAAC,aAAa,EAAE,aAAa,CAAC;IACtD,OAAO,CAAC,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC;IAClD,OAAO,CAAC,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC;IACnD,SAAS,CAAC,MAAM;MACd,QAAQ,CAAC,KAAK,CAAC,CAAC;MAChB,QAAQ,CAAC,CAAC;MACV,YAAY,CAAC,WAAW,CAAC;MACzB,OAAO,CAAC,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC;MACzD,OAAO,CAAC,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC;MACzD,OAAO,CAAC,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC;MACrD,OAAO,CAAC,mBAAmB,CAAC,eAAe,EAAE,eAAe,CAAC;MAC7D,OAAO,CAAC,mBAAmB,CAAC,UAAU,EAAE,UAAU,CAAC;MACnD,OAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;MACnD,OAAO,CAAC,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC;MACzD,OAAO,CAAC,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC;MACrD,OAAO,CAAC,mBAAmB,CAAC,UAAU,EAAE,aAAa,CAAC;IACxD,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF,yBACG,WAAW;IAAA,IACV,KAAK;MAAA,OAAE,KAAK,CAAC,KAAK;IAAA;IAAA,IAClB,KAAK;MAAA,OAAE,KAAK,CAAC,KAAK;IAAA;IAAA,IAClB,QAAQ;MAAA,OAAE,KAAK,CAAC,QAAQ;IAAA;IAAA,IACxB,QAAQ;MAAA,OAAE,CAAC,MAAM,CAAC,CAAC;IAAA;IAAA,IACnB,MAAM;MAAA,OAAE,KAAK,CAAC,MAAM;IAAA;IAAA;MAAA,OACb,KAAK,CAAC,KAAK;IAAA;IAAA;MAAA;QAAA;QAAA;QAAA;MAAA,aAGX,OAAO;MAAA,sDAAP,OAAO;MAAA,kCAYX,IAAI;QAAA,IAAC,IAAI;UAAA,OAAE,KAAK,CAAC,CAAC;QAAA;QAAA,UACf,OAAO;UAAA;YAAA;UAAA,kCAEK,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,OAAO,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;UAAA;QAAA;MAE3G;MAAA,sBAEqC,KAAK,CAAC,QAAQ;MAAA;QAAA,UAjBzC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,SAAS;UAAA,OAC1B,WAAW,CAAC,CAAC;UAAA,OACb,KAAK,CAAC,CAAC,EAAE,SAAS;UAAA,OAClB,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,EAAE,GAAG,SAAS;UAAA,OAC5B,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,EAAE,GAAG,SAAS;UAAA,OAElB,GAAG,KAAK,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,IAAI;UAAA,OACzB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,IAAI,CAAC,CAAC;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;MAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;MAAA;MAAA;IAAA;EAAA;AAcvE;AAEA,eAAe,WAAW;AAAC","ignoreList":[]}
|
|
@@ -4,6 +4,7 @@ export { CopyButton } from "./CopyButton";
|
|
|
4
4
|
export { Disclosure } from "./Disclosure";
|
|
5
5
|
export { Dropdown, DropdownItem, dropdownPosition } from "./Dropdown";
|
|
6
6
|
export { FilterChip } from "./FilterChip";
|
|
7
|
+
export { GestureMenu } from "./GestureMenu";
|
|
7
8
|
export { RemoveButton } from "./RemoveButton";
|
|
8
9
|
export { SegmentedControl } from "./SegmentedControl";
|
|
9
10
|
export { SplitButton } from "./SplitButton";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["actions/index.ts"],"sourcesContent":["export type { ButtonAlign, ButtonLinkProps, ButtonProps, ButtonSize, ButtonVariant, IconButtonLinkProps, IconButtonProps } from \"./Button\";\nexport { Button, ButtonLink, IconButton, IconButtonLink } from \"./Button\";\nexport type { ContextMenuProps } from \"./ContextMenu\";\nexport { ContextMenu } from \"./ContextMenu\";\nexport type { CopyButtonProps, CopyButtonValue } from \"./CopyButton\";\nexport { CopyButton } from \"./CopyButton\";\nexport type { DisclosureProps } from \"./Disclosure\";\nexport { Disclosure } from \"./Disclosure\";\nexport type {\n DropdownAction,\n DropdownActionBase,\n DropdownChoice,\n DropdownItemProps,\n DropdownPosition,\n DropdownProps,\n DropdownSection,\n DropdownTriggerProps,\n} from \"./Dropdown\";\nexport { Dropdown, DropdownItem, dropdownPosition } from \"./Dropdown\";\nexport type { FilterChipOption, FilterChipProps, FilterChipSection } from \"./FilterChip\";\nexport { FilterChip } from \"./FilterChip\";\nexport type { RemoveButtonProps } from \"./RemoveButton\";\nexport { RemoveButton } from \"./RemoveButton\";\nexport type { SegmentedControlProps, SegmentOption } from \"./SegmentedControl\";\nexport { SegmentedControl } from \"./SegmentedControl\";\nexport type { SplitButtonProps } from \"./SplitButton\";\nexport { SplitButton } from \"./SplitButton\";\nexport type {\n SpotlightButtonProps,\n SpotlightButtonVariant,\n SpotlightSearchOptions,\n SpotlightSearchResolver,\n} from \"./SpotlightSearch\";\nexport {\n openSpotlightSearch,\n SpotlightButton,\n} from \"./SpotlightSearch\";\nexport type { TabOption, TabsItemProps, TabsProps } from \"./Tabs\";\nexport { Tabs } from \"./Tabs\";\nexport type { ToolbarGroupProps, ToolbarProps, ToolbarSeparatorProps } from \"./Toolbar\";\nexport { Toolbar } from \"./Toolbar\";\n"],"mappings":"AACA,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,QAAQ,UAAU;AAEzE,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,UAAU,QAAQ,cAAc;AAWzC,SAAS,QAAQ,EAAE,YAAY,EAAE,gBAAgB,QAAQ,YAAY;AAErE,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,YAAY,QAAQ,gBAAgB;AAE7C,SAAS,gBAAgB,QAAQ,oBAAoB;AAErD,SAAS,WAAW,QAAQ,eAAe;AAO3C,SACE,mBAAmB,EACnB,eAAe,QACV,mBAAmB;AAE1B,SAAS,IAAI,QAAQ,QAAQ;AAE7B,SAAS,OAAO,QAAQ,WAAW","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sources":["actions/index.ts"],"sourcesContent":["export type { ButtonAlign, ButtonLinkProps, ButtonProps, ButtonSize, ButtonVariant, IconButtonLinkProps, IconButtonProps } from \"./Button\";\nexport { Button, ButtonLink, IconButton, IconButtonLink } from \"./Button\";\nexport type { ContextMenuProps } from \"./ContextMenu\";\nexport { ContextMenu } from \"./ContextMenu\";\nexport type { CopyButtonProps, CopyButtonValue } from \"./CopyButton\";\nexport { CopyButton } from \"./CopyButton\";\nexport type { DisclosureProps } from \"./Disclosure\";\nexport { Disclosure } from \"./Disclosure\";\nexport type {\n DropdownAction,\n DropdownActionBase,\n DropdownChoice,\n DropdownItemProps,\n DropdownPosition,\n DropdownProps,\n DropdownSection,\n DropdownTriggerProps,\n} from \"./Dropdown\";\nexport { Dropdown, DropdownItem, dropdownPosition } from \"./Dropdown\";\nexport type { FilterChipOption, FilterChipProps, FilterChipSection } from \"./FilterChip\";\nexport { FilterChip } from \"./FilterChip\";\nexport type { GestureKind, GestureMenuAction, GestureMenuItem, GestureMenuProps, GestureMenuSection } from \"./GestureMenu\";\nexport { GestureMenu } from \"./GestureMenu\";\nexport type { RemoveButtonProps } from \"./RemoveButton\";\nexport { RemoveButton } from \"./RemoveButton\";\nexport type { SegmentedControlProps, SegmentOption } from \"./SegmentedControl\";\nexport { SegmentedControl } from \"./SegmentedControl\";\nexport type { SplitButtonProps } from \"./SplitButton\";\nexport { SplitButton } from \"./SplitButton\";\nexport type {\n SpotlightButtonProps,\n SpotlightButtonVariant,\n SpotlightSearchOptions,\n SpotlightSearchResolver,\n} from \"./SpotlightSearch\";\nexport {\n openSpotlightSearch,\n SpotlightButton,\n} from \"./SpotlightSearch\";\nexport type { TabOption, TabsItemProps, TabsProps } from \"./Tabs\";\nexport { Tabs } from \"./Tabs\";\nexport type { ToolbarGroupProps, ToolbarProps, ToolbarSeparatorProps } from \"./Toolbar\";\nexport { Toolbar } from \"./Toolbar\";\n"],"mappings":"AACA,SAAS,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,QAAQ,UAAU;AAEzE,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,UAAU,QAAQ,cAAc;AAWzC,SAAS,QAAQ,EAAE,YAAY,EAAE,gBAAgB,QAAQ,YAAY;AAErE,SAAS,UAAU,QAAQ,cAAc;AAEzC,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,YAAY,QAAQ,gBAAgB;AAE7C,SAAS,gBAAgB,QAAQ,oBAAoB;AAErD,SAAS,WAAW,QAAQ,eAAe;AAO3C,SACE,mBAAmB,EACnB,eAAe,QACV,mBAAmB;AAE1B,SAAS,IAAI,QAAQ,QAAQ;AAE7B,SAAS,OAAO,QAAQ,WAAW","ignoreList":[]}
|
|
@@ -57,7 +57,7 @@ import { Tooltip } from "../feedback/Tooltip";
|
|
|
57
57
|
import { replaceTextareaRange } from "../inputs/editor-dom";
|
|
58
58
|
import { completedEmojiShortcode, emojiInTone, emojiName, emojiShortcodeQuery, loadEmojiIndex, suggestEmoji } from "../inputs/emoji/emoji-index";
|
|
59
59
|
import { FileDropTarget } from "../inputs/FileDropTarget";
|
|
60
|
-
import { toggleBulletList,
|
|
60
|
+
import { toggleBulletList, toggleInlineMarker } from "../inputs/markdown/actions";
|
|
61
61
|
import { openCodeFence } from "../inputs/markdown/code-zone";
|
|
62
62
|
import { SelectChip } from "../inputs/SelectChip";
|
|
63
63
|
import { useLocale } from "../intl/locale";
|
|
@@ -70,18 +70,7 @@ const composerMaxInputHeight = 309;
|
|
|
70
70
|
const emojiSuggestionLimit = 8;
|
|
71
71
|
// Keep editor undo text within approximately 2 MiB, plus the current edit.
|
|
72
72
|
const composerUndoBudgetChars = 1_000_000;
|
|
73
|
-
|
|
74
|
-
const {
|
|
75
|
-
value,
|
|
76
|
-
selectionStart,
|
|
77
|
-
selectionEnd
|
|
78
|
-
} = textarea;
|
|
79
|
-
const label = value.slice(selectionStart, selectionEnd);
|
|
80
|
-
replaceTextareaRange(textarea, selectionStart, selectionEnd, `[${label}]()`);
|
|
81
|
-
// Without a selection the label comes first, otherwise the address.
|
|
82
|
-
const caret = label ? selectionStart + label.length + 3 : selectionStart + 1;
|
|
83
|
-
textarea.setSelectionRange(caret, caret);
|
|
84
|
-
};
|
|
73
|
+
/** The row stays short: what a chat message needs. Other Markdown, typed by hand, keeps working. */
|
|
85
74
|
const formatTools = [{
|
|
86
75
|
id: "bold",
|
|
87
76
|
icon: "ti ti-bold",
|
|
@@ -94,39 +83,12 @@ const formatTools = [{
|
|
|
94
83
|
label: m => m.italicShortcut,
|
|
95
84
|
run: textarea => toggleInlineMarker(textarea, "*"),
|
|
96
85
|
key: "i"
|
|
97
|
-
}, {
|
|
98
|
-
id: "strikethrough",
|
|
99
|
-
icon: "ti ti-strikethrough",
|
|
100
|
-
label: m => m.strikethroughShortcut,
|
|
101
|
-
run: textarea => toggleInlineMarker(textarea, "~~"),
|
|
102
|
-
shiftCode: "KeyX"
|
|
103
|
-
}, {
|
|
104
|
-
id: "code",
|
|
105
|
-
icon: "ti ti-code",
|
|
106
|
-
label: m => m.inlineCodeShortcut,
|
|
107
|
-
run: textarea => toggleInlineMarker(textarea, "`"),
|
|
108
|
-
key: "e"
|
|
109
|
-
}, {
|
|
110
|
-
id: "code-block",
|
|
111
|
-
icon: "ti ti-source-code",
|
|
112
|
-
label: m => m.codeBlock,
|
|
113
|
-
run: toggleCodeBlock
|
|
114
86
|
}, {
|
|
115
87
|
id: "bullet-list",
|
|
116
88
|
icon: "ti ti-list",
|
|
117
89
|
label: m => m.bulletListShortcut,
|
|
118
90
|
run: toggleBulletList,
|
|
119
91
|
shiftCode: "Digit8"
|
|
120
|
-
}, {
|
|
121
|
-
id: "quote",
|
|
122
|
-
icon: "ti ti-quote",
|
|
123
|
-
label: m => m.quote,
|
|
124
|
-
run: toggleQuote
|
|
125
|
-
}, {
|
|
126
|
-
id: "link",
|
|
127
|
-
icon: "ti ti-link",
|
|
128
|
-
label: m => m.insertLink,
|
|
129
|
-
run: insertComposerLink
|
|
130
92
|
}];
|
|
131
93
|
const formatShortcut = event => {
|
|
132
94
|
if (event.altKey || !(event.ctrlKey || event.metaKey)) return undefined;
|
|
@@ -768,7 +730,7 @@ export function ChatComposer(props) {
|
|
|
768
730
|
onClick: chooseFiles,
|
|
769
731
|
get children() {
|
|
770
732
|
var _el$5 = _$getNextElement(_tmpl$3);
|
|
771
|
-
_$effect(() => _$className(_el$5, addingFiles() ? "ti ti-loader-2 k2b-spin" : "ti ti-
|
|
733
|
+
_$effect(() => _$className(_el$5, addingFiles() ? "ti ti-loader-2 k2b-spin" : "ti ti-plus"));
|
|
772
734
|
return _el$5;
|
|
773
735
|
}
|
|
774
736
|
})
|