@svgrid/grid 2.6.21 → 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 +150 -88
- package/README.md +221 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/GridMenus.svelte +201 -18
- 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 +61 -9
- package/dist/SvGrid.controller.svelte.js +363 -123
- package/dist/SvGrid.css +222 -1
- package/dist/SvGrid.helpers.d.ts +55 -0
- package/dist/SvGrid.helpers.js +83 -0
- package/dist/SvGrid.svelte +255 -56
- package/dist/SvGrid.types.d.ts +146 -1
- 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/column-resize-DsfNXMom.js +102 -0
- 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/row-resize-BRcimkUT.js +95 -0
- package/dist/cdn/{src-BYq-qyrp.js → src-COC0vnpg.js} +7846 -7317
- package/dist/cdn/{src-DBel9wRZ.js → src-pYGV2rMR.js} +10033 -9504
- package/dist/cdn/svgrid.js +15 -11
- package/dist/cdn/svgrid.svelte-external.js +15 -11
- 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/column-groups.js +1 -1
- package/dist/column-resize.d.ts +46 -0
- package/dist/column-resize.js +205 -0
- package/dist/columns.d.ts +0 -3
- package/dist/columns.js +0 -57
- package/dist/core.d.ts +19 -4
- package/dist/core.js +460 -119
- 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/filtering/excel-filters.js +28 -0
- package/dist/grid-messages.d.ts +15 -0
- package/dist/grid-messages.js +15 -0
- package/dist/group-display.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +10 -0
- package/dist/list-option.d.ts +6 -0
- package/dist/menus.js +41 -8
- package/dist/row-resize.d.ts +11 -0
- package/dist/row-resize.js +7 -1
- 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 +133 -8
- package/dist/spreadsheet.d.ts +1 -1
- package/dist/spreadsheet.js +1 -1
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/GridMenus.svelte +201 -18
- 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 +404 -133
- package/src/SvGrid.css +222 -1
- package/src/SvGrid.helpers.ts +87 -0
- package/src/SvGrid.svelte +255 -56
- package/src/SvGrid.types.ts +2092 -1940
- 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/column-resize.test.ts +381 -0
- package/src/column-resize.ts +227 -0
- package/src/columns.test.ts +0 -103
- package/src/columns.ts +0 -58
- package/src/core.aggregate.test.ts +134 -0
- package/src/core.filter.test.ts +156 -0
- package/src/core.grouping.test.ts +146 -0
- package/src/core.performance.test.ts +30 -30
- package/src/core.row-shape.test.ts +119 -0
- package/src/core.rowmodel-cache.test.ts +121 -0
- package/src/core.sort.test.ts +293 -0
- package/src/core.ts +1865 -1468
- 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 -295
- package/src/filtering/normalize-fast-path.test.ts +104 -0
- 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 +25 -1
- 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/resize-props.test.ts +361 -0
- 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 -493
- package/src/row-resize.ts +228 -210
- 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 +135 -8
- 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-BfTAKn84.js +0 -488
- package/dist/cdn/GridMenus-C3bJd7w8.js +0 -489
- 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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sanitize-html - a dependency-free HTML sanitizer and a small Markdown
|
|
3
|
+
* renderer, sized for what a grid cell needs to display.
|
|
4
|
+
*
|
|
5
|
+
* A rich-text cell has to paint markup that came from a user, which is the one
|
|
6
|
+
* place a data grid can hand an attacker script execution. Everything rendered
|
|
7
|
+
* through `SvRichCell` goes through `sanitizeHtml` first, and the allowlist is
|
|
8
|
+
* deliberately narrower than a general-purpose sanitizer: it covers the tags
|
|
9
|
+
* `SvRichText` can produce, plus links and images.
|
|
10
|
+
*
|
|
11
|
+
* The parse uses the platform parser where there is one (`DOMParser`, which
|
|
12
|
+
* neither executes scripts nor runs loaders on a detached document) and a
|
|
13
|
+
* conservative regex stripper during SSR, where there is no DOM. The SSR path
|
|
14
|
+
* is stricter by design: when in doubt it drops markup rather than passing it
|
|
15
|
+
* through, and the hydrated client re-sanitizes with the real parser anyway.
|
|
16
|
+
*
|
|
17
|
+
* This is not a general-purpose sanitizer for arbitrary third-party HTML. It is
|
|
18
|
+
* a narrow allowlist for cell content. To render something outside the
|
|
19
|
+
* allowlist, extend it explicitly rather than widening the defaults.
|
|
20
|
+
*/
|
|
21
|
+
/** Tags that survive sanitization. Everything else is unwrapped or dropped. */
|
|
22
|
+
const ALLOWED_TAGS = new Set([
|
|
23
|
+
'a', 'b', 'blockquote', 'br', 'code', 'del', 'div', 'em', 'h1', 'h2', 'h3',
|
|
24
|
+
'h4', 'h5', 'h6', 'i', 'img', 'li', 'mark', 'ol', 'p', 'pre', 's', 'small',
|
|
25
|
+
'span', 'strong', 'sub', 'sup', 'u', 'ul',
|
|
26
|
+
]);
|
|
27
|
+
/** Tags whose whole subtree is removed rather than unwrapped. What sits inside
|
|
28
|
+
* a <script> or <style> is code, so keeping its text would be worse than
|
|
29
|
+
* losing it. */
|
|
30
|
+
const DROP_SUBTREE = new Set([
|
|
31
|
+
'script', 'style', 'iframe', 'object', 'embed', 'noscript', 'template',
|
|
32
|
+
'svg', 'math', 'form', 'input', 'button', 'textarea', 'select', 'link',
|
|
33
|
+
'meta', 'base', 'title', 'head',
|
|
34
|
+
]);
|
|
35
|
+
/** Attributes allowed per tag. `*` applies to every allowed tag. A `style`
|
|
36
|
+
* attribute is never allowed: it is the classic sanitizer hole (url(...)
|
|
37
|
+
* payloads, position tricks that let a cell cover the page chrome), and cell
|
|
38
|
+
* styling belongs to the column rather than to the data. */
|
|
39
|
+
const ALLOWED_ATTRS = {
|
|
40
|
+
'*': new Set(['class', 'dir', 'lang', 'title']),
|
|
41
|
+
a: new Set(['href', 'target', 'rel']),
|
|
42
|
+
img: new Set(['src', 'alt', 'width', 'height']),
|
|
43
|
+
ol: new Set(['start']),
|
|
44
|
+
};
|
|
45
|
+
/** URL schemes a href/src may use. Anything else, `javascript:` and
|
|
46
|
+
* `vbscript:` included, is dropped. `data:` is allowed only on an image src
|
|
47
|
+
* and only for image media types, because `data:text/html` is a script
|
|
48
|
+
* vector. */
|
|
49
|
+
const SAFE_URL = /^(?:https?:|mailto:|tel:|\/|\.\/|\.\.\/|#)/i;
|
|
50
|
+
const SAFE_IMG_DATA_URL = /^data:image\/(?:png|jpe?g|gif|webp|avif);base64,[a-z0-9+/=]*$/i;
|
|
51
|
+
function isSafeUrl(value, tag, attr) {
|
|
52
|
+
// Strip control characters and whitespace first: "java\tscript:" and
|
|
53
|
+
// "java\nscript:" are both read as javascript: by browsers.
|
|
54
|
+
// Matching control characters is the whole point here, hence the disable.
|
|
55
|
+
// eslint-disable-next-line no-control-regex
|
|
56
|
+
const url = value.replace(/[\u0000-\u0020\u007f]/g, '');
|
|
57
|
+
if (tag === 'img' && attr === 'src' && SAFE_IMG_DATA_URL.test(url))
|
|
58
|
+
return true;
|
|
59
|
+
return SAFE_URL.test(url);
|
|
60
|
+
}
|
|
61
|
+
function attrAllowed(tag, attr) {
|
|
62
|
+
if (attr.startsWith('on'))
|
|
63
|
+
return false; // every event handler, in one rule
|
|
64
|
+
if (ALLOWED_ATTRS['*'].has(attr))
|
|
65
|
+
return true;
|
|
66
|
+
return ALLOWED_ATTRS[tag]?.has(attr) ?? false;
|
|
67
|
+
}
|
|
68
|
+
/** DOM path: walk the parsed tree and strip anything off the allowlist. */
|
|
69
|
+
function sanitizeWithDom(html) {
|
|
70
|
+
const doc = new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html');
|
|
71
|
+
const walk = (node) => {
|
|
72
|
+
// Snapshot the child list: the loop mutates it as it unwraps and removes.
|
|
73
|
+
for (const child of [...node.children]) {
|
|
74
|
+
const tag = child.tagName.toLowerCase();
|
|
75
|
+
if (DROP_SUBTREE.has(tag)) {
|
|
76
|
+
child.remove();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
80
|
+
// Unwrap rather than drop, so an unknown wrapper does not cost the user
|
|
81
|
+
// the text inside it.
|
|
82
|
+
walk(child);
|
|
83
|
+
child.replaceWith(...child.childNodes);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
for (const attr of [...child.attributes]) {
|
|
87
|
+
const name = attr.name.toLowerCase();
|
|
88
|
+
if (!attrAllowed(tag, name)) {
|
|
89
|
+
child.removeAttribute(attr.name);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if ((name === 'href' || name === 'src') && !isSafeUrl(attr.value, tag, name)) {
|
|
93
|
+
child.removeAttribute(attr.name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// A link that opens a new tab gets rel="noopener", so the opened page
|
|
97
|
+
// cannot reach back through window.opener.
|
|
98
|
+
if (tag === 'a' && child.getAttribute('target')) {
|
|
99
|
+
child.setAttribute('rel', 'noopener noreferrer');
|
|
100
|
+
}
|
|
101
|
+
walk(child);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
walk(doc.body);
|
|
105
|
+
return doc.body.innerHTML;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* SSR path: no DOM, so drop dangerous subtrees outright and keep only
|
|
109
|
+
* allowlisted tags in their attribute-free form. Stricter than the DOM path on
|
|
110
|
+
* purpose - the client re-sanitizes on hydration, so the cost is a moment of
|
|
111
|
+
* plainer markup rather than a hole.
|
|
112
|
+
*/
|
|
113
|
+
function sanitizeWithoutDom(html) {
|
|
114
|
+
let out = html;
|
|
115
|
+
for (const tag of DROP_SUBTREE) {
|
|
116
|
+
out = out.replace(new RegExp(`<${tag}\\b[\\s\\S]*?</${tag}\\s*>`, 'gi'), '');
|
|
117
|
+
out = out.replace(new RegExp(`<${tag}\\b[^>]*/?>`, 'gi'), '');
|
|
118
|
+
}
|
|
119
|
+
return out.replace(/<\/?([a-z0-9-]+)([^>]*)>/gi, (match, rawTag, attrs) => {
|
|
120
|
+
const tag = rawTag.toLowerCase();
|
|
121
|
+
if (!ALLOWED_TAGS.has(tag))
|
|
122
|
+
return '';
|
|
123
|
+
if (match.startsWith('</'))
|
|
124
|
+
return `</${tag}>`;
|
|
125
|
+
return attrs.trim() ? `<${tag}>` : match;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Return `html` with everything outside the allowlist removed. Idempotent, so
|
|
130
|
+
* it is safe to call on already-sanitized markup.
|
|
131
|
+
*/
|
|
132
|
+
export function sanitizeHtml(html) {
|
|
133
|
+
const input = String(html ?? '');
|
|
134
|
+
if (!input)
|
|
135
|
+
return '';
|
|
136
|
+
if (!input.includes('<'))
|
|
137
|
+
return input; // no angle bracket, no markup to strip
|
|
138
|
+
return typeof DOMParser === 'undefined' ? sanitizeWithoutDom(input) : sanitizeWithDom(input);
|
|
139
|
+
}
|
|
140
|
+
/** Escape text so it embeds in HTML as literal characters. */
|
|
141
|
+
function escapeHtml(text) {
|
|
142
|
+
return text
|
|
143
|
+
.replace(/&/g, '&')
|
|
144
|
+
.replace(/</g, '<')
|
|
145
|
+
.replace(/>/g, '>')
|
|
146
|
+
.replace(/"/g, '"');
|
|
147
|
+
}
|
|
148
|
+
/** A sentinel that cannot occur in escaped input, used to park inline-code
|
|
149
|
+
* spans while the emphasis and link passes run over the rest. */
|
|
150
|
+
const MARK = '\u0000';
|
|
151
|
+
/** Inline Markdown. Code is extracted first so backticks protect their
|
|
152
|
+
* contents from the emphasis and link passes. */
|
|
153
|
+
function renderInline(text) {
|
|
154
|
+
const code = [];
|
|
155
|
+
let out = escapeHtml(text).replace(/`([^`]+)`/g, (_m, c) => {
|
|
156
|
+
code.push(c);
|
|
157
|
+
return `${MARK}${code.length - 1}${MARK}`;
|
|
158
|
+
});
|
|
159
|
+
out = out
|
|
160
|
+
.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, '<img src="$2" alt="$1">')
|
|
161
|
+
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2">$1</a>')
|
|
162
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
163
|
+
.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>')
|
|
164
|
+
.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
|
165
|
+
return out.replace(new RegExp(`${MARK}(\\d+)${MARK}`, 'g'), (_m, i) => `<code>${code[Number(i)]}</code>`);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Render a common Markdown subset to HTML: headings, bold, italic,
|
|
169
|
+
* strikethrough, inline code, links, images, and bullet / ordered lists.
|
|
170
|
+
*
|
|
171
|
+
* Deliberately not a full CommonMark implementation - a grid cell shows a line
|
|
172
|
+
* or two, and a complete parser is a dependency this package does not take. The
|
|
173
|
+
* output is sanitized, so a Markdown link cannot smuggle `javascript:`.
|
|
174
|
+
*/
|
|
175
|
+
export function renderMarkdown(markdown) {
|
|
176
|
+
const src = String(markdown ?? '');
|
|
177
|
+
if (!src)
|
|
178
|
+
return '';
|
|
179
|
+
const out = [];
|
|
180
|
+
let list = null;
|
|
181
|
+
const closeList = () => {
|
|
182
|
+
if (list)
|
|
183
|
+
out.push(`</${list}>`);
|
|
184
|
+
list = null;
|
|
185
|
+
};
|
|
186
|
+
for (const line of src.split(/\r?\n/)) {
|
|
187
|
+
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
188
|
+
const bullet = line.match(/^\s*[-*+]\s+(.*)$/);
|
|
189
|
+
const ordered = line.match(/^\s*\d+[.)]\s+(.*)$/);
|
|
190
|
+
if (heading) {
|
|
191
|
+
closeList();
|
|
192
|
+
const level = heading[1].length;
|
|
193
|
+
out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
|
194
|
+
}
|
|
195
|
+
else if (bullet || ordered) {
|
|
196
|
+
const want = bullet ? 'ul' : 'ol';
|
|
197
|
+
if (list !== want) {
|
|
198
|
+
closeList();
|
|
199
|
+
out.push(`<${want}>`);
|
|
200
|
+
list = want;
|
|
201
|
+
}
|
|
202
|
+
out.push(`<li>${renderInline((bullet ?? ordered)[1])}</li>`);
|
|
203
|
+
}
|
|
204
|
+
else if (!line.trim()) {
|
|
205
|
+
closeList();
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
closeList();
|
|
209
|
+
out.push(`<p>${renderInline(line)}</p>`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
closeList();
|
|
213
|
+
return sanitizeHtml(out.join(''));
|
|
214
|
+
}
|
|
215
|
+
/** Strip markup down to readable text, for exports, aria labels and tooltips
|
|
216
|
+
* where a cell's HTML has to collapse to one plain string. */
|
|
217
|
+
export function htmlToText(html) {
|
|
218
|
+
const clean = sanitizeHtml(html);
|
|
219
|
+
if (!clean)
|
|
220
|
+
return '';
|
|
221
|
+
const spaced = clean
|
|
222
|
+
.replace(/<\/(p|div|h[1-6]|li|blockquote|pre)>/gi, ' ')
|
|
223
|
+
.replace(/<br\s*\/?>/gi, ' ');
|
|
224
|
+
return spaced
|
|
225
|
+
.replace(/<[^>]*>/g, '')
|
|
226
|
+
.replace(/ /gi, ' ')
|
|
227
|
+
.replace(/</gi, '<')
|
|
228
|
+
.replace(/>/gi, '>')
|
|
229
|
+
.replace(/"/gi, '"')
|
|
230
|
+
.replace(/'/gi, "'")
|
|
231
|
+
.replace(/&/gi, '&')
|
|
232
|
+
.replace(/\s+/g, ' ')
|
|
233
|
+
.trim();
|
|
234
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selection-bar-view registry - the injection seam for the bulk-action bar that
|
|
3
|
+
* floats over the grid while rows are selected. `<SvGrid selectionBar={...}>`
|
|
4
|
+
* and its config types ship in the free grid, but the BAR itself is a paid
|
|
5
|
+
* feature in `@svgrid/enterprise`. This tiny reactive holder lets enterprise
|
|
6
|
+
* register the renderer at import time; the grid looks it up and mounts it, or
|
|
7
|
+
* shows an upsell placeholder when it is absent.
|
|
8
|
+
*
|
|
9
|
+
* Same shape as `scheduler-view` and `board-view`, deliberately: three seams
|
|
10
|
+
* that behave differently would be three things to learn.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* // in @svgrid/enterprise, at module load:
|
|
14
|
+
* import { registerSelectionBarView } from '@svgrid/grid'
|
|
15
|
+
* import SvGridSelectionBar from './SvGridSelectionBar.svelte'
|
|
16
|
+
* registerSelectionBarView(SvGridSelectionBar)
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { Component } from 'svelte';
|
|
20
|
+
/** Register the component that renders the selection bar. Enterprise calls this. */
|
|
21
|
+
export declare function registerSelectionBarView(component: Component<any>): void;
|
|
22
|
+
/** The registered selection-bar renderer, or null when enterprise is not installed. */
|
|
23
|
+
export declare function getSelectionBarView(): Component<any> | null;
|
|
24
|
+
/** Whether a selection-bar renderer has been registered. */
|
|
25
|
+
export declare function hasSelectionBarView(): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
let renderer = $state(null);
|
|
2
|
+
/** Register the component that renders the selection bar. Enterprise calls this. */
|
|
3
|
+
export function registerSelectionBarView(component) {
|
|
4
|
+
renderer = component;
|
|
5
|
+
}
|
|
6
|
+
/** The registered selection-bar renderer, or null when enterprise is not installed. */
|
|
7
|
+
export function getSelectionBarView() {
|
|
8
|
+
return renderer;
|
|
9
|
+
}
|
|
10
|
+
/** Whether a selection-bar renderer has been registered. */
|
|
11
|
+
export function hasSelectionBarView() {
|
|
12
|
+
return renderer != null;
|
|
13
|
+
}
|
package/dist/selection.d.ts
CHANGED
|
@@ -28,10 +28,11 @@ export declare function createSelection<TFeatures extends TableFeatures = TableF
|
|
|
28
28
|
maxCol: number;
|
|
29
29
|
}[];
|
|
30
30
|
isInFillPreview: (rowIndex: number, colIndex: number) => boolean;
|
|
31
|
+
dragPreviewRect: () => any;
|
|
31
32
|
findColumnById: (columnId: string) => any;
|
|
32
33
|
onCellPointerDown: (rowIndex: number, colIndex: number, event: PointerEvent) => void;
|
|
33
34
|
onCellPointerEnter: (rowIndex: number, colIndex: number) => void;
|
|
34
|
-
endDragSelection: () => void;
|
|
35
|
+
endDragSelection: (event?: PointerEvent) => void;
|
|
35
36
|
onWindowPointerMove: (event: PointerEvent) => void;
|
|
36
37
|
onCellClick: (rowIndex: number, colIndex: number) => void;
|
|
37
38
|
emitCellDoubleClick: (rowIndex: number, colIndex: number) => void;
|
package/dist/selection.js
CHANGED
|
@@ -224,7 +224,22 @@ export function createSelection(ctx) {
|
|
|
224
224
|
* that contains the cell are returned (active range checked last so its
|
|
225
225
|
* outline wins on overlap). Returns null when the cell is in no range.
|
|
226
226
|
*/
|
|
227
|
+
/** Could `getSelectionRects()` return anything? An anchored active range or
|
|
228
|
+
* a committed one is the only way a rect can exist, and both are plain
|
|
229
|
+
* property reads - so this is the cheap guard in front of anything that
|
|
230
|
+
* would otherwise allocate a rect array to discover there is nothing. */
|
|
231
|
+
function hasAnySelectionRect() {
|
|
232
|
+
const active = ctx.selectionRange;
|
|
233
|
+
const committed = ctx.selectionRanges;
|
|
234
|
+
return Boolean(committed?.length) || Boolean(active?.anchor && active?.focus);
|
|
235
|
+
}
|
|
227
236
|
function getCellRangeEdges(rowIndex, colIndex) {
|
|
237
|
+
// Bail before `getSelectionRects()`, which allocates an array and resolves
|
|
238
|
+
// every range. This runs once per rendered CELL on every render - about 250
|
|
239
|
+
// times per frame on a default grid - and with nothing selected it did all
|
|
240
|
+
// that work to return null every time.
|
|
241
|
+
if (!hasAnySelectionRect())
|
|
242
|
+
return null;
|
|
228
243
|
for (const rect of getSelectionRects()) {
|
|
229
244
|
if (rowIndex < rect.minRow ||
|
|
230
245
|
rowIndex > rect.maxRow ||
|
|
@@ -241,10 +256,40 @@ export function createSelection(ctx) {
|
|
|
241
256
|
}
|
|
242
257
|
return null;
|
|
243
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* The rectangle a drag is currently promising, or null when no drag is in
|
|
261
|
+
* flight. A fill grows the source rectangle out to the pointer; a move
|
|
262
|
+
* detaches an identically-sized rectangle and drops it at the offset the
|
|
263
|
+
* pointer has travelled. Both feed the same preview tint and marquee, so a
|
|
264
|
+
* range move looks like the fill handle users already know.
|
|
265
|
+
*/
|
|
266
|
+
function dragPreviewRect() {
|
|
267
|
+
const m = ctx.moveDrag;
|
|
268
|
+
if (m)
|
|
269
|
+
return ctx.moveDestRect(m);
|
|
270
|
+
const d = ctx.fillDrag;
|
|
271
|
+
if (!d)
|
|
272
|
+
return null;
|
|
273
|
+
return {
|
|
274
|
+
minRow: Math.min(d.sourceMinRow, d.targetRow),
|
|
275
|
+
maxRow: Math.max(d.sourceMaxRow, d.targetRow),
|
|
276
|
+
minCol: Math.min(d.sourceMinCol, d.targetCol),
|
|
277
|
+
maxCol: Math.max(d.sourceMaxCol, d.targetCol),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
244
280
|
/** Returns true when the given cell is inside the fill-drag preview
|
|
245
281
|
* range BUT outside the original source range. Used to paint a
|
|
246
282
|
* dashed-outline preview while the user is dragging the handle. */
|
|
247
283
|
function isInFillPreview(rowIndex, colIndex) {
|
|
284
|
+
if (ctx.moveDrag) {
|
|
285
|
+
const rect = dragPreviewRect();
|
|
286
|
+
if (!rect)
|
|
287
|
+
return false;
|
|
288
|
+
return (rowIndex >= rect.minRow &&
|
|
289
|
+
rowIndex <= rect.maxRow &&
|
|
290
|
+
colIndex >= rect.minCol &&
|
|
291
|
+
colIndex <= rect.maxCol);
|
|
292
|
+
}
|
|
248
293
|
const d = ctx.fillDrag;
|
|
249
294
|
if (!d)
|
|
250
295
|
return false;
|
|
@@ -270,13 +315,14 @@ export function createSelection(ctx) {
|
|
|
270
315
|
* border. Returns null when the cell isn't on the rectangle's boundary.
|
|
271
316
|
*/
|
|
272
317
|
function fillMarqueeEdges(rowIndex, colIndex) {
|
|
273
|
-
|
|
274
|
-
|
|
318
|
+
// Cheap bail before the shared rect helper, which allocates: this runs
|
|
319
|
+
// once per rendered cell and no drag is in flight almost all the time.
|
|
320
|
+
if (!ctx.fillDrag && !ctx.moveDrag)
|
|
275
321
|
return null;
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const maxC =
|
|
322
|
+
const rect = dragPreviewRect();
|
|
323
|
+
if (!rect)
|
|
324
|
+
return null;
|
|
325
|
+
const { minRow: minR, maxRow: maxR, minCol: minC, maxCol: maxC } = rect;
|
|
280
326
|
if (rowIndex < minR || rowIndex > maxR || colIndex < minC || colIndex > maxC) {
|
|
281
327
|
return null;
|
|
282
328
|
}
|
|
@@ -300,6 +346,18 @@ export function createSelection(ctx) {
|
|
|
300
346
|
const column = ctx.allColumns[colIndex];
|
|
301
347
|
if (!row || !column || isGroupRow(row))
|
|
302
348
|
return;
|
|
349
|
+
// A pointerdown on the outer few pixels of a selection border grabs the
|
|
350
|
+
// range to move it. Checked before anything else, because everything below
|
|
351
|
+
// would first collapse the selection this drag is about to relocate.
|
|
352
|
+
// Returns false unless the pointer really was on the strip, so a click
|
|
353
|
+
// anywhere further inside falls straight through to the old behaviour.
|
|
354
|
+
if (ctx.startMoveDrag(event, rowIndex, colIndex)) {
|
|
355
|
+
// A grab that never moves still ends in a `click` on the cell under the
|
|
356
|
+
// border. Left alone that click would collapse the range the user was
|
|
357
|
+
// clearly reaching for, and on a checkbox column it would flip the value.
|
|
358
|
+
moveGrabSwallowsClick = true;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
303
361
|
// NOTE: we no longer bail for checkbox columns. Bailing here meant a
|
|
304
362
|
// range drag could never START on a checkbox cell. A plain click still
|
|
305
363
|
// toggles the checkbox via onCellClick (a same-cell click), while a
|
|
@@ -335,27 +393,93 @@ export function createSelection(ctx) {
|
|
|
335
393
|
extendSelection(rowIndex, colIndex);
|
|
336
394
|
setActiveCell(rowIndex, colIndex);
|
|
337
395
|
}
|
|
338
|
-
|
|
396
|
+
/** `event` is the window `pointerup` when this comes from the real gesture,
|
|
397
|
+
* and absent when `onWindowPointerMove` calls it to recover from a release
|
|
398
|
+
* it never saw. A range move reads its copy modifier from it, so holding
|
|
399
|
+
* Ctrl between the last movement and the release still copies. */
|
|
400
|
+
function endDragSelection(event) {
|
|
339
401
|
ctx.isDraggingSelection = false;
|
|
402
|
+
// Every drag shares one edge-scroll loop, and it exits on its own once no
|
|
403
|
+
// drag is live - but stopping it here cancels the pending frame rather
|
|
404
|
+
// than letting one more fire after the gesture is over.
|
|
405
|
+
ctx.stopEdgeScroll();
|
|
340
406
|
// Also commit a fill-handle drag if one was in progress - the user
|
|
341
407
|
// released the mouse, time to apply the pattern.
|
|
342
408
|
if (ctx.fillDrag)
|
|
343
409
|
ctx.onFillPointerUp();
|
|
410
|
+
if (ctx.moveDrag)
|
|
411
|
+
ctx.onMovePointerUp(event);
|
|
344
412
|
}
|
|
345
413
|
function onWindowPointerMove(event) {
|
|
414
|
+
if (ctx.moveDrag) {
|
|
415
|
+
ctx.onMovePointerMove(event);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
346
418
|
if (ctx.fillDrag) {
|
|
347
419
|
ctx.onFillPointerMove(event);
|
|
348
420
|
return;
|
|
349
421
|
}
|
|
350
|
-
if (!ctx.isDraggingSelection)
|
|
422
|
+
if (!ctx.isDraggingSelection) {
|
|
423
|
+
updateMoveGrabHover(event);
|
|
351
424
|
return;
|
|
425
|
+
}
|
|
352
426
|
// Safety: if no mouse button is held the drag is over (we may have
|
|
353
427
|
// missed pointerup because the user lifted outside the window).
|
|
354
428
|
if (event.buttons === 0) {
|
|
355
429
|
endDragSelection();
|
|
430
|
+
return;
|
|
356
431
|
}
|
|
432
|
+
// Drag-select past the viewport edge scrolls the grid, same as the fill
|
|
433
|
+
// and move drags. Without it a range can only ever cover what was already
|
|
434
|
+
// on screen when the drag started. `onCellPointerEnter` keeps extending
|
|
435
|
+
// the range for cells the pointer actually crosses; this covers the case
|
|
436
|
+
// where the pointer has stopped at the edge and the CELLS are moving.
|
|
437
|
+
ctx.trackEdgeScroll(event);
|
|
357
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Switch the grid to a move cursor while the pointer sits on the grab strip
|
|
441
|
+
* of a selection border - without it the feature is invisible, since the
|
|
442
|
+
* border is painted, not a hit target.
|
|
443
|
+
*
|
|
444
|
+
* This runs on every window pointermove, so the order of the guards is the
|
|
445
|
+
* point: `moveCellsEffective` and the already-hovering flag are plain
|
|
446
|
+
* property reads, and `isOnMoveGrabStrip` bails on `getCellRangeEdges`
|
|
447
|
+
* before measuring anything when no range is selected.
|
|
448
|
+
*/
|
|
449
|
+
function updateMoveGrabHover(event) {
|
|
450
|
+
const wasHovering = ctx.moveGrabHover;
|
|
451
|
+
// Two property reads before anything touches the DOM. `closest()` walks
|
|
452
|
+
// ancestors on EVERY pointer move over the page, so it has to sit behind
|
|
453
|
+
// both "the feature is on" and "there is a range to grab".
|
|
454
|
+
if (!ctx.moveCellsEffective || !hasAnySelectionRect()) {
|
|
455
|
+
if (wasHovering)
|
|
456
|
+
ctx.moveGrabHover = false;
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const target = event.target;
|
|
460
|
+
const cell = target?.closest?.("td[data-svgrid-row][data-svgrid-col]");
|
|
461
|
+
let hovering = false;
|
|
462
|
+
if (cell) {
|
|
463
|
+
const r = Number(cell.dataset.svgridRow);
|
|
464
|
+
const c = Number(cell.dataset.svgridCol);
|
|
465
|
+
if (Number.isFinite(r) && Number.isFinite(c)) {
|
|
466
|
+
hovering = ctx.isOnMoveGrabStrip(cell, r, c, event.clientX, event.clientY);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (hovering !== wasHovering)
|
|
470
|
+
ctx.moveGrabHover = hovering;
|
|
471
|
+
}
|
|
472
|
+
/** Set when a pointerdown started a range move, so the `click` that
|
|
473
|
+
* follows a zero-distance grab is discarded instead of collapsing the
|
|
474
|
+
* selection it grabbed. One gesture, one flag - cleared on the next click
|
|
475
|
+
* whether or not the grab turned into a real drag. */
|
|
476
|
+
let moveGrabSwallowsClick = false;
|
|
358
477
|
function onCellClick(rowIndex, colIndex) {
|
|
478
|
+
if (moveGrabSwallowsClick) {
|
|
479
|
+
moveGrabSwallowsClick = false;
|
|
480
|
+
ctx.gridRootEl?.focus({ preventScroll: true });
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
359
483
|
const row = ctx.allRows[rowIndex];
|
|
360
484
|
const column = ctx.allColumns[colIndex];
|
|
361
485
|
if (!row || !column)
|
|
@@ -441,6 +565,7 @@ export function createSelection(ctx) {
|
|
|
441
565
|
fillMarqueeEdges,
|
|
442
566
|
getSelectionRects,
|
|
443
567
|
isInFillPreview,
|
|
568
|
+
dragPreviewRect,
|
|
444
569
|
findColumnById,
|
|
445
570
|
onCellPointerDown,
|
|
446
571
|
onCellPointerEnter,
|
package/dist/spreadsheet.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export type SpanColumn<TData = Record<string, unknown>> = {
|
|
|
63
63
|
/**
|
|
64
64
|
* Turn declarative per-column `colSpan` / `rowSpan` callbacks into a
|
|
65
65
|
* `MergeSpec[]` you can hand to `spreadsheetLayout` - so value-driven,
|
|
66
|
-
*
|
|
66
|
+
* Declarative spanning runs on the SAME real colspan/rowspan merge engine
|
|
67
67
|
* instead of a second code path. Recompute after sort/filter (indexes are
|
|
68
68
|
* display-row indexes). A common pattern is "merge runs of equal values":
|
|
69
69
|
*
|
package/dist/spreadsheet.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
/**
|
|
23
23
|
* Turn declarative per-column `colSpan` / `rowSpan` callbacks into a
|
|
24
24
|
* `MergeSpec[]` you can hand to `spreadsheetLayout` - so value-driven,
|
|
25
|
-
*
|
|
25
|
+
* Declarative spanning runs on the SAME real colspan/rowspan merge engine
|
|
26
26
|
* instead of a second code path. Recompute after sort/filter (indexes are
|
|
27
27
|
* display-row indexes). A common pattern is "merge runs of equal values":
|
|
28
28
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svgrid/grid",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Svelte 5-native data grid and data table. Headless-first engine with virtual scrolling for 100k+ rows, Excel-style filtering, inline editing, grouping, tree data, pivot, and server-side data. Drop-in <SvGrid> component. TypeScript, SSR-ready.",
|
|
5
5
|
"author": "jQWidgets <boikom@jqwidgets.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -116,15 +116,6 @@
|
|
|
116
116
|
"themes",
|
|
117
117
|
"CHANGELOG.md"
|
|
118
118
|
],
|
|
119
|
-
"scripts": {
|
|
120
|
-
"clean": "rimraf ./build && rimraf ./dist",
|
|
121
|
-
"test:eslint": "eslint ./src",
|
|
122
|
-
"test:types": "svelte-check --tsconfig ./tsconfig.json",
|
|
123
|
-
"test:lib": "vitest run --coverage",
|
|
124
|
-
"test:build": "publint --strict",
|
|
125
|
-
"build": "svelte-package --input ./src --output ./dist && node ../../tools/strip-dist-tests.mjs ./dist && node ../../tools/add-dist-extensions.mjs ./dist && node ./scripts/build-cdn.mjs && node ./scripts/build-themes.mjs",
|
|
126
|
-
"lint": "eslint ./src"
|
|
127
|
-
},
|
|
128
119
|
"devDependencies": {
|
|
129
120
|
"@sveltejs/package": "^2.5.7",
|
|
130
121
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
@@ -141,5 +132,14 @@
|
|
|
141
132
|
},
|
|
142
133
|
"dependencies": {
|
|
143
134
|
"esm-env": "^1.2.2"
|
|
135
|
+
},
|
|
136
|
+
"scripts": {
|
|
137
|
+
"clean": "rimraf ./build && rimraf ./dist",
|
|
138
|
+
"test:eslint": "eslint ./src",
|
|
139
|
+
"test:types": "svelte-check --tsconfig ./tsconfig.json",
|
|
140
|
+
"test:lib": "vitest run --coverage",
|
|
141
|
+
"test:build": "publint --strict",
|
|
142
|
+
"build": "svelte-package --input ./src --output ./dist && node ../../tools/strip-dist-tests.mjs ./dist && node ../../tools/add-dist-extensions.mjs ./dist && node ./scripts/build-cdn.mjs && node ./scripts/build-themes.mjs",
|
|
143
|
+
"lint": "eslint ./src"
|
|
144
144
|
}
|
|
145
|
-
}
|
|
145
|
+
}
|