@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,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typing a decimal into an `editorType: 'number'` cell.
|
|
3
|
+
*
|
|
4
|
+
* `<input type="number">` applies the HTML value-sanitization algorithm: the
|
|
5
|
+
* element only reports a value it can parse as a valid floating-point number.
|
|
6
|
+
* Every intermediate state on the way to "12.5" - "12." - is not one, so the
|
|
7
|
+
* element reports "" and the keystroke vanishes. The same applies to a lone
|
|
8
|
+
* "-" on the way to a negative, and to "1e" on the way to "1e3".
|
|
9
|
+
*
|
|
10
|
+
* jsdom implements that sanitization, so this is reproducible here rather than
|
|
11
|
+
* only in a browser.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, expect, it } from 'vitest'
|
|
14
|
+
import {
|
|
15
|
+
getCellEditorInputType,
|
|
16
|
+
getEditorInputType,
|
|
17
|
+
isNumericEditorInput,
|
|
18
|
+
} from './SvGrid.helpers'
|
|
19
|
+
|
|
20
|
+
describe('what a bare number input does to intermediate values', () => {
|
|
21
|
+
// This is the browser behaviour the editor has to work around, pinned so the
|
|
22
|
+
// reasoning below cannot quietly stop being true.
|
|
23
|
+
const sanitized = (raw: string) => {
|
|
24
|
+
const el = document.createElement('input')
|
|
25
|
+
el.type = 'number'
|
|
26
|
+
el.value = raw
|
|
27
|
+
return el.value
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
it('throws away a trailing decimal point', () => {
|
|
31
|
+
expect(sanitized('12.')).toBe('')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('throws away a lone minus sign', () => {
|
|
35
|
+
expect(sanitized('-')).toBe('')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('throws away a half-typed exponent', () => {
|
|
39
|
+
expect(sanitized('1e')).toBe('')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('keeps values that are already valid', () => {
|
|
43
|
+
expect(sanitized('12.5')).toBe('12.5')
|
|
44
|
+
expect(sanitized('-3')).toBe('-3')
|
|
45
|
+
expect(sanitized('1e3')).toBe('1e3')
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('getCellEditorInputType', () => {
|
|
50
|
+
it('does NOT hand the number editor a type=number input', () => {
|
|
51
|
+
// The whole point: a text input keeps the raw keystrokes, and the value is
|
|
52
|
+
// coerced at commit by parseEditorValue - which the editor already did.
|
|
53
|
+
expect(getCellEditorInputType('number')).toBe('text')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('leaves every other editor on its native input type', () => {
|
|
57
|
+
for (const t of ['text', 'date', 'datetime', 'time', 'password', 'color'] as const) {
|
|
58
|
+
expect(getCellEditorInputType(t), t).toBe(getEditorInputType(t))
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('does not change the FILTER inputs, which share the other helper', () => {
|
|
63
|
+
// The filter row and filter menu still ask getEditorInputType. They have
|
|
64
|
+
// the same defect, but changing them is a separate decision - this pins
|
|
65
|
+
// that they were not swept along by accident.
|
|
66
|
+
expect(getEditorInputType('number')).toBe('number')
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('isNumericEditorInput', () => {
|
|
71
|
+
it('accepts every state on the way to a number', () => {
|
|
72
|
+
// Rejecting these is the exact bug this replaces.
|
|
73
|
+
for (const v of ['', '-', '12', '12.', '12.5', '-3', '.5', '1e', '1e3', '1e-3']) {
|
|
74
|
+
expect(isNumericEditorInput(v), JSON.stringify(v)).toBe(true)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('rejects what can never become a number', () => {
|
|
79
|
+
// The guarantee type="number" used to provide, now that it is gone.
|
|
80
|
+
for (const v of ['abc', '12x', '12..5', '--3', '1 2', '12,5']) {
|
|
81
|
+
expect(isNumericEditorInput(v), JSON.stringify(v)).toBe(false)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end: typing a decimal into an `editorType: 'number'` cell keeps every
|
|
3
|
+
* keystroke, and the value still commits as a NUMBER.
|
|
4
|
+
*
|
|
5
|
+
* The unit half of this lives in `number-editor-literal.test.ts` - the browser
|
|
6
|
+
* sanitization that caused it, and the helpers that route around it. This
|
|
7
|
+
* drives the real editor, which is what actually regressed for users: typing
|
|
8
|
+
* "12.5" produced 125, because the "." was dropped and the digits ran together.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
11
|
+
import { mount, unmount } from 'svelte'
|
|
12
|
+
import SvGrid from './SvGrid.svelte'
|
|
13
|
+
import { createCoreRowModel, tableFeatures } from './index'
|
|
14
|
+
import type { ColumnDef, SvGridApi } from './index'
|
|
15
|
+
|
|
16
|
+
type Row = { id: number; label: string; amount: number }
|
|
17
|
+
const features = tableFeatures({})
|
|
18
|
+
const cols: ColumnDef<typeof features, Row>[] = [
|
|
19
|
+
{ field: 'label', header: 'Label', width: 160 },
|
|
20
|
+
{ field: 'amount', header: 'Amount', width: 140, editorType: 'number' },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
function mountGrid() {
|
|
24
|
+
return new Promise<{
|
|
25
|
+
api: SvGridApi<typeof features, Row>
|
|
26
|
+
target: HTMLElement
|
|
27
|
+
changes: Array<{ newValue: unknown }>
|
|
28
|
+
destroy: () => void
|
|
29
|
+
}>((res, rej) => {
|
|
30
|
+
const target = document.createElement('div')
|
|
31
|
+
document.body.appendChild(target)
|
|
32
|
+
const changes: Array<{ newValue: unknown }> = []
|
|
33
|
+
const app = mount(SvGrid, {
|
|
34
|
+
target,
|
|
35
|
+
props: {
|
|
36
|
+
data: [{ id: 1, label: 'One', amount: 5 }],
|
|
37
|
+
columns: cols,
|
|
38
|
+
features,
|
|
39
|
+
_rowModels: { coreRowModel: createCoreRowModel() },
|
|
40
|
+
getRowId: (r: Row) => String(r.id),
|
|
41
|
+
containerHeight: 200,
|
|
42
|
+
virtualization: false,
|
|
43
|
+
editable: true,
|
|
44
|
+
onCellValueChange: (e: { newValue: unknown }) => changes.push(e),
|
|
45
|
+
onApiReady(api: SvGridApi<typeof features, Row>) {
|
|
46
|
+
res({ api, target, changes, destroy: () => { unmount(app); target.remove() } })
|
|
47
|
+
},
|
|
48
|
+
} as any,
|
|
49
|
+
})
|
|
50
|
+
queueMicrotask(() => { if (!target.querySelector('[role="grid"]')) rej(new Error('no grid')) })
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Start editing the amount cell and return its input, once the lazy chunk lands. */
|
|
55
|
+
async function editAmount(api: SvGridApi<typeof features, Row>, target: HTMLElement) {
|
|
56
|
+
api.startEditing(0, 'amount')
|
|
57
|
+
await vi.waitFor(() => {
|
|
58
|
+
expect(target.querySelector('.sv-grid-cell-editing input')).not.toBeNull()
|
|
59
|
+
})
|
|
60
|
+
return target.querySelector('.sv-grid-cell-editing input') as HTMLInputElement
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Type `text` one character at a time, as a user does. */
|
|
64
|
+
function typeInto(input: HTMLInputElement, text: string) {
|
|
65
|
+
let sofar = ''
|
|
66
|
+
for (const ch of text) {
|
|
67
|
+
sofar += ch
|
|
68
|
+
input.value = sofar
|
|
69
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
70
|
+
// Read back what survived - the element may have rewritten it.
|
|
71
|
+
sofar = input.value
|
|
72
|
+
}
|
|
73
|
+
return input.value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('number editor - decimals survive being typed', () => {
|
|
77
|
+
it('keeps the trailing decimal point mid-entry', async () => {
|
|
78
|
+
const { api, target, destroy } = await mountGrid()
|
|
79
|
+
try {
|
|
80
|
+
const input = await editAmount(api, target)
|
|
81
|
+
// The bug: with type="number" this read back "" and the dot was gone.
|
|
82
|
+
expect(typeInto(input, '12.')).toBe('12.')
|
|
83
|
+
} finally {
|
|
84
|
+
destroy()
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('commits 12.5 as the number 12.5, not 125', async () => {
|
|
89
|
+
const { api, target, changes, destroy } = await mountGrid()
|
|
90
|
+
try {
|
|
91
|
+
const input = await editAmount(api, target)
|
|
92
|
+
typeInto(input, '12.5')
|
|
93
|
+
input.dispatchEvent(new Event('blur', { bubbles: true }))
|
|
94
|
+
|
|
95
|
+
await vi.waitFor(() => expect(changes.length).toBeGreaterThan(0))
|
|
96
|
+
expect(changes.at(-1)!.newValue).toBe(12.5)
|
|
97
|
+
expect(typeof changes.at(-1)!.newValue).toBe('number')
|
|
98
|
+
} finally {
|
|
99
|
+
destroy()
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('keeps a lone minus sign on the way to a negative', async () => {
|
|
104
|
+
const { api, target, changes, destroy } = await mountGrid()
|
|
105
|
+
try {
|
|
106
|
+
const input = await editAmount(api, target)
|
|
107
|
+
expect(typeInto(input, '-')).toBe('-')
|
|
108
|
+
typeInto(input, '-4.25')
|
|
109
|
+
input.dispatchEvent(new Event('blur', { bubbles: true }))
|
|
110
|
+
|
|
111
|
+
await vi.waitFor(() => expect(changes.length).toBeGreaterThan(0))
|
|
112
|
+
expect(changes.at(-1)!.newValue).toBe(-4.25)
|
|
113
|
+
} finally {
|
|
114
|
+
destroy()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('carries the mobile decimal keypad hint', async () => {
|
|
119
|
+
// Losing type="number" also loses the numeric soft keyboard, which is what
|
|
120
|
+
// inputmode restores.
|
|
121
|
+
const { api, target, destroy } = await mountGrid()
|
|
122
|
+
try {
|
|
123
|
+
const input = await editAmount(api, target)
|
|
124
|
+
expect(input.getAttribute('type')).toBe('text')
|
|
125
|
+
expect(input.getAttribute('inputmode')).toBe('decimal')
|
|
126
|
+
} finally {
|
|
127
|
+
destroy()
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('refuses characters that can never be part of a number', async () => {
|
|
132
|
+
// type="number" used to block these; a text input does not, so the editor
|
|
133
|
+
// has to. Without the guard "12x" would reach the commit and coerce to null.
|
|
134
|
+
const { api, target, destroy } = await mountGrid()
|
|
135
|
+
try {
|
|
136
|
+
const input = await editAmount(api, target)
|
|
137
|
+
typeInto(input, '12')
|
|
138
|
+
input.value = '12x'
|
|
139
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
140
|
+
expect(input.value).toBe('12')
|
|
141
|
+
} finally {
|
|
142
|
+
destroy()
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('leaves a text column alone', async () => {
|
|
147
|
+
const { api, target, destroy } = await mountGrid()
|
|
148
|
+
try {
|
|
149
|
+
api.startEditing(0, 'label')
|
|
150
|
+
await vi.waitFor(() => {
|
|
151
|
+
expect(target.querySelector('.sv-grid-cell-editing input')).not.toBeNull()
|
|
152
|
+
})
|
|
153
|
+
const input = target.querySelector('.sv-grid-cell-editing input') as HTMLInputElement
|
|
154
|
+
expect(input.getAttribute('inputmode')).toBeNull()
|
|
155
|
+
expect(typeInto(input, 'hello.')).toBe('hello.')
|
|
156
|
+
} finally {
|
|
157
|
+
destroy()
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
})
|
package/src/overlays.test.ts
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component tests for the overlay trio: SvModal (dialog semantics, Escape /
|
|
3
|
-
* backdrop close), SvPopover (click open + outside close) and SvTooltip
|
|
4
|
-
* (focus-triggered, aria-describedby wiring).
|
|
5
|
-
*/
|
|
6
|
-
import { describe, expect, it, vi, afterEach } from 'vitest'
|
|
7
|
-
import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
|
|
8
|
-
import SvModal from './SvModal.svelte'
|
|
9
|
-
import SvPopover from './SvPopover.svelte'
|
|
10
|
-
import SvTooltip from './SvTooltip.svelte'
|
|
11
|
-
|
|
12
|
-
const body = createRawSnippet(() => ({ render: () => `<p class="body">hello</p>` }))
|
|
13
|
-
const anchorBtn = createRawSnippet(() => ({ render: () => `<button class="anchor">open</button>` }))
|
|
14
|
-
|
|
15
|
-
function mountC(Comp: any, props: Record<string, unknown>) {
|
|
16
|
-
const target = document.createElement('div')
|
|
17
|
-
document.body.appendChild(target)
|
|
18
|
-
const app = mount(Comp, { target, props: props as any })
|
|
19
|
-
flushSync()
|
|
20
|
-
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
21
|
-
}
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
document.querySelectorAll('.sv-modal__backdrop, .sv-pop__panel, .sv-tip').forEach((n) => n.remove())
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
describe('SvModal', () => {
|
|
27
|
-
it('renders a labelled modal dialog when open', () => {
|
|
28
|
-
const { destroy } = mountC(SvModal, { open: true, title: 'Edit', children: body })
|
|
29
|
-
try {
|
|
30
|
-
const dialog = document.querySelector<HTMLElement>('.sv-modal')!
|
|
31
|
-
expect(dialog.getAttribute('role')).toBe('dialog')
|
|
32
|
-
expect(dialog.getAttribute('aria-modal')).toBe('true')
|
|
33
|
-
const titleId = dialog.getAttribute('aria-labelledby')!
|
|
34
|
-
expect(document.getElementById(titleId)!.textContent).toBe('Edit')
|
|
35
|
-
expect(document.querySelector('.sv-modal .body')!.textContent).toBe('hello')
|
|
36
|
-
} finally { destroy() }
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('Escape and the close button both fire onClose', () => {
|
|
40
|
-
let closed = 0
|
|
41
|
-
const { destroy } = mountC(SvModal, { open: true, title: 'X', onClose: () => (closed++), children: body })
|
|
42
|
-
try {
|
|
43
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
44
|
-
flushSync()
|
|
45
|
-
expect(closed).toBe(1)
|
|
46
|
-
} finally { destroy() }
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
describe('SvPopover', () => {
|
|
51
|
-
it('opens the panel on anchor click and closes on outside pointerdown', () => {
|
|
52
|
-
const { target, destroy } = mountC(SvPopover, { anchor: anchorBtn, children: body })
|
|
53
|
-
try {
|
|
54
|
-
expect(document.querySelector('.sv-pop__panel')).toBeNull()
|
|
55
|
-
target.querySelector<HTMLElement>('.anchor')!.click()
|
|
56
|
-
flushSync()
|
|
57
|
-
const panel = document.querySelector<HTMLElement>('.sv-pop__panel')!
|
|
58
|
-
expect(panel).not.toBeNull()
|
|
59
|
-
expect(panel.getAttribute('role')).toBe('dialog')
|
|
60
|
-
// Outside click closes.
|
|
61
|
-
document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
|
|
62
|
-
flushSync()
|
|
63
|
-
expect(document.querySelector('.sv-pop__panel')).toBeNull()
|
|
64
|
-
} finally { destroy() }
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe('SvTooltip', () => {
|
|
69
|
-
it('shows after the delay on focus and wires aria-describedby', () => {
|
|
70
|
-
vi.useFakeTimers()
|
|
71
|
-
try {
|
|
72
|
-
const child = createRawSnippet(() => ({ render: () => `<button class="t">i</button>` }))
|
|
73
|
-
const { target, destroy } = mountC(SvTooltip, { text: 'Tip text', delay: 200, children: child })
|
|
74
|
-
const anchor = target.querySelector<HTMLElement>('.sv-tip__anchor')!
|
|
75
|
-
anchor.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
|
|
76
|
-
vi.advanceTimersByTime(210)
|
|
77
|
-
flushSync()
|
|
78
|
-
const tip = document.querySelector<HTMLElement>('.sv-tip')!
|
|
79
|
-
expect(tip).not.toBeNull()
|
|
80
|
-
expect(tip.getAttribute('role')).toBe('tooltip')
|
|
81
|
-
expect(anchor.getAttribute('aria-describedby')).toBe(tip.id)
|
|
82
|
-
// The wrapper span is never itself focusable, so AT users only ever land
|
|
83
|
-
// on the real interactive child below - it must carry the description
|
|
84
|
-
// too, or it's never announced on focus (see SvTooltip.svelte effect).
|
|
85
|
-
const focusEl = target.querySelector<HTMLElement>('.t')!
|
|
86
|
-
expect(focusEl.getAttribute('aria-describedby')).toBe(tip.id)
|
|
87
|
-
destroy()
|
|
88
|
-
} finally { vi.useRealTimers() }
|
|
89
|
-
})
|
|
90
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Component tests for the overlay trio: SvModal (dialog semantics, Escape /
|
|
3
|
+
* backdrop close), SvPopover (click open + outside close) and SvTooltip
|
|
4
|
+
* (focus-triggered, aria-describedby wiring).
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it, vi, afterEach } from 'vitest'
|
|
7
|
+
import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
|
|
8
|
+
import SvModal from './SvModal.svelte'
|
|
9
|
+
import SvPopover from './SvPopover.svelte'
|
|
10
|
+
import SvTooltip from './SvTooltip.svelte'
|
|
11
|
+
|
|
12
|
+
const body = createRawSnippet(() => ({ render: () => `<p class="body">hello</p>` }))
|
|
13
|
+
const anchorBtn = createRawSnippet(() => ({ render: () => `<button class="anchor">open</button>` }))
|
|
14
|
+
|
|
15
|
+
function mountC(Comp: any, props: Record<string, unknown>) {
|
|
16
|
+
const target = document.createElement('div')
|
|
17
|
+
document.body.appendChild(target)
|
|
18
|
+
const app = mount(Comp, { target, props: props as any })
|
|
19
|
+
flushSync()
|
|
20
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
21
|
+
}
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
document.querySelectorAll('.sv-modal__backdrop, .sv-pop__panel, .sv-tip').forEach((n) => n.remove())
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('SvModal', () => {
|
|
27
|
+
it('renders a labelled modal dialog when open', () => {
|
|
28
|
+
const { destroy } = mountC(SvModal, { open: true, title: 'Edit', children: body })
|
|
29
|
+
try {
|
|
30
|
+
const dialog = document.querySelector<HTMLElement>('.sv-modal')!
|
|
31
|
+
expect(dialog.getAttribute('role')).toBe('dialog')
|
|
32
|
+
expect(dialog.getAttribute('aria-modal')).toBe('true')
|
|
33
|
+
const titleId = dialog.getAttribute('aria-labelledby')!
|
|
34
|
+
expect(document.getElementById(titleId)!.textContent).toBe('Edit')
|
|
35
|
+
expect(document.querySelector('.sv-modal .body')!.textContent).toBe('hello')
|
|
36
|
+
} finally { destroy() }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Escape and the close button both fire onClose', () => {
|
|
40
|
+
let closed = 0
|
|
41
|
+
const { destroy } = mountC(SvModal, { open: true, title: 'X', onClose: () => (closed++), children: body })
|
|
42
|
+
try {
|
|
43
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
44
|
+
flushSync()
|
|
45
|
+
expect(closed).toBe(1)
|
|
46
|
+
} finally { destroy() }
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('SvPopover', () => {
|
|
51
|
+
it('opens the panel on anchor click and closes on outside pointerdown', () => {
|
|
52
|
+
const { target, destroy } = mountC(SvPopover, { anchor: anchorBtn, children: body })
|
|
53
|
+
try {
|
|
54
|
+
expect(document.querySelector('.sv-pop__panel')).toBeNull()
|
|
55
|
+
target.querySelector<HTMLElement>('.anchor')!.click()
|
|
56
|
+
flushSync()
|
|
57
|
+
const panel = document.querySelector<HTMLElement>('.sv-pop__panel')!
|
|
58
|
+
expect(panel).not.toBeNull()
|
|
59
|
+
expect(panel.getAttribute('role')).toBe('dialog')
|
|
60
|
+
// Outside click closes.
|
|
61
|
+
document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
|
|
62
|
+
flushSync()
|
|
63
|
+
expect(document.querySelector('.sv-pop__panel')).toBeNull()
|
|
64
|
+
} finally { destroy() }
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('SvTooltip', () => {
|
|
69
|
+
it('shows after the delay on focus and wires aria-describedby', () => {
|
|
70
|
+
vi.useFakeTimers()
|
|
71
|
+
try {
|
|
72
|
+
const child = createRawSnippet(() => ({ render: () => `<button class="t">i</button>` }))
|
|
73
|
+
const { target, destroy } = mountC(SvTooltip, { text: 'Tip text', delay: 200, children: child })
|
|
74
|
+
const anchor = target.querySelector<HTMLElement>('.sv-tip__anchor')!
|
|
75
|
+
anchor.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
|
|
76
|
+
vi.advanceTimersByTime(210)
|
|
77
|
+
flushSync()
|
|
78
|
+
const tip = document.querySelector<HTMLElement>('.sv-tip')!
|
|
79
|
+
expect(tip).not.toBeNull()
|
|
80
|
+
expect(tip.getAttribute('role')).toBe('tooltip')
|
|
81
|
+
expect(anchor.getAttribute('aria-describedby')).toBe(tip.id)
|
|
82
|
+
// The wrapper span is never itself focusable, so AT users only ever land
|
|
83
|
+
// on the real interactive child below - it must carry the description
|
|
84
|
+
// too, or it's never announced on focus (see SvTooltip.svelte effect).
|
|
85
|
+
const focusEl = target.querySelector<HTMLElement>('.t')!
|
|
86
|
+
expect(focusEl.getAttribute('aria-describedby')).toBe(tip.id)
|
|
87
|
+
destroy()
|
|
88
|
+
} finally { vi.useRealTimers() }
|
|
89
|
+
})
|
|
90
|
+
})
|