@svgrid/grid 1.2.21 → 1.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/GridFooter.svelte +34 -20
- package/dist/GridFooter.svelte.d.ts +8 -0
- package/dist/SvAutoComplete.svelte +100 -0
- package/dist/SvAutoComplete.svelte.d.ts +16 -0
- package/dist/SvButton.svelte +97 -0
- package/dist/SvButton.svelte.d.ts +23 -0
- package/dist/SvCalendar.svelte +669 -0
- package/dist/SvCalendar.svelte.d.ts +70 -0
- package/dist/SvCheckBox.svelte +81 -0
- package/dist/SvCheckBox.svelte.d.ts +19 -0
- package/dist/SvColorInput.svelte +187 -0
- package/dist/SvColorInput.svelte.d.ts +15 -0
- package/dist/SvComboBox.svelte +130 -0
- package/dist/SvComboBox.svelte.d.ts +15 -0
- package/dist/SvCountryInput.svelte +123 -0
- package/dist/SvCountryInput.svelte.d.ts +14 -0
- package/dist/SvDateTimePicker.svelte +322 -0
- package/dist/SvDateTimePicker.svelte.d.ts +37 -0
- package/dist/SvDropDownList.svelte +143 -0
- package/dist/SvDropDownList.svelte.d.ts +15 -0
- package/dist/SvForm.svelte +147 -0
- package/dist/SvForm.svelte.d.ts +29 -0
- package/dist/SvGauge.svelte +99 -0
- package/dist/SvGauge.svelte.d.ts +31 -0
- package/dist/SvGrid.controller.svelte.d.ts +16 -2
- package/dist/SvGrid.controller.svelte.js +63 -8
- package/dist/SvGrid.css +84 -31
- package/dist/SvGrid.helpers.d.ts +4 -0
- package/dist/SvGrid.helpers.js +40 -12
- package/dist/SvGrid.svelte +84 -5
- package/dist/SvGrid.types.d.ts +36 -0
- package/dist/SvGridDropdown.svelte +16 -30
- package/dist/SvGridDropdown.svelte.d.ts +4 -0
- package/dist/SvListBox.svelte +122 -0
- package/dist/SvListBox.svelte.d.ts +19 -0
- package/dist/SvMaskedInput.svelte +0 -0
- package/dist/SvMaskedInput.svelte.d.ts +15 -0
- package/dist/SvNumberInput.svelte +0 -0
- package/dist/SvNumberInput.svelte.d.ts +22 -0
- package/dist/SvPasswordInput.svelte +110 -0
- package/dist/SvPasswordInput.svelte.d.ts +22 -0
- package/dist/SvPhoneInput.svelte +0 -0
- package/dist/SvPhoneInput.svelte.d.ts +19 -0
- package/dist/SvRadioGroup.svelte +108 -0
- package/dist/SvRadioGroup.svelte.d.ts +23 -0
- package/dist/SvRating.svelte +111 -0
- package/dist/SvRating.svelte.d.ts +20 -0
- package/dist/SvRepeatButton.svelte +89 -0
- package/dist/SvRepeatButton.svelte.d.ts +21 -0
- package/dist/SvSlider.svelte +182 -0
- package/dist/SvSlider.svelte.d.ts +25 -0
- package/dist/SvSwitchButton.svelte +94 -0
- package/dist/SvSwitchButton.svelte.d.ts +19 -0
- package/dist/SvTabs.svelte +122 -0
- package/dist/SvTabs.svelte.d.ts +26 -0
- package/dist/SvTagsInput.svelte +81 -0
- package/dist/SvTagsInput.svelte.d.ts +19 -0
- package/dist/SvTimePicker.svelte +359 -0
- package/dist/SvTimePicker.svelte.d.ts +27 -0
- package/dist/SvToggleButton.svelte +57 -0
- package/dist/SvToggleButton.svelte.d.ts +16 -0
- package/dist/SvTree.svelte +210 -0
- package/dist/SvTree.svelte.d.ts +29 -0
- package/dist/build-api.js +78 -1
- package/dist/cdn/svgrid.js +10022 -5904
- package/dist/cdn/svgrid.svelte-external.js +8531 -4429
- package/dist/cell-render.d.ts +4 -0
- package/dist/cell-render.js +35 -1
- package/dist/clipboard.js +19 -1
- package/dist/collaboration.d.ts +2 -0
- package/dist/collaboration.js +4 -0
- package/dist/core.d.ts +22 -1
- package/dist/countries.d.ts +15 -0
- package/dist/countries.js +61 -0
- package/dist/datetime/date-core.d.ts +88 -0
- package/dist/datetime/date-core.js +165 -0
- package/dist/datetime/date-format.d.ts +39 -0
- package/dist/datetime/date-format.js +371 -0
- package/dist/datetime/date-restrict.d.ts +23 -0
- package/dist/datetime/date-restrict.js +50 -0
- package/dist/datetime/date-selection.d.ts +25 -0
- package/dist/datetime/date-selection.js +94 -0
- package/dist/datetime/mask.d.ts +21 -0
- package/dist/datetime/mask.js +90 -0
- package/dist/editing.d.ts +1 -1
- package/dist/editing.js +7 -0
- package/dist/editors/cell-editors.d.ts +2 -2
- package/dist/editors/cell-editors.js +3 -1
- package/dist/export-format.d.ts +178 -0
- package/dist/export-format.js +419 -0
- package/dist/hyperformula-adapter.d.ts +4 -0
- package/dist/hyperformula-adapter.js +15 -6
- package/dist/index.d.ts +36 -0
- package/dist/index.js +41 -0
- package/dist/list-option.d.ts +10 -0
- package/dist/list-option.js +7 -0
- package/dist/menus.js +18 -2
- package/dist/popover.d.ts +48 -0
- package/dist/popover.js +65 -0
- package/dist/row-resize.d.ts +4 -4
- package/dist/row-resize.js +8 -5
- package/dist/selection.d.ts +6 -0
- package/dist/selection.js +31 -5
- package/dist/server-data-source.d.ts +43 -0
- package/dist/server-data-source.js +100 -0
- package/dist/sparkline.js +8 -4
- package/dist/svgrid-wrapper.types.d.ts +26 -1
- package/package.json +6 -1
- package/src/GridFooter.svelte +34 -20
- package/src/SvAutoComplete.svelte +100 -0
- package/src/SvButton.svelte +97 -0
- package/src/SvCalendar.svelte +669 -0
- package/src/SvCalendar.test.ts +211 -0
- package/src/SvCheckBox.svelte +81 -0
- package/src/SvColorInput.svelte +187 -0
- package/src/SvComboBox.svelte +130 -0
- package/src/SvCountryInput.svelte +123 -0
- package/src/SvDateTimePicker.svelte +322 -0
- package/src/SvDateTimePicker.test.ts +136 -0
- package/src/SvDropDownList.svelte +143 -0
- package/src/SvForm.svelte +147 -0
- package/src/SvGauge.svelte +99 -0
- package/src/SvGrid.controller.svelte.ts +74 -8
- package/src/SvGrid.css +84 -31
- package/src/SvGrid.helpers.ts +41 -12
- package/src/SvGrid.svelte +84 -5
- package/src/SvGrid.types.ts +33 -0
- package/src/SvGridDropdown.svelte +16 -30
- package/src/SvListBox.svelte +122 -0
- package/src/SvMaskedInput.svelte +0 -0
- package/src/SvNumberInput.svelte +0 -0
- package/src/SvPasswordInput.svelte +110 -0
- package/src/SvPhoneInput.svelte +0 -0
- package/src/SvRadioGroup.svelte +108 -0
- package/src/SvRating.svelte +111 -0
- package/src/SvRepeatButton.svelte +89 -0
- package/src/SvSlider.svelte +182 -0
- package/src/SvSwitchButton.svelte +94 -0
- package/src/SvTabs.svelte +122 -0
- package/src/SvTagsInput.svelte +81 -0
- package/src/SvTimePicker.svelte +359 -0
- package/src/SvTimePicker.test.ts +98 -0
- package/src/SvToggleButton.svelte +57 -0
- package/src/SvTree.svelte +210 -0
- package/src/build-api.ts +115 -0
- package/src/cell-render.test.ts +38 -0
- package/src/cell-render.ts +43 -0
- package/src/clipboard.test.ts +18 -0
- package/src/clipboard.ts +24 -1
- package/src/collaboration.test.ts +30 -0
- package/src/collaboration.ts +6 -0
- package/src/core.ts +27 -3
- package/src/countries.ts +71 -0
- package/src/datetime/date-core.test.ts +217 -0
- package/src/datetime/date-core.ts +204 -0
- package/src/datetime/date-format.test.ts +121 -0
- package/src/datetime/date-format.ts +317 -0
- package/src/datetime/date-restrict.ts +60 -0
- package/src/datetime/date-selection.test.ts +129 -0
- package/src/datetime/date-selection.ts +137 -0
- package/src/datetime/mask.test.ts +36 -0
- package/src/datetime/mask.ts +84 -0
- package/src/editing.test.ts +22 -0
- package/src/editing.ts +7 -1
- package/src/editors/cell-editors.ts +7 -1
- package/src/export-data-api.test.ts +126 -0
- package/src/export-format.test.ts +107 -0
- package/src/export-format.ts +598 -0
- package/src/hyperformula-adapter.test.ts +35 -1
- package/src/hyperformula-adapter.ts +18 -6
- package/src/index.ts +71 -0
- package/src/list-option.ts +15 -0
- package/src/menus.ts +21 -2
- package/src/popover.ts +79 -0
- package/src/row-resize.test.ts +25 -0
- package/src/row-resize.ts +8 -5
- package/src/selection.test.ts +36 -4
- package/src/selection.ts +30 -9
- package/src/server-data-source.test.ts +201 -0
- package/src/server-data-source.ts +148 -0
- package/src/sparkline.test.ts +9 -0
- package/src/sparkline.ts +9 -4
- package/src/svgrid-wrapper.types.ts +28 -1
- package/src/svgrid.behavior.test.ts +27 -0
- package/src/svgrid.new-features.wrapper.test.ts +27 -1
- package/src/ui-buttons.test.ts +105 -0
- package/src/ui-composite.test.ts +89 -0
- package/src/ui-inputs.test.ts +92 -0
- package/src/ui-range.test.ts +61 -0
- package/src/ui-selection.test.ts +106 -0
package/dist/cell-render.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare function createCellRender<TFeatures extends TableFeatures = Table
|
|
|
6
6
|
computeRowClass: (row: Row<TData>, rowIndex: number) => string;
|
|
7
7
|
computeCellClass: (row: Row<TData>, column: Column<TData>) => string;
|
|
8
8
|
computeCellTooltip: (row: Row<TData>, column: Column<TData>) => string | null;
|
|
9
|
+
computeCellValidity: (row: Row<TData>, column: Column<TData>) => {
|
|
10
|
+
invalid: boolean;
|
|
11
|
+
message: string | null;
|
|
12
|
+
};
|
|
9
13
|
computeCellNote: (row: Row<TData>, column: Column<TData>) => string | null;
|
|
10
14
|
getColumnEditorOptions: (column: Column<TData>, row?: Row<TData> | null) => CellEditorOption[];
|
|
11
15
|
formatListCellValue: (column: Column<TData>, value: unknown, row?: Row<TData> | null) => string;
|
package/dist/cell-render.js
CHANGED
|
@@ -5,7 +5,7 @@ import { normalizeEditorOptions, } from "./index";
|
|
|
5
5
|
import "./sv-grid-scrollbar";
|
|
6
6
|
import { formatNumericWithConfig, getDateFormatter, resolveDatePattern, } from "./cell-formatting";
|
|
7
7
|
import { resolveCellFormat, } from "./conditional-formatting";
|
|
8
|
-
import { resolveClassList, getOptionLabel, asDate, } from "./SvGrid.helpers";
|
|
8
|
+
import { getCellKey, resolveClassList, getOptionLabel, asDate, } from "./SvGrid.helpers";
|
|
9
9
|
import { getPinnedCellValue, } from "./cell-values";
|
|
10
10
|
export function createCellRender(ctx) {
|
|
11
11
|
function cellConditionalFormat(row, column, value) {
|
|
@@ -69,6 +69,39 @@ export function createCellRender(ctx) {
|
|
|
69
69
|
}
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve per-cell validity via the column's declarative `validate` hook.
|
|
74
|
+
* Runs for every rendered cell so pre-existing bad data is flagged on load.
|
|
75
|
+
* Returns `{ invalid, message }` - `invalid` drives the red highlight class,
|
|
76
|
+
* `message` (when present) becomes the cell's tooltip.
|
|
77
|
+
*/
|
|
78
|
+
function computeCellValidity(row, column) {
|
|
79
|
+
const rule = column.columnDef.validate;
|
|
80
|
+
if (typeof rule !== "function")
|
|
81
|
+
return { invalid: false, message: null };
|
|
82
|
+
// Read the edit-aware value: inline edits land in `editedCellValues`
|
|
83
|
+
// (keyed rowId:colId) and are NOT written into the row's memoized value
|
|
84
|
+
// cache, so `getCellValueByColumnId` would return the stale pre-edit value
|
|
85
|
+
// and validation would never re-run after an edit. Consult the overlay
|
|
86
|
+
// first - same rule the display path (getCellDisplayValue) and summaries
|
|
87
|
+
// use - so a cell re-validates live as the user types.
|
|
88
|
+
const edited = ctx.editedCellValues ?? {};
|
|
89
|
+
const key = getCellKey(row.id, column.id);
|
|
90
|
+
const value = key in edited ? edited[key] : row.getCellValueByColumnId(column.id);
|
|
91
|
+
const out = rule({
|
|
92
|
+
value,
|
|
93
|
+
row: row.original,
|
|
94
|
+
rowIndex: row.index,
|
|
95
|
+
column,
|
|
96
|
+
});
|
|
97
|
+
// Valid: null / undefined / true. Invalid: false or a message string.
|
|
98
|
+
if (out == null || out === true)
|
|
99
|
+
return { invalid: false, message: null };
|
|
100
|
+
if (out === false)
|
|
101
|
+
return { invalid: true, message: null };
|
|
102
|
+
const msg = String(out);
|
|
103
|
+
return { invalid: true, message: msg.trim() ? msg : null };
|
|
104
|
+
}
|
|
72
105
|
/**
|
|
73
106
|
* Resolve a per-cell note (a longer comment / annotation). Notes
|
|
74
107
|
* come from the grid's `notes` prop - a `{ [rowId]: { [columnId]: string } }`
|
|
@@ -236,6 +269,7 @@ export function createCellRender(ctx) {
|
|
|
236
269
|
computeRowClass,
|
|
237
270
|
computeCellClass,
|
|
238
271
|
computeCellTooltip,
|
|
272
|
+
computeCellValidity,
|
|
239
273
|
computeCellNote,
|
|
240
274
|
getColumnEditorOptions,
|
|
241
275
|
formatListCellValue,
|
package/dist/clipboard.js
CHANGED
|
@@ -355,6 +355,7 @@ export function createClipboard(ctx) {
|
|
|
355
355
|
const endCol = Math.max(anchor.colIndex, focus.colIndex);
|
|
356
356
|
const next = ctx.internalData.slice();
|
|
357
357
|
let mutated = false;
|
|
358
|
+
const changes = [];
|
|
358
359
|
for (let r = startRow; r <= endRow; r += 1) {
|
|
359
360
|
const row = ctx.allRows[r];
|
|
360
361
|
if (!row || isGroupRow(row))
|
|
@@ -381,8 +382,18 @@ export function createClipboard(ctx) {
|
|
|
381
382
|
// else. parseEditorValue handles the per-type coercion.
|
|
382
383
|
const editorType = (column.columnDef.editorType ??
|
|
383
384
|
"text");
|
|
384
|
-
|
|
385
|
+
const field = column.columnDef.field;
|
|
386
|
+
const oldValue = updated[field];
|
|
387
|
+
const cleared = parseEditorValue(editorType, "");
|
|
388
|
+
updated[field] = cleared;
|
|
385
389
|
rowChanged = true;
|
|
390
|
+
changes.push({
|
|
391
|
+
rowIndex: dataIndex,
|
|
392
|
+
columnId: column.id,
|
|
393
|
+
oldValue,
|
|
394
|
+
newValue: cleared,
|
|
395
|
+
row: updated,
|
|
396
|
+
});
|
|
386
397
|
}
|
|
387
398
|
if (rowChanged) {
|
|
388
399
|
next[dataIndex] = updated;
|
|
@@ -392,6 +403,13 @@ export function createClipboard(ctx) {
|
|
|
392
403
|
if (mutated) {
|
|
393
404
|
ctx.internalData = next;
|
|
394
405
|
ctx.grid.store.setState((prev) => ({ ...prev }));
|
|
406
|
+
// Fire onCellValueChange per cleared cell - same as paste's writeCellRaw -
|
|
407
|
+
// so consumers (formula engines, autosave) recompute. Clear IS a value
|
|
408
|
+
// change; without this, a HyperFormula-backed grid wouldn't re-evaluate.
|
|
409
|
+
if (ctx.props.onCellValueChange) {
|
|
410
|
+
for (const ch of changes)
|
|
411
|
+
ctx.props.onCellValueChange(ch);
|
|
412
|
+
}
|
|
395
413
|
}
|
|
396
414
|
return mutated;
|
|
397
415
|
}
|
package/dist/collaboration.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export type CollabMessage = {
|
|
|
43
43
|
export type CollabTransport = {
|
|
44
44
|
post(msg: CollabMessage): void;
|
|
45
45
|
subscribe(handler: (msg: CollabMessage) => void): () => void;
|
|
46
|
+
/** Release any underlying resource (e.g. a BroadcastChannel). Optional. */
|
|
47
|
+
dispose?(): void;
|
|
46
48
|
};
|
|
47
49
|
export type Collaboration = {
|
|
48
50
|
/** Broadcast where this user's cursor is (or null when it leaves). */
|
package/dist/collaboration.js
CHANGED
|
@@ -14,6 +14,9 @@ export function broadcastChannelTransport(name) {
|
|
|
14
14
|
channel.addEventListener('message', listener);
|
|
15
15
|
return () => channel.removeEventListener('message', listener);
|
|
16
16
|
},
|
|
17
|
+
dispose() {
|
|
18
|
+
channel?.close();
|
|
19
|
+
},
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
export function createCollaboration(options) {
|
|
@@ -93,6 +96,7 @@ export function createCollaboration(options) {
|
|
|
93
96
|
unsubscribe();
|
|
94
97
|
if (pruneTimer)
|
|
95
98
|
clearInterval(pruneTimer);
|
|
99
|
+
transport.dispose?.(); // release the channel (BroadcastChannel etc.)
|
|
96
100
|
},
|
|
97
101
|
};
|
|
98
102
|
}
|
package/dist/core.d.ts
CHANGED
|
@@ -173,7 +173,7 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
173
173
|
* and `columnGroupShow: 'closed'` children show until the user expands it.
|
|
174
174
|
*/
|
|
175
175
|
openByDefault?: boolean;
|
|
176
|
-
editorType?: 'text' | 'number' | 'date' | 'datetime' | 'time' | 'password' | 'checkbox' | 'list' | 'chips' | 'select' | 'rich-select' | 'autocomplete' | 'textarea' | 'color' | 'rating';
|
|
176
|
+
editorType?: 'text' | 'number' | 'date' | 'datetime' | 'time' | 'date-native' | 'datetime-native' | 'time-native' | 'password' | 'checkbox' | 'list' | 'chips' | 'select' | 'rich-select' | 'autocomplete' | 'textarea' | 'color' | 'rating';
|
|
177
177
|
/**
|
|
178
178
|
* Custom in-cell editor. Receives the cell context PLUS a `commit(value)`
|
|
179
179
|
* and `cancel()` helper. Use when none of the built-in `editorType`s fit;
|
|
@@ -191,6 +191,27 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
|
|
|
191
191
|
* string skips the tooltip.
|
|
192
192
|
*/
|
|
193
193
|
tooltip?: string | ((ctx: CellContext<TData>) => string | null | undefined);
|
|
194
|
+
/**
|
|
195
|
+
* Declarative per-cell validation (Handsontable-style). Runs for EVERY
|
|
196
|
+
* rendered cell - including values already present in `data` on load, not
|
|
197
|
+
* just on edit - so bad data is flagged immediately. Invalid cells get the
|
|
198
|
+
* `sv-grid-cell-invalid` class (red highlight) and the returned message as
|
|
199
|
+
* their tooltip.
|
|
200
|
+
*
|
|
201
|
+
* Return value:
|
|
202
|
+
* - `null` / `undefined` / `true` → valid (no highlight)
|
|
203
|
+
* - `false` → invalid, no message
|
|
204
|
+
* - a non-empty `string` → invalid, string is the tooltip
|
|
205
|
+
*
|
|
206
|
+
* The value keeps rendering as-is (the grid does NOT roll it back); pair
|
|
207
|
+
* with `onCellValueChange` if you also want to reject the commit.
|
|
208
|
+
*/
|
|
209
|
+
validate?: (params: {
|
|
210
|
+
value: unknown;
|
|
211
|
+
row: TData;
|
|
212
|
+
rowIndex: number;
|
|
213
|
+
column: Column<TData>;
|
|
214
|
+
}) => string | boolean | null | undefined;
|
|
194
215
|
/**
|
|
195
216
|
* Gate editing per column or per cell.
|
|
196
217
|
*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* countries - a compact country reference for SvPhoneInput / SvCountryInput.
|
|
3
|
+
* ISO 3166-1 alpha-2 code, display name, E.164 dial code, and a flag emoji
|
|
4
|
+
* (derived from the code so no image assets are needed). This is a curated
|
|
5
|
+
* common-set; extend `COUNTRIES` for full coverage.
|
|
6
|
+
*/
|
|
7
|
+
export type Country = {
|
|
8
|
+
code: string;
|
|
9
|
+
name: string;
|
|
10
|
+
dial: string;
|
|
11
|
+
};
|
|
12
|
+
/** Regional-indicator flag emoji from an ISO alpha-2 code. */
|
|
13
|
+
export declare function flagEmoji(code: string): string;
|
|
14
|
+
export declare const COUNTRIES: Country[];
|
|
15
|
+
export declare const COUNTRY_BY_CODE: Map<string, Country>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Regional-indicator flag emoji from an ISO alpha-2 code. */
|
|
2
|
+
export function flagEmoji(code) {
|
|
3
|
+
return code
|
|
4
|
+
.toUpperCase()
|
|
5
|
+
.replace(/./g, (c) => String.fromCodePoint(127397 + c.charCodeAt(0)));
|
|
6
|
+
}
|
|
7
|
+
export const COUNTRIES = [
|
|
8
|
+
{ code: 'US', name: 'United States', dial: '+1' },
|
|
9
|
+
{ code: 'CA', name: 'Canada', dial: '+1' },
|
|
10
|
+
{ code: 'GB', name: 'United Kingdom', dial: '+44' },
|
|
11
|
+
{ code: 'IE', name: 'Ireland', dial: '+353' },
|
|
12
|
+
{ code: 'FR', name: 'France', dial: '+33' },
|
|
13
|
+
{ code: 'DE', name: 'Germany', dial: '+49' },
|
|
14
|
+
{ code: 'ES', name: 'Spain', dial: '+34' },
|
|
15
|
+
{ code: 'PT', name: 'Portugal', dial: '+351' },
|
|
16
|
+
{ code: 'IT', name: 'Italy', dial: '+39' },
|
|
17
|
+
{ code: 'NL', name: 'Netherlands', dial: '+31' },
|
|
18
|
+
{ code: 'BE', name: 'Belgium', dial: '+32' },
|
|
19
|
+
{ code: 'CH', name: 'Switzerland', dial: '+41' },
|
|
20
|
+
{ code: 'AT', name: 'Austria', dial: '+43' },
|
|
21
|
+
{ code: 'SE', name: 'Sweden', dial: '+46' },
|
|
22
|
+
{ code: 'NO', name: 'Norway', dial: '+47' },
|
|
23
|
+
{ code: 'DK', name: 'Denmark', dial: '+45' },
|
|
24
|
+
{ code: 'FI', name: 'Finland', dial: '+358' },
|
|
25
|
+
{ code: 'PL', name: 'Poland', dial: '+48' },
|
|
26
|
+
{ code: 'CZ', name: 'Czechia', dial: '+420' },
|
|
27
|
+
{ code: 'GR', name: 'Greece', dial: '+30' },
|
|
28
|
+
{ code: 'BG', name: 'Bulgaria', dial: '+359' },
|
|
29
|
+
{ code: 'RO', name: 'Romania', dial: '+40' },
|
|
30
|
+
{ code: 'UA', name: 'Ukraine', dial: '+380' },
|
|
31
|
+
{ code: 'RU', name: 'Russia', dial: '+7' },
|
|
32
|
+
{ code: 'TR', name: 'Turkey', dial: '+90' },
|
|
33
|
+
{ code: 'IL', name: 'Israel', dial: '+972' },
|
|
34
|
+
{ code: 'AE', name: 'United Arab Emirates', dial: '+971' },
|
|
35
|
+
{ code: 'SA', name: 'Saudi Arabia', dial: '+966' },
|
|
36
|
+
{ code: 'IN', name: 'India', dial: '+91' },
|
|
37
|
+
{ code: 'PK', name: 'Pakistan', dial: '+92' },
|
|
38
|
+
{ code: 'CN', name: 'China', dial: '+86' },
|
|
39
|
+
{ code: 'HK', name: 'Hong Kong', dial: '+852' },
|
|
40
|
+
{ code: 'JP', name: 'Japan', dial: '+81' },
|
|
41
|
+
{ code: 'KR', name: 'South Korea', dial: '+82' },
|
|
42
|
+
{ code: 'SG', name: 'Singapore', dial: '+65' },
|
|
43
|
+
{ code: 'MY', name: 'Malaysia', dial: '+60' },
|
|
44
|
+
{ code: 'TH', name: 'Thailand', dial: '+66' },
|
|
45
|
+
{ code: 'ID', name: 'Indonesia', dial: '+62' },
|
|
46
|
+
{ code: 'PH', name: 'Philippines', dial: '+63' },
|
|
47
|
+
{ code: 'VN', name: 'Vietnam', dial: '+84' },
|
|
48
|
+
{ code: 'AU', name: 'Australia', dial: '+61' },
|
|
49
|
+
{ code: 'NZ', name: 'New Zealand', dial: '+64' },
|
|
50
|
+
{ code: 'ZA', name: 'South Africa', dial: '+27' },
|
|
51
|
+
{ code: 'NG', name: 'Nigeria', dial: '+234' },
|
|
52
|
+
{ code: 'EG', name: 'Egypt', dial: '+20' },
|
|
53
|
+
{ code: 'KE', name: 'Kenya', dial: '+254' },
|
|
54
|
+
{ code: 'MA', name: 'Morocco', dial: '+212' },
|
|
55
|
+
{ code: 'BR', name: 'Brazil', dial: '+55' },
|
|
56
|
+
{ code: 'AR', name: 'Argentina', dial: '+54' },
|
|
57
|
+
{ code: 'CL', name: 'Chile', dial: '+56' },
|
|
58
|
+
{ code: 'CO', name: 'Colombia', dial: '+57' },
|
|
59
|
+
{ code: 'MX', name: 'Mexico', dial: '+52' },
|
|
60
|
+
];
|
|
61
|
+
export const COUNTRY_BY_CODE = new Map(COUNTRIES.map((c) => [c.code, c]));
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* date-core - framework-free date math shared by SvCalendar, SvTimePicker and
|
|
3
|
+
* SvDateTimePicker. No Svelte, no DOM: pure functions over the native `Date`
|
|
4
|
+
* so it is trivially unit- and mutation-testable and reusable standalone.
|
|
5
|
+
*
|
|
6
|
+
* Conventions:
|
|
7
|
+
* - A "day" is compared by local calendar date (year/month/date), ignoring the
|
|
8
|
+
* time-of-day, unless a helper says otherwise.
|
|
9
|
+
* - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
|
|
10
|
+
* `firstDayOfWeek`.
|
|
11
|
+
*/
|
|
12
|
+
export type DateLike = Date | number | string;
|
|
13
|
+
/** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
|
|
14
|
+
export declare function toDate(value: DateLike | null | undefined): Date | null;
|
|
15
|
+
/** A new Date at local midnight of the same calendar day (time stripped). */
|
|
16
|
+
export declare function startOfDay(d: Date): Date;
|
|
17
|
+
/** First day (midnight) of the month `d` falls in. */
|
|
18
|
+
export declare function startOfMonth(d: Date): Date;
|
|
19
|
+
/** Last day (midnight) of the month `d` falls in. */
|
|
20
|
+
export declare function endOfMonth(d: Date): Date;
|
|
21
|
+
/** Number of days in the month `d` falls in. */
|
|
22
|
+
export declare function daysInMonth(d: Date): number;
|
|
23
|
+
/**
|
|
24
|
+
* Start of the week containing `d`, honoring `firstDayOfWeek` (0-6). Returns a
|
|
25
|
+
* local-midnight Date on or before `d`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function startOfWeek(d: Date, firstDayOfWeek?: number): Date;
|
|
28
|
+
/** True when both dates are the same local calendar day. */
|
|
29
|
+
export declare function isSameDay(a: Date | null, b: Date | null): boolean;
|
|
30
|
+
/** True when both dates are in the same year+month. */
|
|
31
|
+
export declare function isSameMonth(a: Date | null, b: Date | null): boolean;
|
|
32
|
+
/** -1 / 0 / 1 comparison by calendar day (time-of-day ignored). */
|
|
33
|
+
export declare function compareDay(a: Date, b: Date): number;
|
|
34
|
+
/** Add `n` days (can be negative), returning a new Date. */
|
|
35
|
+
export declare function addDays(d: Date, n: number): Date;
|
|
36
|
+
/**
|
|
37
|
+
* Add `n` calendar months, clamping the day-of-month so 31 Jan + 1 month = 28/29
|
|
38
|
+
* Feb rather than spilling into March (matches how calendars paginate).
|
|
39
|
+
*/
|
|
40
|
+
export declare function addMonths(d: Date, n: number): Date;
|
|
41
|
+
/** Add `n` years, clamping Feb-29 to Feb-28 on non-leap targets. */
|
|
42
|
+
export declare function addYears(d: Date, n: number): Date;
|
|
43
|
+
/** Clamp `d` to the inclusive [min, max] range (either bound may be null). */
|
|
44
|
+
export declare function clampDate(d: Date, min: Date | null, max: Date | null): Date;
|
|
45
|
+
/**
|
|
46
|
+
* ISO 8601 week number (1-53) for `d`. Week 1 is the week containing the first
|
|
47
|
+
* Thursday of the year; weeks start Monday. Independent of `firstDayOfWeek`,
|
|
48
|
+
* matching Smart's `weekNumbers` column which is always ISO.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isoWeek(d: Date): number;
|
|
51
|
+
/**
|
|
52
|
+
* The [start, end] years of the decade block that `year` belongs to, as Smart
|
|
53
|
+
* renders it: a 12-cell page running from the decade start minus 1 to plus 10
|
|
54
|
+
* (one leading + one trailing year for context). We expose the canonical
|
|
55
|
+
* decade [Y0, Y0+9] and let the view pad.
|
|
56
|
+
*/
|
|
57
|
+
export declare function decadeRange(year: number): {
|
|
58
|
+
start: number;
|
|
59
|
+
end: number;
|
|
60
|
+
};
|
|
61
|
+
/** The [start, end] years of the century block (100-year) `year` belongs to. */
|
|
62
|
+
export declare function centuryRange(year: number): {
|
|
63
|
+
start: number;
|
|
64
|
+
end: number;
|
|
65
|
+
};
|
|
66
|
+
export type MonthMatrixCell = {
|
|
67
|
+
date: Date;
|
|
68
|
+
/** In the displayed month (vs. leading/trailing days of adjacent months). */
|
|
69
|
+
inMonth: boolean;
|
|
70
|
+
/** ISO week number of the row this cell sits in. */
|
|
71
|
+
week: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Build a month grid of `weeks` rows x 7 columns starting at `firstDayOfWeek`,
|
|
75
|
+
* padded with the trailing days of the previous month and leading days of the
|
|
76
|
+
* next so every row is full. `weeks` defaults to 6 (the max any month needs),
|
|
77
|
+
* matching Smart's fixed-height calendar.
|
|
78
|
+
*/
|
|
79
|
+
export declare function monthMatrix(monthDate: Date, firstDayOfWeek?: number, weeks?: number): MonthMatrixCell[][];
|
|
80
|
+
/**
|
|
81
|
+
* Ordered weekday indexes (0-6) for a header row starting at `firstDayOfWeek`.
|
|
82
|
+
* e.g. firstDayOfWeek=1 -> [1,2,3,4,5,6,0].
|
|
83
|
+
*/
|
|
84
|
+
export declare function weekdayOrder(firstDayOfWeek?: number): number[];
|
|
85
|
+
/** Merge a calendar day with a time-of-day taken from `time`, returning a new Date. */
|
|
86
|
+
export declare function withTime(day: Date, time: Date): Date;
|
|
87
|
+
/** Snap a minute value to the nearest lower multiple of `interval` (>=1). */
|
|
88
|
+
export declare function snapMinute(minute: number, interval: number): number;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* date-core - framework-free date math shared by SvCalendar, SvTimePicker and
|
|
3
|
+
* SvDateTimePicker. No Svelte, no DOM: pure functions over the native `Date`
|
|
4
|
+
* so it is trivially unit- and mutation-testable and reusable standalone.
|
|
5
|
+
*
|
|
6
|
+
* Conventions:
|
|
7
|
+
* - A "day" is compared by local calendar date (year/month/date), ignoring the
|
|
8
|
+
* time-of-day, unless a helper says otherwise.
|
|
9
|
+
* - `firstDayOfWeek` is 0-6 (0 = Sunday), matching `Date.getDay()` and Smart's
|
|
10
|
+
* `firstDayOfWeek`.
|
|
11
|
+
*/
|
|
12
|
+
/** Coerce a Date | epoch-ms | parseable string to a Date, or null if invalid. */
|
|
13
|
+
export function toDate(value) {
|
|
14
|
+
if (value == null)
|
|
15
|
+
return null;
|
|
16
|
+
const d = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
17
|
+
return isNaN(d.getTime()) ? null : d;
|
|
18
|
+
}
|
|
19
|
+
/** A new Date at local midnight of the same calendar day (time stripped). */
|
|
20
|
+
export function startOfDay(d) {
|
|
21
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
22
|
+
}
|
|
23
|
+
/** First day (midnight) of the month `d` falls in. */
|
|
24
|
+
export function startOfMonth(d) {
|
|
25
|
+
return new Date(d.getFullYear(), d.getMonth(), 1);
|
|
26
|
+
}
|
|
27
|
+
/** Last day (midnight) of the month `d` falls in. */
|
|
28
|
+
export function endOfMonth(d) {
|
|
29
|
+
return new Date(d.getFullYear(), d.getMonth() + 1, 0);
|
|
30
|
+
}
|
|
31
|
+
/** Number of days in the month `d` falls in. */
|
|
32
|
+
export function daysInMonth(d) {
|
|
33
|
+
return endOfMonth(d).getDate();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Start of the week containing `d`, honoring `firstDayOfWeek` (0-6). Returns a
|
|
37
|
+
* local-midnight Date on or before `d`.
|
|
38
|
+
*/
|
|
39
|
+
export function startOfWeek(d, firstDayOfWeek = 0) {
|
|
40
|
+
const start = startOfDay(d);
|
|
41
|
+
const diff = (start.getDay() - firstDayOfWeek + 7) % 7;
|
|
42
|
+
start.setDate(start.getDate() - diff);
|
|
43
|
+
return start;
|
|
44
|
+
}
|
|
45
|
+
/** True when both dates are the same local calendar day. */
|
|
46
|
+
export function isSameDay(a, b) {
|
|
47
|
+
if (!a || !b)
|
|
48
|
+
return false;
|
|
49
|
+
return (a.getFullYear() === b.getFullYear() &&
|
|
50
|
+
a.getMonth() === b.getMonth() &&
|
|
51
|
+
a.getDate() === b.getDate());
|
|
52
|
+
}
|
|
53
|
+
/** True when both dates are in the same year+month. */
|
|
54
|
+
export function isSameMonth(a, b) {
|
|
55
|
+
if (!a || !b)
|
|
56
|
+
return false;
|
|
57
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
|
|
58
|
+
}
|
|
59
|
+
/** -1 / 0 / 1 comparison by calendar day (time-of-day ignored). */
|
|
60
|
+
export function compareDay(a, b) {
|
|
61
|
+
const av = startOfDay(a).getTime();
|
|
62
|
+
const bv = startOfDay(b).getTime();
|
|
63
|
+
return av < bv ? -1 : av > bv ? 1 : 0;
|
|
64
|
+
}
|
|
65
|
+
/** Add `n` days (can be negative), returning a new Date. */
|
|
66
|
+
export function addDays(d, n) {
|
|
67
|
+
const r = new Date(d.getTime());
|
|
68
|
+
r.setDate(r.getDate() + n);
|
|
69
|
+
return r;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Add `n` calendar months, clamping the day-of-month so 31 Jan + 1 month = 28/29
|
|
73
|
+
* Feb rather than spilling into March (matches how calendars paginate).
|
|
74
|
+
*/
|
|
75
|
+
export function addMonths(d, n) {
|
|
76
|
+
const day = d.getDate();
|
|
77
|
+
const r = new Date(d.getFullYear(), d.getMonth() + n, 1, d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
|
|
78
|
+
r.setDate(Math.min(day, daysInMonth(r)));
|
|
79
|
+
return r;
|
|
80
|
+
}
|
|
81
|
+
/** Add `n` years, clamping Feb-29 to Feb-28 on non-leap targets. */
|
|
82
|
+
export function addYears(d, n) {
|
|
83
|
+
return addMonths(d, n * 12);
|
|
84
|
+
}
|
|
85
|
+
/** Clamp `d` to the inclusive [min, max] range (either bound may be null). */
|
|
86
|
+
export function clampDate(d, min, max) {
|
|
87
|
+
if (min && d.getTime() < min.getTime())
|
|
88
|
+
return new Date(min.getTime());
|
|
89
|
+
if (max && d.getTime() > max.getTime())
|
|
90
|
+
return new Date(max.getTime());
|
|
91
|
+
return d;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* ISO 8601 week number (1-53) for `d`. Week 1 is the week containing the first
|
|
95
|
+
* Thursday of the year; weeks start Monday. Independent of `firstDayOfWeek`,
|
|
96
|
+
* matching Smart's `weekNumbers` column which is always ISO.
|
|
97
|
+
*/
|
|
98
|
+
export function isoWeek(d) {
|
|
99
|
+
// Shift to the Thursday of this week, then count weeks from Jan 1.
|
|
100
|
+
const date = startOfDay(d);
|
|
101
|
+
const day = (date.getDay() + 6) % 7; // Mon=0..Sun=6
|
|
102
|
+
date.setDate(date.getDate() - day + 3); // Thursday of this week
|
|
103
|
+
const firstThursday = new Date(date.getFullYear(), 0, 4);
|
|
104
|
+
const firstDay = (firstThursday.getDay() + 6) % 7;
|
|
105
|
+
firstThursday.setDate(firstThursday.getDate() - firstDay + 3);
|
|
106
|
+
const diffMs = date.getTime() - firstThursday.getTime();
|
|
107
|
+
return 1 + Math.round(diffMs / (7 * 24 * 3600 * 1000));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The [start, end] years of the decade block that `year` belongs to, as Smart
|
|
111
|
+
* renders it: a 12-cell page running from the decade start minus 1 to plus 10
|
|
112
|
+
* (one leading + one trailing year for context). We expose the canonical
|
|
113
|
+
* decade [Y0, Y0+9] and let the view pad.
|
|
114
|
+
*/
|
|
115
|
+
export function decadeRange(year) {
|
|
116
|
+
const start = Math.floor(year / 10) * 10;
|
|
117
|
+
return { start, end: start + 9 };
|
|
118
|
+
}
|
|
119
|
+
/** The [start, end] years of the century block (100-year) `year` belongs to. */
|
|
120
|
+
export function centuryRange(year) {
|
|
121
|
+
const start = Math.floor(year / 100) * 100;
|
|
122
|
+
return { start, end: start + 99 };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a month grid of `weeks` rows x 7 columns starting at `firstDayOfWeek`,
|
|
126
|
+
* padded with the trailing days of the previous month and leading days of the
|
|
127
|
+
* next so every row is full. `weeks` defaults to 6 (the max any month needs),
|
|
128
|
+
* matching Smart's fixed-height calendar.
|
|
129
|
+
*/
|
|
130
|
+
export function monthMatrix(monthDate, firstDayOfWeek = 0, weeks = 6) {
|
|
131
|
+
const first = startOfMonth(monthDate);
|
|
132
|
+
const gridStart = startOfWeek(first, firstDayOfWeek);
|
|
133
|
+
const rows = [];
|
|
134
|
+
let cursor = gridStart;
|
|
135
|
+
for (let r = 0; r < weeks; r++) {
|
|
136
|
+
const row = [];
|
|
137
|
+
const weekNo = isoWeek(cursor);
|
|
138
|
+
for (let c = 0; c < 7; c++) {
|
|
139
|
+
row.push({
|
|
140
|
+
date: cursor,
|
|
141
|
+
inMonth: cursor.getMonth() === first.getMonth() && cursor.getFullYear() === first.getFullYear(),
|
|
142
|
+
week: weekNo,
|
|
143
|
+
});
|
|
144
|
+
cursor = addDays(cursor, 1);
|
|
145
|
+
}
|
|
146
|
+
rows.push(row);
|
|
147
|
+
}
|
|
148
|
+
return rows;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Ordered weekday indexes (0-6) for a header row starting at `firstDayOfWeek`.
|
|
152
|
+
* e.g. firstDayOfWeek=1 -> [1,2,3,4,5,6,0].
|
|
153
|
+
*/
|
|
154
|
+
export function weekdayOrder(firstDayOfWeek = 0) {
|
|
155
|
+
return Array.from({ length: 7 }, (_, i) => (firstDayOfWeek + i) % 7);
|
|
156
|
+
}
|
|
157
|
+
/** Merge a calendar day with a time-of-day taken from `time`, returning a new Date. */
|
|
158
|
+
export function withTime(day, time) {
|
|
159
|
+
return new Date(day.getFullYear(), day.getMonth(), day.getDate(), time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds());
|
|
160
|
+
}
|
|
161
|
+
/** Snap a minute value to the nearest lower multiple of `interval` (>=1). */
|
|
162
|
+
export function snapMinute(minute, interval) {
|
|
163
|
+
const step = Math.max(1, Math.floor(interval));
|
|
164
|
+
return Math.floor(minute / step) * step;
|
|
165
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* date-format - a small .NET/Smart-style date format-token engine used for
|
|
3
|
+
* `formatString` display and for parsing the masked DateTimePicker input. It is
|
|
4
|
+
* locale-aware for names (month/weekday, AM/PM) via `Intl`, and framework-free.
|
|
5
|
+
*
|
|
6
|
+
* Supported tokens (case-sensitive, matching Smart's `formatString`):
|
|
7
|
+
* d dd day of month (5, 05)
|
|
8
|
+
* ddd dddd weekday name (Mon, Monday) [locale]
|
|
9
|
+
* M MM month number (6, 06)
|
|
10
|
+
* MMM MMMM month name (Jun, June) [locale]
|
|
11
|
+
* yy yyyy year (26, 2026)
|
|
12
|
+
* H HH hour 24 (7, 07)
|
|
13
|
+
* h hh hour 12 (7, 07)
|
|
14
|
+
* m mm minute
|
|
15
|
+
* s ss second
|
|
16
|
+
* f ff fff fractional seconds (tenths..millis)
|
|
17
|
+
* t tt AM/PM designator (A, AM) [locale]
|
|
18
|
+
*
|
|
19
|
+
* Anything else is a literal. Wrap literal letters in single quotes ('T') or
|
|
20
|
+
* escape with a backslash (\T) to keep them out of token matching.
|
|
21
|
+
*/
|
|
22
|
+
type Part = {
|
|
23
|
+
token: string;
|
|
24
|
+
} | {
|
|
25
|
+
literal: string;
|
|
26
|
+
};
|
|
27
|
+
/** Split a mask into an ordered list of token / literal parts. */
|
|
28
|
+
export declare function tokenizeMask(mask: string): Part[];
|
|
29
|
+
/** Render `date` per `mask` in `locale`. */
|
|
30
|
+
export declare function formatDate(date: Date, mask: string, locale?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Parse `str` against `mask` in `locale`. Returns a Date or null if the string
|
|
33
|
+
* does not satisfy the mask. `yearCutoff` disambiguates 2-digit years: values
|
|
34
|
+
* <= (yearCutoff % 100) map to 2000s, above to 1900s (Smart's default 1926 ->
|
|
35
|
+
* a 26 pivot). `base` supplies fields the mask omits (defaults to epoch-zero
|
|
36
|
+
* local midnight so a time-only mask yields a today-agnostic time).
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseDate(str: string, mask: string, locale?: string, yearCutoff?: number, base?: Date): Date | null;
|
|
39
|
+
export {};
|