@svgrid/grid 2.6.22 → 2.7.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/CHANGELOG.md +88 -88
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/GridMenus.svelte +184 -6
- package/dist/SvAutoComplete.svelte +169 -165
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDateRangeInput.svelte +25 -3
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.controller.svelte.d.ts +48 -1
- package/dist/SvGrid.controller.svelte.js +214 -52
- package/dist/SvGrid.css +221 -0
- package/dist/SvGrid.helpers.d.ts +55 -0
- package/dist/SvGrid.helpers.js +83 -0
- package/dist/SvGrid.svelte +145 -0
- package/dist/SvGrid.types.d.ts +105 -0
- package/dist/SvGridCellEditor.svelte +744 -662
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -266
- package/dist/SvGridSelect.svelte.d.ts +1 -1
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -326
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -289
- package/dist/SvMultiSelect.svelte.d.ts +1 -1
- package/dist/SvNumberInput.svelte +172 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichCell.svelte +125 -0
- package/dist/SvRichCell.svelte.d.ts +15 -0
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextArea.svelte +5 -1
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -235
- package/dist/SvTreeSelect.svelte.d.ts +1 -1
- package/dist/builtin-editors.d.ts +2 -2
- package/dist/builtin-editors.js +19 -3
- package/dist/cdn/GridMenus-C4oLsJyj.js +600 -0
- package/dist/cdn/GridMenus-yEivjNwl.js +601 -0
- package/dist/cdn/SvDateRangeInput-Bh0A0JkF.js +199 -0
- package/dist/cdn/SvDateRangeInput-DflbiP7N.js +198 -0
- package/dist/cdn/SvDateTimePicker-BWwpfB_o.js +812 -0
- package/dist/cdn/SvDateTimePicker-Bivn8dAP.js +816 -0
- package/dist/cdn/SvGridCellEditor-AXL8cHGO.js +550 -0
- package/dist/cdn/SvGridCellEditor-Ba7rY3eu.js +549 -0
- package/dist/cdn/{SvGridChart-BwVos976.js → SvGridChart-Bs2GIR2Q.js} +1 -1
- package/dist/cdn/{SvGridChart-BEJmNNx9.js → SvGridChart-CwhFz7GV.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-BOknOkrP.js → SvGridChartPanel-1WXuStzM.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-D2PjII4O.js → SvGridChartPanel-e5_nqNP9.js} +1 -1
- package/dist/cdn/{SvGridChartView-00LPUHL1.js → SvGridChartView-SmPW10dI.js} +1 -1
- package/dist/cdn/{SvGridChartView-BhUEJ5ki.js → SvGridChartView-eSPuJE6g.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-B1ZcLpgs.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CtdbHcIS.js} +1 -1
- package/dist/cdn/date-format-CcP1tafP.js +1395 -0
- package/dist/cdn/date-format-DZT7T1wf.js +1393 -0
- package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
- package/dist/cdn/{src-C9Hihx1W.js → src-COC0vnpg.js} +7233 -6701
- package/dist/cdn/{src-D1lXwq1l.js → src-pYGV2rMR.js} +5747 -5215
- package/dist/cdn/svgrid.js +15 -13
- package/dist/cdn/svgrid.svelte-external.js +15 -13
- package/dist/cell-values.d.ts +16 -0
- package/dist/cell-values.js +28 -0
- package/dist/chart-export.js +8 -8
- package/dist/clipboard.d.ts +22 -0
- package/dist/clipboard.js +405 -27
- package/dist/editing.js +12 -7
- package/dist/editors/cell-editors.d.ts +11 -0
- package/dist/editors/cell-editors.js +18 -1
- package/dist/grid-messages.d.ts +15 -0
- package/dist/grid-messages.js +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/list-option.d.ts +6 -0
- package/dist/menus.js +40 -7
- package/dist/sanitize-html.d.ts +37 -0
- package/dist/sanitize-html.js +234 -0
- package/dist/selection-bar-view.svelte.d.ts +25 -0
- package/dist/selection-bar-view.svelte.js +13 -0
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +129 -13
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/GridMenus.svelte +184 -6
- package/src/SvAutoComplete.svelte +169 -165
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDateRangeInput.svelte +25 -3
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +251 -61
- package/src/SvGrid.css +221 -0
- package/src/SvGrid.helpers.ts +87 -0
- package/src/SvGrid.svelte +145 -0
- package/src/SvGrid.types.ts +2092 -1980
- package/src/SvGridCellEditor.svelte +744 -662
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -266
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -326
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +293 -289
- package/src/SvNumberInput.svelte +172 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichCell.svelte +125 -0
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextArea.svelte +5 -1
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -235
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/builtin-editors.test.ts +18 -1
- package/src/builtin-editors.ts +19 -3
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -118
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/clipboard.test.ts +93 -20
- package/src/clipboard.ts +433 -33
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1865 -1865
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +322 -322
- package/src/date-string-column.test.ts +87 -0
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -949
- package/src/editing.ts +609 -606
- package/src/editor-block-prop.test.ts +132 -0
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/editors/cell-editors.ts +27 -1
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -325
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/grid-messages.ts +34 -0
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/index.ts +13 -0
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +179 -173
- package/src/menus.test.ts +43 -0
- package/src/menus.ts +597 -567
- package/src/merge-objects.ts +48 -48
- package/src/move-cells.test.ts +850 -0
- package/src/number-editor-literal.test.ts +84 -0
- package/src/number-editor.grid.test.ts +160 -0
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/row-drag-touch.ts +211 -211
- package/src/row-drag.test.ts +353 -353
- package/src/row-drag.ts +415 -415
- package/src/row-resize.test.ts +524 -524
- package/src/row-resize.ts +228 -228
- package/src/sanitize-html.test.ts +182 -0
- package/src/sanitize-html.ts +235 -0
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection-bar-view.svelte.ts +36 -0
- package/src/selection.test.ts +900 -743
- package/src/selection.ts +131 -13
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +377 -331
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
- package/src/svgrid.menu-scroll-close.test.ts +204 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.selection-bar-seam.test.ts +185 -0
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.upsell-license.test.ts +117 -0
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +322 -322
- package/dist/cdn/GridMenus-BuoBPqxx.js +0 -493
- package/dist/cdn/GridMenus-n4llxoOI.js +0 -494
- package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
- package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
- package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
- package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
package/src/row-drag-touch.ts
CHANGED
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
// Touch / pointer row dragging (#66).
|
|
2
|
-
//
|
|
3
|
-
// HTML5 drag events do not exist for touch input on iOS Safari or Android
|
|
4
|
-
// Chrome - `dragstart` never fires - so the drag path in `row-drag.ts` is dead
|
|
5
|
-
// on a phone. This is a second INPUT path onto the same model: it populates the
|
|
6
|
-
// same cross-grid `bus` and the same `rowDropIndex` / `rowDropSide`, and commits
|
|
7
|
-
// through the same `commitDropOnRow`, so reorder, cross-grid hand-off and the
|
|
8
|
-
// `onRowDragEnd` payload cannot drift between mouse and touch.
|
|
9
|
-
//
|
|
10
|
-
// Loaded with `import()` on the first touch that lands on a draggable row, so
|
|
11
|
-
// desktop users never download it. The 350 ms long press below more than covers
|
|
12
|
-
// the fetch, and the drag cannot begin before it elapses anyway.
|
|
13
|
-
//
|
|
14
|
-
// A touch that starts on a row must still be able to SCROLL the grid, so the
|
|
15
|
-
// drag only begins after a long press; moving before that threshold hands the
|
|
16
|
-
// gesture back to the browser.
|
|
17
|
-
|
|
18
|
-
/** Everything the touch path needs from the `createRowDrag` closure. */
|
|
19
|
-
export type TouchDragDeps = {
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
-
ctx: any;
|
|
22
|
-
managed: () => boolean;
|
|
23
|
-
originalAt: (rowIndex: number) => unknown;
|
|
24
|
-
commitDropOnRow: (rowIndex: number, side: "before" | "after") => void;
|
|
25
|
-
clearIndicators: () => void;
|
|
26
|
-
cancelPendingLeave: () => void;
|
|
27
|
-
/** Publish the dragged row on the shared cross-grid bus. */
|
|
28
|
-
openBus: (row: unknown) => void;
|
|
29
|
-
closeBus: () => void;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const LONG_PRESS_MS = 350;
|
|
33
|
-
/** Movement (px) before the long press is treated as a scroll instead. */
|
|
34
|
-
const SCROLL_SLOP_PX = 10;
|
|
35
|
-
/** Distance from an edge (px) at which the grid starts auto-scrolling. */
|
|
36
|
-
const EDGE_PX = 48;
|
|
37
|
-
const EDGE_SPEED_PX = 12;
|
|
38
|
-
|
|
39
|
-
type TouchState = {
|
|
40
|
-
pointerId: number;
|
|
41
|
-
rowIndex: number;
|
|
42
|
-
startX: number;
|
|
43
|
-
startY: number;
|
|
44
|
-
started: boolean;
|
|
45
|
-
timer: ReturnType<typeof setTimeout> | null;
|
|
46
|
-
scrollFrame: number | null;
|
|
47
|
-
scroller: HTMLElement | null;
|
|
48
|
-
root: HTMLElement | null;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** What {@link createTouchDrag} returns. */
|
|
52
|
-
export type TouchDragHandle = {
|
|
53
|
-
start: (e: PointerEvent, rowIndex: number) => void;
|
|
54
|
-
destroy: () => void;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export function createTouchDrag(deps: TouchDragDeps): TouchDragHandle {
|
|
58
|
-
const { ctx } = deps;
|
|
59
|
-
let touch: TouchState | null = null;
|
|
60
|
-
|
|
61
|
-
/** Resolve the row under a viewport point, and which half of it. */
|
|
62
|
-
function rowAtPoint(x: number, y: number): { index: number; side: "before" | "after" } | null {
|
|
63
|
-
const el = document.elementFromPoint(x, y) as HTMLElement | null;
|
|
64
|
-
const tr = el?.closest?.("tr.sv-grid-row") as HTMLElement | null;
|
|
65
|
-
if (!tr) return null;
|
|
66
|
-
const marker = tr.querySelector<HTMLElement>("[data-svgrid-row]");
|
|
67
|
-
const raw = marker?.dataset.svgridRow;
|
|
68
|
-
if (raw == null) return null;
|
|
69
|
-
const index = Number(raw);
|
|
70
|
-
if (!Number.isFinite(index)) return null;
|
|
71
|
-
const rect = tr.getBoundingClientRect();
|
|
72
|
-
return { index, side: y < rect.top + rect.height / 2 ? "before" : "after" };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function stopEdgeScroll() {
|
|
76
|
-
if (touch?.scrollFrame != null) {
|
|
77
|
-
cancelAnimationFrame(touch.scrollFrame);
|
|
78
|
-
touch.scrollFrame = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Keep scrolling while the finger is parked near the top or bottom edge. */
|
|
83
|
-
function edgeScroll(clientY: number) {
|
|
84
|
-
const scroller = touch?.scroller;
|
|
85
|
-
if (!touch || !scroller) return;
|
|
86
|
-
const rect = scroller.getBoundingClientRect();
|
|
87
|
-
let dy = 0;
|
|
88
|
-
if (clientY < rect.top + EDGE_PX) dy = -EDGE_SPEED_PX;
|
|
89
|
-
else if (clientY > rect.bottom - EDGE_PX) dy = EDGE_SPEED_PX;
|
|
90
|
-
stopEdgeScroll();
|
|
91
|
-
if (dy === 0) return;
|
|
92
|
-
const step = () => {
|
|
93
|
-
if (!touch?.started) return;
|
|
94
|
-
scroller.scrollTop += dy;
|
|
95
|
-
// Re-hit-test as rows move under a stationary finger, otherwise the
|
|
96
|
-
// indicator freezes on whichever row happened to be there at touch time.
|
|
97
|
-
const hit = rowAtPoint(touch.startX, clientY);
|
|
98
|
-
if (hit) {
|
|
99
|
-
ctx.rowDropIndex = hit.index;
|
|
100
|
-
ctx.rowDropSide = hit.side;
|
|
101
|
-
}
|
|
102
|
-
touch.scrollFrame = requestAnimationFrame(step);
|
|
103
|
-
};
|
|
104
|
-
touch.scrollFrame = requestAnimationFrame(step);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// `pointermove.preventDefault()` does NOT stop iOS scrolling; only a
|
|
108
|
-
// non-passive `touchmove` does. Registered for the life of the drag.
|
|
109
|
-
function blockTouchScroll(e: TouchEvent) {
|
|
110
|
-
if (touch?.started) e.preventDefault();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function detach() {
|
|
114
|
-
window.removeEventListener("pointermove", onPointerMove);
|
|
115
|
-
window.removeEventListener("pointerup", onPointerUp);
|
|
116
|
-
window.removeEventListener("pointercancel", onPointerCancel);
|
|
117
|
-
window.removeEventListener("touchmove", blockTouchScroll);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function end(commit: boolean) {
|
|
121
|
-
if (!touch) return;
|
|
122
|
-
const t = touch;
|
|
123
|
-
if (t.timer) clearTimeout(t.timer);
|
|
124
|
-
stopEdgeScroll();
|
|
125
|
-
touch = null;
|
|
126
|
-
detach();
|
|
127
|
-
t.root?.classList.remove("sv-grid-row-drag-active");
|
|
128
|
-
if (!t.started) return;
|
|
129
|
-
if (commit && ctx.rowDropIndex != null) {
|
|
130
|
-
deps.commitDropOnRow(
|
|
131
|
-
ctx.rowDropIndex as number,
|
|
132
|
-
(ctx.rowDropSide ?? "before") as "before" | "after",
|
|
133
|
-
);
|
|
134
|
-
} else {
|
|
135
|
-
deps.clearIndicators();
|
|
136
|
-
deps.closeBus();
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function onPointerMove(e: PointerEvent) {
|
|
141
|
-
if (!touch || e.pointerId !== touch.pointerId) return;
|
|
142
|
-
if (!touch.started) {
|
|
143
|
-
// Still inside the long press: a real move means the user is scrolling.
|
|
144
|
-
const moved = Math.abs(e.clientY - touch.startY) + Math.abs(e.clientX - touch.startX);
|
|
145
|
-
if (moved > SCROLL_SLOP_PX) end(false);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
e.preventDefault();
|
|
149
|
-
const hit = rowAtPoint(e.clientX, e.clientY);
|
|
150
|
-
if (hit) {
|
|
151
|
-
deps.cancelPendingLeave();
|
|
152
|
-
ctx.rowDropIndex = hit.index;
|
|
153
|
-
ctx.rowDropSide = hit.side;
|
|
154
|
-
}
|
|
155
|
-
touch.startX = e.clientX;
|
|
156
|
-
edgeScroll(e.clientY);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function onPointerUp(e: PointerEvent) {
|
|
160
|
-
if (!touch || e.pointerId !== touch.pointerId) return;
|
|
161
|
-
end(true);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function onPointerCancel(e: PointerEvent) {
|
|
165
|
-
if (!touch || e.pointerId !== touch.pointerId) return;
|
|
166
|
-
end(false);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
/** Begin a press that may become a drag. Caller has already checked touch. */
|
|
171
|
-
start(e: PointerEvent, rowIndex: number) {
|
|
172
|
-
if (!deps.managed() || touch) return;
|
|
173
|
-
const row = deps.originalAt(rowIndex);
|
|
174
|
-
if (row == null) return;
|
|
175
|
-
const target = e.currentTarget as HTMLElement | null;
|
|
176
|
-
touch = {
|
|
177
|
-
pointerId: e.pointerId,
|
|
178
|
-
rowIndex,
|
|
179
|
-
startX: e.clientX,
|
|
180
|
-
startY: e.clientY,
|
|
181
|
-
started: false,
|
|
182
|
-
timer: null,
|
|
183
|
-
scrollFrame: null,
|
|
184
|
-
scroller: target?.closest?.<HTMLElement>(".sv-grid-container") ?? null,
|
|
185
|
-
root: target?.closest?.<HTMLElement>(".sv-grid-root") ?? null,
|
|
186
|
-
};
|
|
187
|
-
window.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
188
|
-
window.addEventListener("pointerup", onPointerUp);
|
|
189
|
-
window.addEventListener("pointercancel", onPointerCancel);
|
|
190
|
-
window.addEventListener("touchmove", blockTouchScroll, { passive: false });
|
|
191
|
-
touch.timer = setTimeout(() => {
|
|
192
|
-
if (!touch) return;
|
|
193
|
-
touch.started = true;
|
|
194
|
-
touch.timer = null;
|
|
195
|
-
touch.root?.classList.add("sv-grid-row-drag-active");
|
|
196
|
-
try {
|
|
197
|
-
target?.setPointerCapture?.(e.pointerId);
|
|
198
|
-
} catch {
|
|
199
|
-
// Not every environment implements pointer capture (tests, old WebViews).
|
|
200
|
-
}
|
|
201
|
-
deps.openBus(row);
|
|
202
|
-
ctx.rowDragActive = true;
|
|
203
|
-
ctx.rowDropIndex = rowIndex;
|
|
204
|
-
ctx.rowDropSide = "before";
|
|
205
|
-
}, LONG_PRESS_MS);
|
|
206
|
-
},
|
|
207
|
-
destroy() {
|
|
208
|
-
end(false);
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
1
|
+
// Touch / pointer row dragging (#66).
|
|
2
|
+
//
|
|
3
|
+
// HTML5 drag events do not exist for touch input on iOS Safari or Android
|
|
4
|
+
// Chrome - `dragstart` never fires - so the drag path in `row-drag.ts` is dead
|
|
5
|
+
// on a phone. This is a second INPUT path onto the same model: it populates the
|
|
6
|
+
// same cross-grid `bus` and the same `rowDropIndex` / `rowDropSide`, and commits
|
|
7
|
+
// through the same `commitDropOnRow`, so reorder, cross-grid hand-off and the
|
|
8
|
+
// `onRowDragEnd` payload cannot drift between mouse and touch.
|
|
9
|
+
//
|
|
10
|
+
// Loaded with `import()` on the first touch that lands on a draggable row, so
|
|
11
|
+
// desktop users never download it. The 350 ms long press below more than covers
|
|
12
|
+
// the fetch, and the drag cannot begin before it elapses anyway.
|
|
13
|
+
//
|
|
14
|
+
// A touch that starts on a row must still be able to SCROLL the grid, so the
|
|
15
|
+
// drag only begins after a long press; moving before that threshold hands the
|
|
16
|
+
// gesture back to the browser.
|
|
17
|
+
|
|
18
|
+
/** Everything the touch path needs from the `createRowDrag` closure. */
|
|
19
|
+
export type TouchDragDeps = {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
ctx: any;
|
|
22
|
+
managed: () => boolean;
|
|
23
|
+
originalAt: (rowIndex: number) => unknown;
|
|
24
|
+
commitDropOnRow: (rowIndex: number, side: "before" | "after") => void;
|
|
25
|
+
clearIndicators: () => void;
|
|
26
|
+
cancelPendingLeave: () => void;
|
|
27
|
+
/** Publish the dragged row on the shared cross-grid bus. */
|
|
28
|
+
openBus: (row: unknown) => void;
|
|
29
|
+
closeBus: () => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const LONG_PRESS_MS = 350;
|
|
33
|
+
/** Movement (px) before the long press is treated as a scroll instead. */
|
|
34
|
+
const SCROLL_SLOP_PX = 10;
|
|
35
|
+
/** Distance from an edge (px) at which the grid starts auto-scrolling. */
|
|
36
|
+
const EDGE_PX = 48;
|
|
37
|
+
const EDGE_SPEED_PX = 12;
|
|
38
|
+
|
|
39
|
+
type TouchState = {
|
|
40
|
+
pointerId: number;
|
|
41
|
+
rowIndex: number;
|
|
42
|
+
startX: number;
|
|
43
|
+
startY: number;
|
|
44
|
+
started: boolean;
|
|
45
|
+
timer: ReturnType<typeof setTimeout> | null;
|
|
46
|
+
scrollFrame: number | null;
|
|
47
|
+
scroller: HTMLElement | null;
|
|
48
|
+
root: HTMLElement | null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** What {@link createTouchDrag} returns. */
|
|
52
|
+
export type TouchDragHandle = {
|
|
53
|
+
start: (e: PointerEvent, rowIndex: number) => void;
|
|
54
|
+
destroy: () => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function createTouchDrag(deps: TouchDragDeps): TouchDragHandle {
|
|
58
|
+
const { ctx } = deps;
|
|
59
|
+
let touch: TouchState | null = null;
|
|
60
|
+
|
|
61
|
+
/** Resolve the row under a viewport point, and which half of it. */
|
|
62
|
+
function rowAtPoint(x: number, y: number): { index: number; side: "before" | "after" } | null {
|
|
63
|
+
const el = document.elementFromPoint(x, y) as HTMLElement | null;
|
|
64
|
+
const tr = el?.closest?.("tr.sv-grid-row") as HTMLElement | null;
|
|
65
|
+
if (!tr) return null;
|
|
66
|
+
const marker = tr.querySelector<HTMLElement>("[data-svgrid-row]");
|
|
67
|
+
const raw = marker?.dataset.svgridRow;
|
|
68
|
+
if (raw == null) return null;
|
|
69
|
+
const index = Number(raw);
|
|
70
|
+
if (!Number.isFinite(index)) return null;
|
|
71
|
+
const rect = tr.getBoundingClientRect();
|
|
72
|
+
return { index, side: y < rect.top + rect.height / 2 ? "before" : "after" };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stopEdgeScroll() {
|
|
76
|
+
if (touch?.scrollFrame != null) {
|
|
77
|
+
cancelAnimationFrame(touch.scrollFrame);
|
|
78
|
+
touch.scrollFrame = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Keep scrolling while the finger is parked near the top or bottom edge. */
|
|
83
|
+
function edgeScroll(clientY: number) {
|
|
84
|
+
const scroller = touch?.scroller;
|
|
85
|
+
if (!touch || !scroller) return;
|
|
86
|
+
const rect = scroller.getBoundingClientRect();
|
|
87
|
+
let dy = 0;
|
|
88
|
+
if (clientY < rect.top + EDGE_PX) dy = -EDGE_SPEED_PX;
|
|
89
|
+
else if (clientY > rect.bottom - EDGE_PX) dy = EDGE_SPEED_PX;
|
|
90
|
+
stopEdgeScroll();
|
|
91
|
+
if (dy === 0) return;
|
|
92
|
+
const step = () => {
|
|
93
|
+
if (!touch?.started) return;
|
|
94
|
+
scroller.scrollTop += dy;
|
|
95
|
+
// Re-hit-test as rows move under a stationary finger, otherwise the
|
|
96
|
+
// indicator freezes on whichever row happened to be there at touch time.
|
|
97
|
+
const hit = rowAtPoint(touch.startX, clientY);
|
|
98
|
+
if (hit) {
|
|
99
|
+
ctx.rowDropIndex = hit.index;
|
|
100
|
+
ctx.rowDropSide = hit.side;
|
|
101
|
+
}
|
|
102
|
+
touch.scrollFrame = requestAnimationFrame(step);
|
|
103
|
+
};
|
|
104
|
+
touch.scrollFrame = requestAnimationFrame(step);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// `pointermove.preventDefault()` does NOT stop iOS scrolling; only a
|
|
108
|
+
// non-passive `touchmove` does. Registered for the life of the drag.
|
|
109
|
+
function blockTouchScroll(e: TouchEvent) {
|
|
110
|
+
if (touch?.started) e.preventDefault();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function detach() {
|
|
114
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
115
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
116
|
+
window.removeEventListener("pointercancel", onPointerCancel);
|
|
117
|
+
window.removeEventListener("touchmove", blockTouchScroll);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function end(commit: boolean) {
|
|
121
|
+
if (!touch) return;
|
|
122
|
+
const t = touch;
|
|
123
|
+
if (t.timer) clearTimeout(t.timer);
|
|
124
|
+
stopEdgeScroll();
|
|
125
|
+
touch = null;
|
|
126
|
+
detach();
|
|
127
|
+
t.root?.classList.remove("sv-grid-row-drag-active");
|
|
128
|
+
if (!t.started) return;
|
|
129
|
+
if (commit && ctx.rowDropIndex != null) {
|
|
130
|
+
deps.commitDropOnRow(
|
|
131
|
+
ctx.rowDropIndex as number,
|
|
132
|
+
(ctx.rowDropSide ?? "before") as "before" | "after",
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
deps.clearIndicators();
|
|
136
|
+
deps.closeBus();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function onPointerMove(e: PointerEvent) {
|
|
141
|
+
if (!touch || e.pointerId !== touch.pointerId) return;
|
|
142
|
+
if (!touch.started) {
|
|
143
|
+
// Still inside the long press: a real move means the user is scrolling.
|
|
144
|
+
const moved = Math.abs(e.clientY - touch.startY) + Math.abs(e.clientX - touch.startX);
|
|
145
|
+
if (moved > SCROLL_SLOP_PX) end(false);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
const hit = rowAtPoint(e.clientX, e.clientY);
|
|
150
|
+
if (hit) {
|
|
151
|
+
deps.cancelPendingLeave();
|
|
152
|
+
ctx.rowDropIndex = hit.index;
|
|
153
|
+
ctx.rowDropSide = hit.side;
|
|
154
|
+
}
|
|
155
|
+
touch.startX = e.clientX;
|
|
156
|
+
edgeScroll(e.clientY);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onPointerUp(e: PointerEvent) {
|
|
160
|
+
if (!touch || e.pointerId !== touch.pointerId) return;
|
|
161
|
+
end(true);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function onPointerCancel(e: PointerEvent) {
|
|
165
|
+
if (!touch || e.pointerId !== touch.pointerId) return;
|
|
166
|
+
end(false);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
/** Begin a press that may become a drag. Caller has already checked touch. */
|
|
171
|
+
start(e: PointerEvent, rowIndex: number) {
|
|
172
|
+
if (!deps.managed() || touch) return;
|
|
173
|
+
const row = deps.originalAt(rowIndex);
|
|
174
|
+
if (row == null) return;
|
|
175
|
+
const target = e.currentTarget as HTMLElement | null;
|
|
176
|
+
touch = {
|
|
177
|
+
pointerId: e.pointerId,
|
|
178
|
+
rowIndex,
|
|
179
|
+
startX: e.clientX,
|
|
180
|
+
startY: e.clientY,
|
|
181
|
+
started: false,
|
|
182
|
+
timer: null,
|
|
183
|
+
scrollFrame: null,
|
|
184
|
+
scroller: target?.closest?.<HTMLElement>(".sv-grid-container") ?? null,
|
|
185
|
+
root: target?.closest?.<HTMLElement>(".sv-grid-root") ?? null,
|
|
186
|
+
};
|
|
187
|
+
window.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
188
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
189
|
+
window.addEventListener("pointercancel", onPointerCancel);
|
|
190
|
+
window.addEventListener("touchmove", blockTouchScroll, { passive: false });
|
|
191
|
+
touch.timer = setTimeout(() => {
|
|
192
|
+
if (!touch) return;
|
|
193
|
+
touch.started = true;
|
|
194
|
+
touch.timer = null;
|
|
195
|
+
touch.root?.classList.add("sv-grid-row-drag-active");
|
|
196
|
+
try {
|
|
197
|
+
target?.setPointerCapture?.(e.pointerId);
|
|
198
|
+
} catch {
|
|
199
|
+
// Not every environment implements pointer capture (tests, old WebViews).
|
|
200
|
+
}
|
|
201
|
+
deps.openBus(row);
|
|
202
|
+
ctx.rowDragActive = true;
|
|
203
|
+
ctx.rowDropIndex = rowIndex;
|
|
204
|
+
ctx.rowDropSide = "before";
|
|
205
|
+
}, LONG_PRESS_MS);
|
|
206
|
+
},
|
|
207
|
+
destroy() {
|
|
208
|
+
end(false);
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|