@nxgt/material-core 0.13.2 → 0.14.0-next-20261013055642
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 +10 -0
- package/README.md +245 -0
- package/dist/chunks/index-54gkyr43.js +23 -0
- package/dist/chunks/index-54gkyr43.js.map +10 -0
- package/dist/chunks/index-wdn02459.js +47 -0
- package/dist/chunks/index-wdn02459.js.map +10 -0
- package/dist/data-grid/aggregates.d.ts +16 -0
- package/dist/data-grid/aggregates.d.ts.map +1 -0
- package/dist/data-grid/announce.d.ts +41 -0
- package/dist/data-grid/announce.d.ts.map +1 -0
- package/dist/data-grid/change-ops.d.ts +29 -0
- package/dist/data-grid/change-ops.d.ts.map +1 -0
- package/dist/data-grid/change-select.d.ts +25 -0
- package/dist/data-grid/change-select.d.ts.map +1 -0
- package/dist/data-grid/change-types.d.ts +72 -0
- package/dist/data-grid/change-types.d.ts.map +1 -0
- package/dist/data-grid/changes.d.ts +46 -0
- package/dist/data-grid/changes.d.ts.map +1 -0
- package/dist/data-grid/conflict-text.d.ts +20 -0
- package/dist/data-grid/conflict-text.d.ts.map +1 -0
- package/dist/data-grid/edit-messages.d.ts +6 -0
- package/dist/data-grid/edit-messages.d.ts.map +1 -0
- package/dist/data-grid/edit-session.d.ts +107 -0
- package/dist/data-grid/edit-session.d.ts.map +1 -0
- package/dist/data-grid/exports-announce.d.ts +7 -0
- package/dist/data-grid/exports-announce.d.ts.map +1 -0
- package/dist/data-grid/exports-clipboard.d.ts +7 -0
- package/dist/data-grid/exports-clipboard.d.ts.map +1 -0
- package/dist/data-grid/exports-editing.d.ts +11 -0
- package/dist/data-grid/exports-editing.d.ts.map +1 -0
- package/dist/data-grid/exports-model.d.ts +10 -0
- package/dist/data-grid/exports-model.d.ts.map +1 -0
- package/dist/data-grid/exports-navigation.d.ts +8 -0
- package/dist/data-grid/exports-navigation.d.ts.map +1 -0
- package/dist/data-grid/exports-rows.d.ts +7 -0
- package/dist/data-grid/exports-rows.d.ts.map +1 -0
- package/dist/data-grid/exports-saving.d.ts +15 -0
- package/dist/data-grid/exports-saving.d.ts.map +1 -0
- package/dist/data-grid/history.d.ts +46 -0
- package/dist/data-grid/history.d.ts.map +1 -0
- package/dist/data-grid/index.d.ts +16 -0
- package/dist/data-grid/index.d.ts.map +1 -0
- package/dist/data-grid/index.js +1643 -0
- package/dist/data-grid/index.js.map +37 -0
- package/dist/data-grid/key-types.d.ts +82 -0
- package/dist/data-grid/key-types.d.ts.map +1 -0
- package/dist/data-grid/keys.d.ts +16 -0
- package/dist/data-grid/keys.d.ts.map +1 -0
- package/dist/data-grid/model.d.ts +52 -0
- package/dist/data-grid/model.d.ts.map +1 -0
- package/dist/data-grid/msg.d.ts +22 -0
- package/dist/data-grid/msg.d.ts.map +1 -0
- package/dist/data-grid/navigation.d.ts +33 -0
- package/dist/data-grid/navigation.d.ts.map +1 -0
- package/dist/data-grid/number-codes.d.ts +62 -0
- package/dist/data-grid/number-codes.d.ts.map +1 -0
- package/dist/data-grid/number-messages.d.ts +21 -0
- package/dist/data-grid/number-messages.d.ts.map +1 -0
- package/dist/data-grid/paste-plan.d.ts +43 -0
- package/dist/data-grid/paste-plan.d.ts.map +1 -0
- package/dist/data-grid/range.d.ts +52 -0
- package/dist/data-grid/range.d.ts.map +1 -0
- package/dist/data-grid/rebase.d.ts +10 -0
- package/dist/data-grid/rebase.d.ts.map +1 -0
- package/dist/data-grid/same-value.d.ts +7 -0
- package/dist/data-grid/same-value.d.ts.map +1 -0
- package/dist/data-grid/save-answer.d.ts +26 -0
- package/dist/data-grid/save-answer.d.ts.map +1 -0
- package/dist/data-grid/save-failure.d.ts +32 -0
- package/dist/data-grid/save-failure.d.ts.map +1 -0
- package/dist/data-grid/save-queue.d.ts +77 -0
- package/dist/data-grid/save-queue.d.ts.map +1 -0
- package/dist/data-grid/save-versions.d.ts +11 -0
- package/dist/data-grid/save-versions.d.ts.map +1 -0
- package/dist/data-grid/sort-snapshot.d.ts +22 -0
- package/dist/data-grid/sort-snapshot.d.ts.map +1 -0
- package/dist/data-grid/standard-schema.d.ts +38 -0
- package/dist/data-grid/standard-schema.d.ts.map +1 -0
- package/dist/data-grid/tsv.d.ts +29 -0
- package/dist/data-grid/tsv.d.ts.map +1 -0
- package/dist/data-grid/types.d.ts +89 -0
- package/dist/data-grid/types.d.ts.map +1 -0
- package/dist/data-grid/validate.d.ts +66 -0
- package/dist/data-grid/validate.d.ts.map +1 -0
- package/dist/data-grid/version.d.ts +22 -0
- package/dist/data-grid/version.d.ts.map +1 -0
- package/dist/lib/parse-number.d.ts +25 -0
- package/dist/lib/parse-number.d.ts.map +1 -0
- package/dist/lib/parse-number.js +15 -0
- package/dist/lib/parse-number.js.map +9 -0
- package/dist/lib/text-match.js +7 -21
- package/dist/lib/text-match.js.map +3 -4
- package/docs/roadmap.md +10 -1
- package/package.json +15 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @nxgt/material-core
|
|
2
2
|
|
|
3
|
+
## 0.14.0-next-20261013055642
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#693](https://github.com/softistx/nxgt-material/pull/693) [`075693d`](https://github.com/softistx/nxgt-material/commit/075693dda8355458d3a2795277bfba0de14a773b) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `@nxgt/material-core/data-grid`: the pure half of the coming editable `DataGrid` — its model (rows and columns by position and key, what each cell allows, the version each cell was read from, per cell with the row's as the fallback), navigation and keyboard commands (arrows over the header, body and footer rows, Home/End and Ctrl/Cmd+Left/Right, Ctrl/Cmd+Up/Down to a column's ends, Ctrl/Cmd+Home/End, PageUp/PageDown, Tab and Enter skipping cells that cannot be edited, RTL), the selected range, the edit session, validation (a column's parse, then `validate`, then any Standard Schema; the server's refusals by entry index), a number column with codes (`numberCodes`: "abs" or "disp" in any language, `,` or `.` as the decimal separator, bounds and decimals, an empty text clearing the cell) and the live region's message descriptors.
|
|
8
|
+
|
|
9
|
+
`@nxgt/material-core/parse-number`: `parseNumberInput`, `clampNumber` and `stepNumber` move here from `@nxgt/material`'s `NumberField`, plus `decimalsTyped`.
|
|
10
|
+
|
|
11
|
+
- [#699](https://github.com/softistx/nxgt-material/pull/699) [`b13f08a`](https://github.com/softistx/nxgt-material/commit/b13f08a5c1a92f362ecd4e1f365173b5a8eb88f6) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `@nxgt/material-core/data-grid`: the rest of the coming editable `DataGrid`'s pure half — the changes per cell (`editCell`, each change keeping the value and the version it was read at, per cell with the row's as the fallback; `rebase` after a refetch, a moved version and value turning a pending edit into a conflict while a cell whose value is still its base adopts the new version; a saved value, and "Keep theirs", kept as an overlay until the app's rows hold it, a refetch still read at the version a save or a conflict replaced changing nothing; `resolveConflict` with "theirs" or "mine"; a conflict outliving any edit until it is resolved; `expireSaved`; `changeCounts` and `blocking`), the save queue (`startCall`, `applyAnswer`, `applyFailure`, `applyAbort`, `retry`: one call in flight at a time; per cell up to 200 entries a call, per row once the row is left, or in one batch on `save()` with no limit, blocked while a cell is in error or in conflict; an answer is all or nothing, `versions[i]` updating entry `i`'s cells (and, with `versionScope: 'row'`, the row's other changes) and a refusal flagging its cells as errors or conflicts; paused after 3 failures in a row, an abort counting none), undo and redo (`record`, `undo`, `redo`: 100 steps, an undo is a new edit, cells changed since are skipped and counted), tab-separated copy and paste as Excel and Google Sheets write it, a cell with a quote and no line break read raw (`parseTsv`, `serializeTsv`), the all-or-nothing paste plan (`planPaste`: past the grid, locked, read-only, computed, not parsed and invalid cells listed as problems), the totals' aggregates (`aggregate`: sum, mean, min, max, count, or the app's own, over numbers only; a count of nothing is 0), the sort order kept while editing (`reconcileOrder`) and the conflict panel's two values, a cleared one reading "Cleared" (`conflictValues`).
|
|
12
|
+
|
|
3
13
|
## 0.13.2
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -120,6 +120,8 @@ can reach. No other subpath imports a `node:` built-in.
|
|
|
120
120
|
| `/states` | `EMPTY_STATE_KINDS`, `ERROR_STATE_KINDS`, `resolveEmptyStateKind`, `isErrorKind`, `stateCopy`, `STATE_ACTION_KEYS`, `describeError`, `STATE_ILLUSTRATIONS`, `STATE_ACCENT`, `STATE_ICON` and the types `EmptyStateKind`, `ErrorStateKind`, `StateKind`, `LegacyEmptyStateVariant`, `StateFamily`, `StateCopy`, `ErrorDetail`, `StateIllustration`, `IllustrationShape`, `IllustrationTone`, `StateAccent`, `StateIconName` — the kinds, copy keys, error details and illustrations behind `EmptyState` and `ErrorState` |
|
|
121
121
|
| `/vite` | `nxgtMaterial()`, `pdfkitStandardFonts()`, `cspAssetsInlineLimit`, `CSP_NONCE_PLACEHOLDER`, `ZOD_JITLESS_SCRIPT` and the types `NxgtMaterialOptions`, `NxgtMaterialCspOptions`, `NxgtMaterialLibrary` — see below |
|
|
122
122
|
| `/pdfkit-fonts` | **Node only.** `pdfkitStandardFontDirs`, `pdfkitStandardFontFiles`, `PDFKIT_STANDARD_FONTS`, the types `PdfkitStandardFont` and `PdfkitFontsWarn` — where the app's pdfkit keeps its standard fonts, for a server tracer |
|
|
123
|
+
| `/data-grid` | **Headless**, for the editable `DataGrid` coming in `@nxgt/material`; nothing renders yet. The model (`buildGridModel`, `cellAccess`, `cellVersion` and `isNewCellVersion`), navigation (`navigate`, `gridCommandFor`), the range (`extend`, `selectAll`, `rangeCells`, …), the edit session (`editReducer`), validation (`validateCell`, `serverErrors`, `refusalsByCell`, Standard Schema), `numberCodes`, the changes per cell (`NO_CHANGES`, `editCell`, `rebase`, `resolveConflict`, `expireSaved`, `changeCounts`, `blocking`), the save queue (`IDLE_QUEUE`, `nextCall`, `startCall`, `applyAnswer`, `applyFailure`, `applyAbort`, `retry`), undo and redo (`EMPTY_HISTORY`, `record`, `undo`, `redo`), copy and paste (`parseTsv`, `serializeTsv`, `planPaste`), the totals (`aggregate`), the kept sort order (`snapshotOrder`, `reconcileOrder`, `sortingChanged`), the conflict panel's text (`conflictValues`, `conflictSummary`, `clearedText`) and the live region's descriptors (`savedMessage`, `cellStateText`, …), with the types `GridModel`, `GridColumnSpec`, `GridRowSpec`, `GridMsg`, `GridPos`, `GridRange`, `GridVersion`, `GridCode`, `GridChanges`, `GridSaveQueue`, `GridCall`, `GridSaveAnswer`, `GridHistory`, `GridStep`, `GridPasteInput`, `GridPastePlan`, `GridPasteProblem`, `GridAggregate` and `GridSort`. A cell's version is `0` or `undefined` when it does not exist yet on the server. See below |
|
|
124
|
+
| `/parse-number` | `parseNumberInput`, `decimalsTyped`, `clampNumber`, `stepNumber` — a typed number read with `,` or `.` as its decimal separator, its typed decimals, and the clamp and step behind `NumberField` |
|
|
123
125
|
|
|
124
126
|
## Usage
|
|
125
127
|
|
|
@@ -1357,6 +1359,249 @@ You rarely call it yourself: `withNxgtPrint` (`@nxgt/material/next`) turns
|
|
|
1357
1359
|
the directories into `outputFileTracingIncludes` globs, and the Nuxt module
|
|
1358
1360
|
of `@nxgt/material-vue` adds the files to `nitro.externals.traceInclude`.
|
|
1359
1361
|
|
|
1362
|
+
### An editable grid's pure half: `/data-grid`
|
|
1363
|
+
|
|
1364
|
+
The logic behind the editable `DataGrid` that is coming to `@nxgt/material`,
|
|
1365
|
+
with nothing rendered yet: where a cell is, which cell a key moves to, what a
|
|
1366
|
+
typed text parses to, and the messages to announce. It never reads the DOM, so
|
|
1367
|
+
it also runs in a test or on a server. Positions are `{ row, col }`; the header
|
|
1368
|
+
row is `-1` and the footer rows follow the body.
|
|
1369
|
+
|
|
1370
|
+
```ts
|
|
1371
|
+
import {
|
|
1372
|
+
buildGridModel,
|
|
1373
|
+
cellVersion,
|
|
1374
|
+
gridCommandFor,
|
|
1375
|
+
gridPlatform,
|
|
1376
|
+
isGridPos,
|
|
1377
|
+
navigate,
|
|
1378
|
+
numberCodes,
|
|
1379
|
+
validateCell,
|
|
1380
|
+
type GridPos,
|
|
1381
|
+
} from '@nxgt/material-core/data-grid';
|
|
1382
|
+
|
|
1383
|
+
type Row = { id: string; outOf: number; version?: number };
|
|
1384
|
+
|
|
1385
|
+
const model = buildGridModel({
|
|
1386
|
+
columns: [
|
|
1387
|
+
{ id: 'name', label: 'Name', frozen: true },
|
|
1388
|
+
{ id: 'maths', label: 'Maths', editor: 'number' },
|
|
1389
|
+
],
|
|
1390
|
+
rows: [{ id: 'ada' }, { id: 'alan' }],
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
// A key press is a command, and a command takes the active cell somewhere.
|
|
1394
|
+
const key = { key: 'ArrowDown', shiftKey: false, ctrlKey: false, metaKey: false, altKey: false };
|
|
1395
|
+
const command = gridCommandFor(key, 'nav', gridPlatform(navigator.platform));
|
|
1396
|
+
// { type: 'move', cmd: 'down' }
|
|
1397
|
+
|
|
1398
|
+
let active: GridPos = { row: 0, col: 1 };
|
|
1399
|
+
if (command?.type === 'move') {
|
|
1400
|
+
const next = navigate(active, command.cmd, {
|
|
1401
|
+
model,
|
|
1402
|
+
pageSize: 10,
|
|
1403
|
+
dir: 'ltr',
|
|
1404
|
+
skipReadOnly: true,
|
|
1405
|
+
});
|
|
1406
|
+
if (isGridPos(next)) active = next; // else 'exit-forward' / 'exit-back': leave the grid
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// A number column that also takes codes, in any language, with `,` or `.`.
|
|
1410
|
+
const grades = numberCodes<Row>({
|
|
1411
|
+
min: 0,
|
|
1412
|
+
max: (row) => row.outOf,
|
|
1413
|
+
decimals: 2,
|
|
1414
|
+
codes: [{ value: 'ABS', labels: { en: 'abs', fr: 'abs' }, aliases: { fr: ['absent'] } }],
|
|
1415
|
+
});
|
|
1416
|
+
const row: Row = { id: 'ada', outOf: 20 };
|
|
1417
|
+
|
|
1418
|
+
grades.parse('abs', { lang: 'fr', row }); // { value: 'ABS' }
|
|
1419
|
+
grades.parse('12,5', { lang: 'fr', row }); // { value: 12.5 }
|
|
1420
|
+
grades.parse('25', { lang: 'en', row }); // { error: GridMsg } — "Enter a number from 0 to 20"
|
|
1421
|
+
|
|
1422
|
+
// Parse, then the column's check, then a Standard Schema: the first error wins.
|
|
1423
|
+
const result = validateCell({
|
|
1424
|
+
text: '12,5',
|
|
1425
|
+
row,
|
|
1426
|
+
parse: (text) => grades.parse(text, { lang: 'fr', row }),
|
|
1427
|
+
});
|
|
1428
|
+
// { ok: true, value: 12.5 } or { ok: false, error: GridMsg }
|
|
1429
|
+
|
|
1430
|
+
// The version the server gave a cell: per cell, else the row's.
|
|
1431
|
+
cellVersion(row, 'maths', { getRowVersion: (r) => r.version });
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
- **Messages are descriptors.** Every error and announcement is a `GridMsg`
|
|
1435
|
+
(`{ key: 'material.data-grid.…', fallback, values? }`): the library
|
|
1436
|
+
translates `key`, and `gridFallbackText(message)` gives the English text.
|
|
1437
|
+
- **Versions are passed through unchanged.** `cellVersion` answers
|
|
1438
|
+
`getCellVersion` when it is given, else `getRowVersion`, else `undefined`;
|
|
1439
|
+
`isNewCellVersion` is true for `0` and `undefined`, a cell that does not
|
|
1440
|
+
exist yet on the server.
|
|
1441
|
+
- **Numbers are never rounded or clamped.** Too many decimals or a value out
|
|
1442
|
+
of bounds is an error. Spaces are stripped (`"1 234"` is 1234), but a
|
|
1443
|
+
second separator is refused: `"1,234"` is 1.234 and `"1,234.5"` an error.
|
|
1444
|
+
`parse` needs the row; with a bound that is a function of the row and no
|
|
1445
|
+
row, it refuses the number rather than let it through unchecked.
|
|
1446
|
+
- **A cleared cell skips the schema.** `null` still runs `validate`, but not
|
|
1447
|
+
the Standard Schema, so a `z.number()` does not refuse a clear; refuse an
|
|
1448
|
+
empty cell in `validate` (or with `numberCodes({ emptyClears: false })`).
|
|
1449
|
+
Without `parse`, the value is the text: a column whose value is not a
|
|
1450
|
+
string must give one.
|
|
1451
|
+
- **Tab in an editor always moves on.** `tabBehavior: 'exit'` makes Tab leave
|
|
1452
|
+
the grid from a cell, but a commit's Tab (`commitNext` / `commitPrev`)
|
|
1453
|
+
still walks the cells. `gridCommandFor` returns `{ type: 'swallow' }` for
|
|
1454
|
+
the browser's back and forward (Alt+Arrow, Cmd+[ and Cmd+]): call
|
|
1455
|
+
`preventDefault` and do nothing.
|
|
1456
|
+
|
|
1457
|
+
#### Saving changes: the queue
|
|
1458
|
+
|
|
1459
|
+
Each edited cell is a change that keeps the value and the version it was read
|
|
1460
|
+
at. The queue picks what the next call sends, one call at a time; the answer
|
|
1461
|
+
is all or nothing.
|
|
1462
|
+
|
|
1463
|
+
```ts
|
|
1464
|
+
import {
|
|
1465
|
+
applyAbort,
|
|
1466
|
+
applyAnswer,
|
|
1467
|
+
applyFailure,
|
|
1468
|
+
cellVersion,
|
|
1469
|
+
editCell,
|
|
1470
|
+
expireSaved,
|
|
1471
|
+
gridCellKey,
|
|
1472
|
+
type GridCall,
|
|
1473
|
+
type GridSaveAnswer,
|
|
1474
|
+
IDLE_QUEUE,
|
|
1475
|
+
NO_CHANGES,
|
|
1476
|
+
startCall,
|
|
1477
|
+
} from '@nxgt/material-core/data-grid';
|
|
1478
|
+
|
|
1479
|
+
type Row = { id: string; maths: number | null; version: number };
|
|
1480
|
+
const row: Row = { id: 'ada', maths: 12, version: 3 };
|
|
1481
|
+
// The app's save: one call, answered as a whole.
|
|
1482
|
+
declare function onSave(entries: GridCall['entries'], signal: AbortSignal): Promise<GridSaveAnswer>;
|
|
1483
|
+
|
|
1484
|
+
let changes = NO_CHANGES;
|
|
1485
|
+
let queue = IDLE_QUEUE;
|
|
1486
|
+
|
|
1487
|
+
// An edit: going back to the value read drops it again.
|
|
1488
|
+
changes = editCell(changes, {
|
|
1489
|
+
key: gridCellKey(row.id, 'maths'),
|
|
1490
|
+
base: row.maths,
|
|
1491
|
+
baseVersion: cellVersion(row, 'maths', { getRowVersion: (r) => r.version }),
|
|
1492
|
+
value: 14,
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
// Cell mode sends everything dirty (200 entries a call at most); row mode the
|
|
1496
|
+
// rows left; batch mode only on save(), with no limit, and not while a cell is
|
|
1497
|
+
// in error or in conflict.
|
|
1498
|
+
const started = startCall(changes, queue, { mode: 'cell' });
|
|
1499
|
+
if (started) {
|
|
1500
|
+
({ changes, queue } = started); // its cells are 'saving'
|
|
1501
|
+
const { call } = started;
|
|
1502
|
+
const controller = new AbortController();
|
|
1503
|
+
try {
|
|
1504
|
+
const answer = await onSave(call.entries, controller.signal);
|
|
1505
|
+
// Versions per row (no getCellVersion): the row's other changes move too.
|
|
1506
|
+
({ changes, queue } = applyAnswer(changes, queue, call.id, answer, Date.now(), { versionScope: 'row' }));
|
|
1507
|
+
} catch {
|
|
1508
|
+
({ changes, queue } = controller.signal.aborted
|
|
1509
|
+
? applyAbort(changes, queue, call.id) // no failure counted
|
|
1510
|
+
: applyFailure(changes, queue, call.id)); // paused after 3: retry(queue)
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
changes = expireSaved(changes, Date.now()); // 1.5s later: the "saved" mark goes
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
- **`versions[i]` is entry `i`'s.** It becomes the cell's new `baseVersion`
|
|
1517
|
+
(every changed cell of the row, in row mode; with `versionScope: 'row'`,
|
|
1518
|
+
the row's other changes still on the old version too). Without one, the
|
|
1519
|
+
version the cell was read at stays, and the next `rebase` adopts the
|
|
1520
|
+
row's: a cell whose value is still its base never conflicts.
|
|
1521
|
+
- **A refusal saves nothing.** `{ ok: false, refused }` names entries by index:
|
|
1522
|
+
an `error` flags its cell, a `conflict` opens one (`resolveConflict(changes,
|
|
1523
|
+
key, 'theirs' | 'mine')`), and every other cell goes back to dirty, to be
|
|
1524
|
+
sent again. A cell edited during the call stays dirty either way. A
|
|
1525
|
+
refusal naming no cell sent is counted in `outcome.unmatched`.
|
|
1526
|
+
- **A saved value stays until the rows hold it.** It is kept as an overlay
|
|
1527
|
+
(status `'clean'` once the mark expires), as "Keep theirs" is, so the cell
|
|
1528
|
+
never flashes back to stale data. The version a save or a conflict
|
|
1529
|
+
replaced is kept as `staleVersion`: a refetch that still reads at it is
|
|
1530
|
+
older than the grid and changes nothing, and `rebase` drops the overlay
|
|
1531
|
+
once the refetched rows hold the value.
|
|
1532
|
+
- **A refetch is a `rebase`.** It compares cell versions when `getCellVersion`
|
|
1533
|
+
is given, else the row's; a pending edit whose version and value moved
|
|
1534
|
+
becomes a conflict, and a conflict outlives any edit until it is resolved.
|
|
1535
|
+
|
|
1536
|
+
```ts
|
|
1537
|
+
import {
|
|
1538
|
+
aggregate,
|
|
1539
|
+
buildGridModel,
|
|
1540
|
+
conflictValues,
|
|
1541
|
+
EMPTY_HISTORY,
|
|
1542
|
+
type GridCellKey,
|
|
1543
|
+
type GridNumberCodeValue,
|
|
1544
|
+
NO_CHANGES,
|
|
1545
|
+
numberCodes,
|
|
1546
|
+
parseTsv,
|
|
1547
|
+
planPaste,
|
|
1548
|
+
splitCellKey,
|
|
1549
|
+
undo,
|
|
1550
|
+
} from '@nxgt/material-core/data-grid';
|
|
1551
|
+
|
|
1552
|
+
const changes = NO_CHANGES; // the grid's changes
|
|
1553
|
+
const rows = new Map([['ada', { maths: 12 as GridNumberCodeValue }]]);
|
|
1554
|
+
const model = buildGridModel({
|
|
1555
|
+
columns: [{ id: 'maths', label: 'Maths', editor: 'number' }],
|
|
1556
|
+
rows: [{ id: 'ada' }],
|
|
1557
|
+
});
|
|
1558
|
+
const grades = numberCodes({ min: 0, max: 20, decimals: 2 });
|
|
1559
|
+
const [lang, locale] = ['fr', 'fr-FR'];
|
|
1560
|
+
const valueInRows = (key: GridCellKey) => {
|
|
1561
|
+
const { rowId } = splitCellKey(key);
|
|
1562
|
+
return rows.get(rowId)?.maths;
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
// Undo is a new edit: send each of `undone.edits` through editCell; `skipped` cells changed since.
|
|
1566
|
+
const undone = undo(EMPTY_HISTORY, (key) => changes[key]?.value ?? valueInRows(key)); // null: nothing to undo
|
|
1567
|
+
|
|
1568
|
+
// A paste is all or nothing, at the matrix's own size: { ok: true, edits, range } or { ok: false, problems }.
|
|
1569
|
+
const plan = planPaste({
|
|
1570
|
+
matrix: parseTsv('14\r\n'),
|
|
1571
|
+
anchor: { row: 0, col: 0 },
|
|
1572
|
+
model,
|
|
1573
|
+
parse: (text) => grades.parse(text, { lang, row: undefined }),
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
// Totals over numbers only: codes and empty cells are skipped.
|
|
1577
|
+
aggregate('mean', [12, 'ABS', null, 14]); // 13; aggregate('count', []) is 0
|
|
1578
|
+
|
|
1579
|
+
// The conflict panel's two values, formatted with the column's own format; a cleared one is "Cleared".
|
|
1580
|
+
conflictValues({
|
|
1581
|
+
theirs: null,
|
|
1582
|
+
yours: 12.5,
|
|
1583
|
+
format: (value) => grades.format(value as GridNumberCodeValue, { lang, locale }),
|
|
1584
|
+
}); // { theirs: Cleared, yours: "12,5" }
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
### A typed number: `/parse-number`
|
|
1588
|
+
|
|
1589
|
+
A number typed with `,` or `.` as its decimal separator, the decimals it was
|
|
1590
|
+
typed with, and the clamp and step behind `NumberField` and the data grid's
|
|
1591
|
+
number editor.
|
|
1592
|
+
|
|
1593
|
+
```ts
|
|
1594
|
+
import { clampNumber, decimalsTyped, parseNumberInput, stepNumber } from '@nxgt/material-core/parse-number';
|
|
1595
|
+
|
|
1596
|
+
parseNumberInput('12,5'); // 12.5
|
|
1597
|
+
parseNumberInput('1 234,5'); // 1234.5: the last of `,` and `.` is the decimal separator
|
|
1598
|
+
parseNumberInput('-'); // null: empty or incomplete
|
|
1599
|
+
decimalsTyped('12,50'); // 2: a trailing zero is what the user typed
|
|
1600
|
+
clampNumber(15, 0, 10); // 10: `min` and `max` are each optional
|
|
1601
|
+
stepNumber(1.1, 1, 0.1); // 1.2: no float drift
|
|
1602
|
+
stepNumber(null, -1, 1, 0); // 0: empty starts at 0, then is clamped
|
|
1603
|
+
```
|
|
1604
|
+
|
|
1360
1605
|
## Traps
|
|
1361
1606
|
|
|
1362
1607
|
| Symptom | Fix |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/lib/text-match.ts
|
|
2
|
+
var DIACRITICS = /[̀-ͯ]/g;
|
|
3
|
+
function foldText2(text, locale) {
|
|
4
|
+
return lowercase(text, locale).normalize("NFD").replace(DIACRITICS, "");
|
|
5
|
+
}
|
|
6
|
+
function lowercase(text, locale) {
|
|
7
|
+
try {
|
|
8
|
+
return text.toLocaleLowerCase(locale);
|
|
9
|
+
} catch {
|
|
10
|
+
return text.toLocaleLowerCase();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function matchesText2(text, query, locale) {
|
|
14
|
+
const needle = foldText2(query.trim(), locale);
|
|
15
|
+
if (!needle)
|
|
16
|
+
return true;
|
|
17
|
+
return foldText2(text, locale).includes(needle);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { foldText2, matchesText2 };
|
|
21
|
+
|
|
22
|
+
//# debugId=021CEAFE8087CA9864756E2164756E21
|
|
23
|
+
//# sourceMappingURL=index-54gkyr43.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/text-match.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Accent- and case-insensitive text matching, for a list filtered as the user\n * types: `'e'` finds \"École\", `'ecole'` finds \"ÉCOLE\".\n */\n\n/** Combining diacritical marks, which `normalize('NFD')` splits from their letter. */\nconst DIACRITICS = /[̀-ͯ]/g;\n\n/**\n * `text` lowercased for `locale` (the runtime's when omitted), then without\n * its diacritics: `foldText('Élève')` is `'eleve'`. Lowercasing comes first,\n * so a Turkish `'İ'` (`'tr'`) folds to `'i'` rather than keeping a dot.\n */\nexport function foldText(text: string, locale?: string): string {\n\treturn lowercase(text, locale).normalize('NFD').replace(DIACRITICS, '');\n}\n\nfunction lowercase(text: string, locale?: string): string {\n\ttry {\n\t\treturn text.toLocaleLowerCase(locale);\n\t} catch {\n\t\t// An invalid language tag: the runtime's own rules, never a throw.\n\t\treturn text.toLocaleLowerCase();\n\t}\n}\n\n/**\n * Whether `query` occurs in `text`, ignoring case and accents. A query of\n * spaces only matches everything, as an empty one does.\n */\nexport function matchesText(\n\ttext: string,\n\tquery: string,\n\tlocale?: string,\n): boolean {\n\tconst needle = foldText(query.trim(), locale);\n\tif (!needle) return true;\n\treturn foldText(text, locale).includes(needle);\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAMA,IAAM,aAAa;AAOZ,SAAS,SAAQ,CAAC,MAAc,QAAyB;AAAA,EAC/D,OAAO,UAAU,MAAM,MAAM,EAAE,UAAU,KAAK,EAAE,QAAQ,YAAY,EAAE;AAAA;AAGvE,SAAS,SAAS,CAAC,MAAc,QAAyB;AAAA,EACzD,IAAI;AAAA,IACH,OAAO,KAAK,kBAAkB,MAAM;AAAA,IACnC,MAAM;AAAA,IAEP,OAAO,KAAK,kBAAkB;AAAA;AAAA;AAQzB,SAAS,YAAW,CAC1B,MACA,OACA,QACU;AAAA,EACV,MAAM,SAAS,UAAS,MAAM,KAAK,GAAG,MAAM;AAAA,EAC5C,IAAI,CAAC;AAAA,IAAQ,OAAO;AAAA,EACpB,OAAO,UAAS,MAAM,MAAM,EAAE,SAAS,MAAM;AAAA;",
|
|
8
|
+
"debugId": "021CEAFE8087CA9864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/lib/parse-number.ts
|
|
2
|
+
var SPACES = /\s/g;
|
|
3
|
+
function parseNumberInput2(raw) {
|
|
4
|
+
const cleaned = raw.trim().replace(SPACES, "");
|
|
5
|
+
if (!cleaned || cleaned === "-" || cleaned === "+" || cleaned === "." || cleaned === ",") {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const lastComma = cleaned.lastIndexOf(",");
|
|
9
|
+
const lastDot = cleaned.lastIndexOf(".");
|
|
10
|
+
const normalized = lastComma > lastDot ? cleaned.replace(/\./g, "").replace(",", ".") : cleaned.replace(/,/g, "");
|
|
11
|
+
const value = Number(normalized);
|
|
12
|
+
return Number.isFinite(value) ? value : null;
|
|
13
|
+
}
|
|
14
|
+
function decimalsTyped2(raw) {
|
|
15
|
+
const cleaned = raw.trim().replace(SPACES, "");
|
|
16
|
+
const separator = Math.max(cleaned.lastIndexOf(","), cleaned.lastIndexOf("."));
|
|
17
|
+
if (separator === -1)
|
|
18
|
+
return 0;
|
|
19
|
+
return /^\d*/.exec(cleaned.slice(separator + 1))?.[0].length ?? 0;
|
|
20
|
+
}
|
|
21
|
+
function clampNumber2(value, min, max) {
|
|
22
|
+
let next = value;
|
|
23
|
+
if (min != null)
|
|
24
|
+
next = Math.max(min, next);
|
|
25
|
+
if (max != null)
|
|
26
|
+
next = Math.min(max, next);
|
|
27
|
+
return next;
|
|
28
|
+
}
|
|
29
|
+
function decimalPlaces(value) {
|
|
30
|
+
if (!Number.isFinite(value))
|
|
31
|
+
return 0;
|
|
32
|
+
const text = String(value);
|
|
33
|
+
const index = text.indexOf(".");
|
|
34
|
+
if (index === -1 || text.includes("e") || text.includes("E"))
|
|
35
|
+
return 0;
|
|
36
|
+
return text.length - index - 1;
|
|
37
|
+
}
|
|
38
|
+
function stepNumber2(value, direction, step = 1, min, max) {
|
|
39
|
+
const places = Math.max(decimalPlaces(step), decimalPlaces(value ?? 0));
|
|
40
|
+
const next = Number(((value ?? 0) + direction * step).toFixed(places));
|
|
41
|
+
return clampNumber2(next, min, max);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { parseNumberInput2, decimalsTyped2, clampNumber2, stepNumber2 };
|
|
45
|
+
|
|
46
|
+
//# debugId=1910164834C0257964756E2164756E21
|
|
47
|
+
//# sourceMappingURL=index-wdn02459.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/parse-number.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * The number a user typed, read with either decimal separator: `'12,5'` and\n * `'12.5'` are both 12.5. Behind `NumberField` and the data grid's number\n * editor (`numberCodes`).\n */\n\n/** Spaces a user or a locale puts in a number (`\\s` covers the no-break ones). */\nconst SPACES = /\\s/g;\n\n/**\n * `raw` as a number, or `null` when it is empty or incomplete (`'-'`, `','`).\n * The last of `,` and `.` is the decimal separator and the other one groups\n * (`'1 234,5'` and `'1,234.5'` are 1234.5); a lone `,` is a decimal comma.\n */\nexport function parseNumberInput(raw: string): number | null {\n\tconst cleaned = raw.trim().replace(SPACES, '');\n\tif (\n\t\t!cleaned ||\n\t\tcleaned === '-' ||\n\t\tcleaned === '+' ||\n\t\tcleaned === '.' ||\n\t\tcleaned === ','\n\t) {\n\t\treturn null;\n\t}\n\tconst lastComma = cleaned.lastIndexOf(',');\n\tconst lastDot = cleaned.lastIndexOf('.');\n\tconst normalized =\n\t\tlastComma > lastDot\n\t\t\t? cleaned.replace(/\\./g, '').replace(',', '.')\n\t\t\t: cleaned.replace(/,/g, '');\n\tconst value = Number(normalized);\n\treturn Number.isFinite(value) ? value : null;\n}\n\n/**\n * How many decimals `raw` was typed with, read as `parseNumberInput` reads\n * it: the digits after the last `,` or `.` — `'12,50'` is 2, `'12'` and\n * `'12.'` are 0. A trailing zero counts: it is what the user typed.\n */\nexport function decimalsTyped(raw: string): number {\n\tconst cleaned = raw.trim().replace(SPACES, '');\n\tconst separator = Math.max(\n\t\tcleaned.lastIndexOf(','),\n\t\tcleaned.lastIndexOf('.'),\n\t);\n\tif (separator === -1) return 0;\n\treturn /^\\d*/.exec(cleaned.slice(separator + 1))?.[0].length ?? 0;\n}\n\n/** `value` kept within `min` and `max`, either of which may be absent. */\nexport function clampNumber(value: number, min?: number, max?: number) {\n\tlet next = value;\n\tif (min != null) next = Math.max(min, next);\n\tif (max != null) next = Math.min(max, next);\n\treturn next;\n}\n\nfunction decimalPlaces(value: number) {\n\tif (!Number.isFinite(value)) return 0;\n\tconst text = String(value);\n\tconst index = text.indexOf('.');\n\tif (index === -1 || text.includes('e') || text.includes('E')) return 0;\n\treturn text.length - index - 1;\n}\n\n/**\n * `value` one `step` up or down, rounded to the decimals of the step or the\n * value (no float drift: 1.1 + 0.1 is 1.2), then clamped. Empty starts at 0.\n */\nexport function stepNumber(\n\tvalue: number | null,\n\tdirection: 1 | -1,\n\tstep = 1,\n\tmin?: number,\n\tmax?: number,\n) {\n\tconst places = Math.max(decimalPlaces(step), decimalPlaces(value ?? 0));\n\tconst next = Number(((value ?? 0) + direction * step).toFixed(places));\n\treturn clampNumber(next, min, max);\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAOA,IAAM,SAAS;AAOR,SAAS,iBAAgB,CAAC,KAA4B;AAAA,EAC5D,MAAM,UAAU,IAAI,KAAK,EAAE,QAAQ,QAAQ,EAAE;AAAA,EAC7C,IACC,CAAC,WACD,YAAY,OACZ,YAAY,OACZ,YAAY,OACZ,YAAY,KACX;AAAA,IACD,OAAO;AAAA,EACR;AAAA,EACA,MAAM,YAAY,QAAQ,YAAY,GAAG;AAAA,EACzC,MAAM,UAAU,QAAQ,YAAY,GAAG;AAAA,EACvC,MAAM,aACL,YAAY,UACT,QAAQ,QAAQ,OAAO,EAAE,EAAE,QAAQ,KAAK,GAAG,IAC3C,QAAQ,QAAQ,MAAM,EAAE;AAAA,EAC5B,MAAM,QAAQ,OAAO,UAAU;AAAA,EAC/B,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA;AAQlC,SAAS,cAAa,CAAC,KAAqB;AAAA,EAClD,MAAM,UAAU,IAAI,KAAK,EAAE,QAAQ,QAAQ,EAAE;AAAA,EAC7C,MAAM,YAAY,KAAK,IACtB,QAAQ,YAAY,GAAG,GACvB,QAAQ,YAAY,GAAG,CACxB;AAAA,EACA,IAAI,cAAc;AAAA,IAAI,OAAO;AAAA,EAC7B,OAAO,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC,CAAC,IAAI,GAAG,UAAU;AAAA;AAI1D,SAAS,YAAW,CAAC,OAAe,KAAc,KAAc;AAAA,EACtE,IAAI,OAAO;AAAA,EACX,IAAI,OAAO;AAAA,IAAM,OAAO,KAAK,IAAI,KAAK,IAAI;AAAA,EAC1C,IAAI,OAAO;AAAA,IAAM,OAAO,KAAK,IAAI,KAAK,IAAI;AAAA,EAC1C,OAAO;AAAA;AAGR,SAAS,aAAa,CAAC,OAAe;AAAA,EACrC,IAAI,CAAC,OAAO,SAAS,KAAK;AAAA,IAAG,OAAO;AAAA,EACpC,MAAM,OAAO,OAAO,KAAK;AAAA,EACzB,MAAM,QAAQ,KAAK,QAAQ,GAAG;AAAA,EAC9B,IAAI,UAAU,MAAM,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,GAAG;AAAA,IAAG,OAAO;AAAA,EACrE,OAAO,KAAK,SAAS,QAAQ;AAAA;AAOvB,SAAS,WAAU,CACzB,OACA,WACA,OAAO,GACP,KACA,KACC;AAAA,EACD,MAAM,SAAS,KAAK,IAAI,cAAc,IAAI,GAAG,cAAc,SAAS,CAAC,CAAC;AAAA,EACtE,MAAM,OAAO,SAAS,SAAS,KAAK,YAAY,MAAM,QAAQ,MAAM,CAAC;AAAA,EACrE,OAAO,aAAY,MAAM,KAAK,GAAG;AAAA;",
|
|
8
|
+
"debugId": "1910164834C0257964756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A totals row's built-in aggregates over a column's values. Only numbers
|
|
3
|
+
* count: codes ("abs"), empty cells and anything else are skipped, so the
|
|
4
|
+
* mean of 12, "abs" and 14 is 13. With no number, `count` is 0 and the
|
|
5
|
+
* others are `null` (the cell shows nothing); an app's own aggregate is
|
|
6
|
+
* not called.
|
|
7
|
+
*/
|
|
8
|
+
export type GridAggregateName = 'sum' | 'mean' | 'min' | 'max' | 'count';
|
|
9
|
+
/** An app's own aggregate, over the column's numbers (never empty). */
|
|
10
|
+
export type GridAggregateFn = (values: number[]) => number | null;
|
|
11
|
+
export type GridAggregate = GridAggregateName | GridAggregateFn;
|
|
12
|
+
/** The finite numbers among `values`. */
|
|
13
|
+
export declare function numericValues(values: readonly unknown[]): number[];
|
|
14
|
+
/** `kind` over the numbers among `values`; with none, 0 for `count` and `null` otherwise. */
|
|
15
|
+
export declare function aggregate(kind: GridAggregate, values: readonly unknown[]): number | null;
|
|
16
|
+
//# sourceMappingURL=aggregates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregates.d.ts","sourceRoot":"","sources":["../../src/data-grid/aggregates.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAEzE,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC;AAElE,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,eAAe,CAAC;AAEhE,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,EAAE,CAKlE;AAWD,6FAA6F;AAC7F,wBAAgB,SAAS,CACxB,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,SAAS,OAAO,EAAE,GACxB,MAAM,GAAG,IAAI,CAIf"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GridCellStatus, GridMsg } from './types';
|
|
2
|
+
/** How long saved announcements merge, in ms. */
|
|
3
|
+
export declare const SAVED_MERGE_MS = 1000;
|
|
4
|
+
/** "3 changes saved". */
|
|
5
|
+
export declare function savedMessage(count: number): GridMsg;
|
|
6
|
+
/** The saved count to announce: added to the last one when it was under a second ago. */
|
|
7
|
+
export type SavedTally = {
|
|
8
|
+
count: number;
|
|
9
|
+
at: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function mergeSaved(last: SavedTally | null, count: number, now: number): SavedTally;
|
|
12
|
+
/** A save that failed (the network): it is retried, or paused after 3 failures. */
|
|
13
|
+
export declare function saveFailedMessage(paused?: boolean): GridMsg;
|
|
14
|
+
/** "2 changes refused. F8: next problem": the server said no to them. */
|
|
15
|
+
export declare function refusedMessage(count: number): GridMsg;
|
|
16
|
+
/** "1 conflict: changed by someone else. F8: next problem". */
|
|
17
|
+
export declare function conflictMessage(count: number): GridMsg;
|
|
18
|
+
/** "Not cleared: 1 cell changed meanwhile": a Delete whose check answered too late. */
|
|
19
|
+
export declare function clearDroppedMessage(count: number): GridMsg;
|
|
20
|
+
/** "6 cells pasted". */
|
|
21
|
+
export declare function pasteAppliedMessage(count: number): GridMsg;
|
|
22
|
+
/** "Nothing pasted: 2 problems". */
|
|
23
|
+
export declare function pasteRefusedMessage(count: number): GridMsg;
|
|
24
|
+
/** "Undone: 3 cells", or "Undone: 2 cells, 1 skipped" when some had moved on. */
|
|
25
|
+
export declare function undoneMessage(count: number, skipped?: number): GridMsg;
|
|
26
|
+
/** "Redone: 3 cells". */
|
|
27
|
+
export declare function redoneMessage(count: number): GridMsg;
|
|
28
|
+
/** "Not saved", "Saving", "Saved", "Error", "Conflict". */
|
|
29
|
+
export declare function cellStateText(status: GridCellStatus): GridMsg;
|
|
30
|
+
/** "Locked", or "Locked: Grades are final" with the app's reason. */
|
|
31
|
+
export declare function lockedText(reason?: string): GridMsg;
|
|
32
|
+
/** "Read-only", or "Read-only: Left the course". */
|
|
33
|
+
export declare function readOnlyText(reason?: string): GridMsg;
|
|
34
|
+
/** "Calculated": a computed column's cell. */
|
|
35
|
+
export declare function computedText(): GridMsg;
|
|
36
|
+
/**
|
|
37
|
+
* The grid's description: how to move, edit and leave it ("Cmd" on macOS).
|
|
38
|
+
* With `tabBehavior: 'exit'`, Tab leaves the grid at once.
|
|
39
|
+
*/
|
|
40
|
+
export declare function exitHintText(platform?: 'mac' | 'other', tabBehavior?: 'cells' | 'exit'): GridMsg;
|
|
41
|
+
//# sourceMappingURL=announce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announce.d.ts","sourceRoot":"","sources":["../../src/data-grid/announce.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvD,iDAAiD;AACjD,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,yBAAyB;AACzB,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMnD;AAED,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,wBAAgB,UAAU,CACzB,IAAI,EAAE,UAAU,GAAG,IAAI,EACvB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACT,UAAU,CAKZ;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,MAAM,UAAQ,GAAG,OAAO,CAUzD;AAKD,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUtD;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAU1D;AAED,wBAAwB;AACxB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAM1D;AAED,oCAAoC;AACpC,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAU1D;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,OAAO,CAYjE;AAED,yBAAyB;AACzB,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMpD;AAUD,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAE7D;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAMnD;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAUrD;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,GAAE,KAAK,GAAG,OAAiB,EACnC,WAAW,GAAE,OAAO,GAAG,MAAgB,GACrC,OAAO,CAaT"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The changes' small immutable operations, shared by `changes.ts`,
|
|
3
|
+
* `rebase.ts` and the save answers.
|
|
4
|
+
*/
|
|
5
|
+
import type { GridChange, GridChanges } from './change-types';
|
|
6
|
+
import type { GridCellKey, GridVersion } from './types';
|
|
7
|
+
/** `change` without the fields named. */
|
|
8
|
+
export declare function omit<K extends keyof GridChange>(change: GridChange, ...keys: K[]): GridChange;
|
|
9
|
+
export declare function withEntry(changes: GridChanges, change: GridChange): GridChanges;
|
|
10
|
+
export declare function without(changes: GridChanges, key: GridCellKey): GridChanges;
|
|
11
|
+
/**
|
|
12
|
+
* The overlay a cell keeps once nothing is left to send: it shows `value`,
|
|
13
|
+
* the server's, until the app's rows hold it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function cleanOverlay(change: GridChange, value: unknown, version: GridChange['baseVersion']): GridChange;
|
|
16
|
+
/** Whether `change` keeps a version a save or a conflict replaced. */
|
|
17
|
+
export declare function hasStaleVersion(change: GridChange): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a refetch read at `version` is older than what `change` knows.
|
|
20
|
+
* `0` and `undefined` (a cell that does not exist yet) are the same read
|
|
21
|
+
* here: an app may report a missing cell either way.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isStaleRead(change: GridChange, version: GridVersion | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* `change` moved to `version`, the one it had kept as `staleVersion`; the
|
|
26
|
+
* same change when the version did not move.
|
|
27
|
+
*/
|
|
28
|
+
export declare function moveVersion(change: GridChange, version: GridVersion | undefined): GridChange;
|
|
29
|
+
//# sourceMappingURL=change-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-ops.d.ts","sourceRoot":"","sources":["../../src/data-grid/change-ops.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGxD,yCAAyC;AACzC,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,UAAU,EAC9C,MAAM,EAAE,UAAU,EAClB,GAAG,IAAI,EAAE,CAAC,EAAE,GACV,UAAU,CAIZ;AAED,wBAAgB,SAAS,CACxB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,GAChB,WAAW,CAEb;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,WAAW,CAK3E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,UAAU,CAAC,aAAa,CAAC,GAChC,UAAU,CASZ;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAOT;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAC1B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,GAAG,SAAS,GAC9B,UAAU,CAGZ"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the changes add up to: the counts a status bar and a save button
|
|
3
|
+
* show, and the entries in the order they were edited.
|
|
4
|
+
*/
|
|
5
|
+
import type { GridChange, GridChanges } from './change-types';
|
|
6
|
+
/** The entries, oldest edit first. */
|
|
7
|
+
export declare function changeList(changes: GridChanges): GridChange[];
|
|
8
|
+
export type GridChangeCounts = {
|
|
9
|
+
/** Not saved yet, and valid: `dirty` and `saving`. */
|
|
10
|
+
dirty: number;
|
|
11
|
+
saving: number;
|
|
12
|
+
errors: number;
|
|
13
|
+
conflicts: number;
|
|
14
|
+
};
|
|
15
|
+
/** Every count in one pass (`onChangesChange`); a `clean` overlay counts for nothing. */
|
|
16
|
+
export declare function changeCounts(changes: GridChanges): GridChangeCounts;
|
|
17
|
+
/** Cells not saved yet (`dirty` or `saving`). */
|
|
18
|
+
export declare function dirtyCount(changes: GridChanges): number;
|
|
19
|
+
export declare function errorCount(changes: GridChanges): number;
|
|
20
|
+
export declare function conflictCount(changes: GridChanges): number;
|
|
21
|
+
/** Whether a cell in error or in conflict blocks a batch save. */
|
|
22
|
+
export declare function blocking(changes: GridChanges): boolean;
|
|
23
|
+
/** Whether anything is left to save or resolve (a saved cell or an overlay is not). */
|
|
24
|
+
export declare function hasPendingChanges(changes: GridChanges): boolean;
|
|
25
|
+
//# sourceMappingURL=change-select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-select.d.ts","sourceRoot":"","sources":["../../src/data-grid/change-select.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE9D,sCAAsC;AACtC,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,EAAE,CAE7D;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,gBAAgB,CASnE;AAED,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAEvD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAEvD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE1D;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAGtD;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAI/D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the grid's changes, one entry per changed cell, shared by
|
|
3
|
+
* `changes.ts` and the save queue. An entry also outlives its save as an
|
|
4
|
+
* overlay: the cell keeps showing the value saved (or the server's, on
|
|
5
|
+
* "Keep theirs") until the app's rows hold it.
|
|
6
|
+
*/
|
|
7
|
+
import type { GridCellKey, GridCellStatus, GridMsg, GridVersion } from './types';
|
|
8
|
+
/** What the server holds instead, when a change conflicts with it. */
|
|
9
|
+
export type GridConflict = {
|
|
10
|
+
value: unknown;
|
|
11
|
+
version: GridVersion | undefined;
|
|
12
|
+
/** Who changed it ("Ada Lovelace"). */
|
|
13
|
+
by?: string;
|
|
14
|
+
/** When (as the server words it). */
|
|
15
|
+
at?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A change's status: a cell's (`dirty`, `saving`, `saved`, `error`,
|
|
19
|
+
* `conflict`), or `clean`: an overlay with nothing to send and no mark,
|
|
20
|
+
* kept until the app's rows catch up.
|
|
21
|
+
*/
|
|
22
|
+
export type GridChangeStatus = GridCellStatus | 'clean';
|
|
23
|
+
/** One changed cell. */
|
|
24
|
+
export type GridChange = {
|
|
25
|
+
key: GridCellKey;
|
|
26
|
+
rowId: string;
|
|
27
|
+
columnId: string;
|
|
28
|
+
/** The value the server holds, as far as the grid knows: going back to it ends the edit. */
|
|
29
|
+
base: unknown;
|
|
30
|
+
/** The version `base` is at, this cell's own (or its row's). */
|
|
31
|
+
baseVersion: GridVersion | undefined;
|
|
32
|
+
/** The value the cell shows and sends. */
|
|
33
|
+
value: unknown;
|
|
34
|
+
/** The text typed, kept with a local error ("25" when the max is 20). */
|
|
35
|
+
text?: string;
|
|
36
|
+
status: GridChangeStatus;
|
|
37
|
+
error?: GridMsg;
|
|
38
|
+
/** Kept while it is unresolved, under an error typed over it too. */
|
|
39
|
+
conflict?: GridConflict;
|
|
40
|
+
/**
|
|
41
|
+
* `base` came from a save or the server, not from the app's rows: the
|
|
42
|
+
* entry stays (as `clean` at the least) until `rebase` sees the rows
|
|
43
|
+
* hold it.
|
|
44
|
+
*/
|
|
45
|
+
overlay?: true;
|
|
46
|
+
/**
|
|
47
|
+
* The version a save or a conflict replaced (`baseVersion` before it).
|
|
48
|
+
* A refetch read at it is older than what the grid knows: `rebase`
|
|
49
|
+
* leaves the entry as it is, until `expireSaved` drops the guard on an
|
|
50
|
+
* overlay. Present or not (`undefined` is a version: the cell did not
|
|
51
|
+
* exist yet), so test it with `hasStaleVersion`; an `undefined` one is
|
|
52
|
+
* lost through JSON, so keep the changes in memory, not serialised.
|
|
53
|
+
*/
|
|
54
|
+
staleVersion?: GridVersion | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* What the call in flight sent for this cell. Its status stays `saving`
|
|
57
|
+
* until the answer, unless it is edited meanwhile.
|
|
58
|
+
*/
|
|
59
|
+
inFlight?: {
|
|
60
|
+
value: unknown;
|
|
61
|
+
};
|
|
62
|
+
/** When it was saved (`status: 'saved'`), or theirs kept: `expireSaved` reads it. */
|
|
63
|
+
savedAt?: number;
|
|
64
|
+
/** The edit's order: later edits have a higher one. */
|
|
65
|
+
seq: number;
|
|
66
|
+
};
|
|
67
|
+
/** The changes by cell key. A plain object, so a reducer's state can hold it. */
|
|
68
|
+
export type GridChanges = {
|
|
69
|
+
readonly [key: GridCellKey]: GridChange;
|
|
70
|
+
};
|
|
71
|
+
export declare const NO_CHANGES: GridChanges;
|
|
72
|
+
//# sourceMappingURL=change-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-types.d.ts","sourceRoot":"","sources":["../../src/data-grid/change-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EACX,WAAW,EACX,cAAc,EACd,OAAO,EACP,WAAW,EACX,MAAM,SAAS,CAAC;AAEjB,sEAAsE;AACtE,MAAM,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,uCAAuC;IACvC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,OAAO,CAAC;AAExD,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG;IACxB,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,IAAI,EAAE,OAAO,CAAC;IACd,gEAAgE;IAChE,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,0CAA0C;IAC1C,KAAK,EAAE,OAAO,CAAC;IACf,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;OAIG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC;IACf;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9B,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,iFAAiF;AACjF,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,GAAG,UAAU,CAAA;CAAE,CAAC;AAEtE,eAAO,MAAM,UAAU,EAAE,WAA+B,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { GridChanges } from './change-types';
|
|
2
|
+
import type { GridCellKey, GridMsg, GridVersion } from './types';
|
|
3
|
+
/** How long a saved cell shows it, in ms. */
|
|
4
|
+
export declare const SAVED_EXPIRE_MS = 1500;
|
|
5
|
+
/** An edit: the value, or the text the column could not accept and why. */
|
|
6
|
+
export type GridEditInput = {
|
|
7
|
+
key: GridCellKey;
|
|
8
|
+
/** The cell's value in the app's row, kept as the base by a first edit. */
|
|
9
|
+
base: unknown;
|
|
10
|
+
/** The version the row gives that cell now (`cellVersion`). */
|
|
11
|
+
baseVersion: GridVersion | undefined;
|
|
12
|
+
} & ({
|
|
13
|
+
value: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
text: string;
|
|
16
|
+
error: GridMsg;
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* `changes` with an edit applied. A first edit keeps `base` and
|
|
20
|
+
* `baseVersion`; later ones keep the entry's. An edit back to the base
|
|
21
|
+
* drops the entry (an overlay goes back to `clean`), unless a call is
|
|
22
|
+
* sending it or it is in conflict. An edit with an `error` keeps its text,
|
|
23
|
+
* flagged, and is never sent. A conflict outlives any edit, an invalid one
|
|
24
|
+
* included, until it is resolved: it is never sent at the old version.
|
|
25
|
+
*/
|
|
26
|
+
export declare function editCell(changes: GridChanges, input: GridEditInput): GridChanges;
|
|
27
|
+
/**
|
|
28
|
+
* A conflict resolved. `'theirs'` keeps theirs as a `clean` overlay, at
|
|
29
|
+
* their version: nothing is sent, and the cell shows it until the app's
|
|
30
|
+
* rows hold it. `'mine'` rebases the change on theirs, version included,
|
|
31
|
+
* to be sent again (still flagged when an invalid text was typed over the
|
|
32
|
+
* conflict). Given `now`, theirs' guard against a stale read lasts until
|
|
33
|
+
* `expireSaved`, as a saved value's does.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveConflict(changes: GridChanges, key: GridCellKey, choice: 'theirs' | 'mine', now?: number): GridChanges;
|
|
36
|
+
/**
|
|
37
|
+
* `changes` once the saved marks `after` ms old or more are gone. A saved
|
|
38
|
+
* value the app's rows do not hold yet stays, as a `clean` overlay, and so
|
|
39
|
+
* does theirs kept: from then on a refetch that reads a different version
|
|
40
|
+
* wins, even one at the version the save replaced (`staleVersion` goes).
|
|
41
|
+
* Versions are opaque and may come back (a deleted cell reads at `0`
|
|
42
|
+
* again), so the guard lasts only that long. A dirty or conflicting entry
|
|
43
|
+
* keeps its `staleVersion`: its own value wins anyway.
|
|
44
|
+
*/
|
|
45
|
+
export declare function expireSaved(changes: GridChanges, now: number, after?: number): GridChanges;
|
|
46
|
+
//# sourceMappingURL=changes.d.ts.map
|