flexdesk 0.17.0 → 0.17.1
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 +19 -0
- package/README.md +221 -3
- package/canvas.js +18 -0
- package/charts.js +21 -0
- package/core.js +14 -0
- package/css/flexdesk.css +24 -0
- package/dist/charts.js +3 -5
- package/dist/charts.js.map +1 -1
- package/dist/{chunk-Q36EPT2R.js → chunk-LWV23YVE.js} +1614 -138
- package/dist/chunk-LWV23YVE.js.map +7 -0
- package/dist/{chunk-MCIXFNVF.js → chunk-RMTRWWEO.js} +2 -2
- package/dist/flexdesk.css +24 -0
- package/dist/flow.js +2 -2
- package/dist/flow.js.map +2 -2
- package/dist/tiles.js +2 -2
- package/dist/widgets.js +43 -5
- package/dist/widgets.js.map +1 -1
- package/editor.js +20 -0
- package/flow.js +21 -0
- package/host.js +14 -0
- package/package.json +10 -1
- package/src/flow/canvas_editor/editor.js +1 -1
- package/src/ui/components/cell_editor_host.js +100 -18
- package/src/ui/components/cell_editors.js +480 -0
- package/src/ui/components/cell_grid.js +121 -8
- package/src/ui/components/cell_paste.js +243 -0
- package/src/ui/components/data_table.js +96 -10
- package/src/ui/components/staged_edits.js +19 -1
- package/src/ui/components/tsv.js +74 -0
- package/tiles.js +27 -0
- package/widgets.js +69 -0
- package/wm.js +28 -0
- package/dist/chunk-Q36EPT2R.js.map +0 -7
- package/dist/chunk-QWQJUDPD.js +0 -610
- package/dist/chunk-QWQJUDPD.js.map +0 -7
- /package/dist/{chunk-MCIXFNVF.js.map → chunk-RMTRWWEO.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.1 (2026-10-21)
|
|
4
|
+
|
|
5
|
+
A patch release: everything below is additive. Every entry is documented in a
|
|
6
|
+
README section headed "(0.17.1)"; each entry links to it.
|
|
7
|
+
|
|
8
|
+
### Added and changed
|
|
9
|
+
|
|
10
|
+
- [Built-in cell editors](README.md#built-in-cell-editors-0171): an editable DataTable column opens the editor for its type (`text`, `multitext`, `number`, `bool`, `choice`, `datetime`), each with a parse that refuses what it cannot read; `editors` replaces one per type or per column, with `editTypes`, `locale` and `editorMessages`. The editors and their parsers (`CELL_EDITORS`, `createNumberEditor`, `parseNumber`, `parseDateTime` and the rest of `cell_editors.js`) are exported from `@flexdesk/widgets`.
|
|
11
|
+
- [Copying cells as tab-separated values](README.md#copying-cells-as-tab-separated-values-0171): in cell mode Ctrl+C copies the selected cell range as the tab-separated text Excel and Google Sheets paste into cells; `toTSV` and `tsvCell` (from `@flexdesk/widgets`) write it.
|
|
12
|
+
- [Pasting a block from Excel](README.md#pasting-a-block-from-excel-0171): `pasteable: true` beside `staged` pastes a tab-separated block at the cursor cell, each value read by its column's editor, staged as one undo entry, with column and row overrun handling; `fromTSV` (from `@flexdesk/widgets`) reads it.
|
|
13
|
+
- [Building a modeler](README.md#building-a-modeler-0171): the canvas editor's modeler options shown working together, with `demo/flow_modeler.html` (linked from `demo/index.html`).
|
|
14
|
+
- [The `development` condition resolves in the installed package](README.md#the-development-condition-resolves-in-the-installed-package-0171): the nine root entry barrels are in `files`, so the `exports` map's `development` targets ship (BUG-0032). An `import` still resolves to `dist/`.
|
|
15
|
+
|
|
16
|
+
### Removed or renamed exports
|
|
17
|
+
|
|
18
|
+
None. Compared with 0.17.0, across every entry point (`core`, `widgets`, `wm`,
|
|
19
|
+
`host`, `canvas`, `flow`, `charts`, `tiles`, `editor`) and the package
|
|
20
|
+
`exports` map, exports were only added.
|
|
21
|
+
|
|
3
22
|
## 0.17.0 (2026-10-21)
|
|
4
23
|
|
|
5
24
|
Every feature below is documented in a README section headed "(0.17.0)"; each
|
package/README.md
CHANGED
|
@@ -1324,10 +1324,10 @@ grid.getCellSelection(); // { anchor, focus, range }
|
|
|
1324
1324
|
| The keys (focused table) | Arrows move the cursor (Shift extends the range). Ctrl+arrow jumps to the edge of the block of filled cells, as a spreadsheet does. Home/End go to the row's first and last cell (Ctrl: the page's first and last cell). Tab moves right and wraps to the next row (Shift+Tab back); on the last cell (Shift+Tab: the first) the table does not take the key, so focus moves on out of the table. ArrowDown on the last row stays put. Ctrl+A selects every cell of the page; Escape collapses the range onto the cursor. The cursor stays on the page drawn and is scrolled into view; under `virtualize` its row is drawn. |
|
|
1325
1325
|
| `getCellSelection()` | `{anchor, focus, range}`. A cell is `{row, col, rowIdx}`: `row` is the position in the order shown (as `getDisplayedRows()` lists them, after sort and filter), `col` the data column, `rowIdx` the original row index. `range` is `{top, bottom, left, right, rows, columns}`: positions, the data columns at its edges, and the original row indices and data columns it covers (a hidden column inside it is not one of them). Null outside cell mode. |
|
|
1326
1326
|
| `setCellSelection(focus, anchor?)` | Puts the cursor on `{row, col}`, or selects from `anchor` to it; `null` clears. The row is brought into view. |
|
|
1327
|
-
| `getSelection()`, `setSelection()
|
|
1327
|
+
| `getSelection()`, `setSelection()` | Still about rows: `getSelection()` returns the rows the range touches and `onSelectionChange` hears them. `setSelection(indices)` selects the cells of those rows, first to last as shown. Ctrl+C and the copy menu copy cells, not rows, from 0.17.1 (below). |
|
|
1328
1328
|
| `onCellSelectionChange(sel)` | Called with `getCellSelection()` after every change. |
|
|
1329
1329
|
| `editable` | Which cells open an editor: `true`, an array of data column indices, or `(colIdx, row, rowIdx) => boolean`. Needs cell mode. F2, Enter, a double-click or a printable key opens it on the cursor cell (a printable key replaces the value with that character). In the editor Enter commits and moves down, Tab commits and moves right (Shift: up, left), Escape restores the cell and calls nothing; the focus leaving it commits without moving. `editCell(row, col)` opens one from code; `isEditing()` says whether one is open. |
|
|
1330
|
-
| `editors: {[colIdx \| type]: factory}` | The editor for a column, looked up by column index and then by type (`columnTypes`, else the detected `num`/`text`). A factory gets `{value, row, rowIdx, colIdx, column: {index, header, type}, initialText, commit(value), cancel()}` and returns an element or `{el, getValue?, focus?, destroy?}`. An input, select or textarea commits its `value` on Enter and Tab; any other editor calls `commit(value)` itself.
|
|
1330
|
+
| `editors: {[colIdx \| type]: factory}` | The editor for a column, looked up by column index and then by type (`columnTypes`, else the detected `num`/`text`). A factory gets `{value, row, rowIdx, colIdx, column: {index, header, type}, initialText, commit(value), cancel()}` and returns an element or `{el, getValue?, focus?, destroy?}`. An input, select or textarea commits its `value` on Enter and Tab; any other editor calls `commit(value)` itself. Without one, a column gets the built-in editor for its type, else a text box (`createTextEditor`); see [Built-in cell editors](#built-in-cell-editors-0171) for the editors, the `editTypes`, `locale` and `editorMessages` options and what a factory is told since 0.17.1. |
|
|
1331
1331
|
| `onCellCommit({rowIdx, colIdx, value, previous})` | Called exactly once per commit; `previous` is the value the row holds, and a value left as the cell showed it is no commit. Return nothing or `{ok: true}` (or a Promise of it) and the value is written into the row the table holds for `rowIdx` when it settles (`rows[rowIdx][colIdx]`, so a row put in by `updateRow` meanwhile keeps its other cells) and drawn. `{ok: false, error}`, a rejection or a throw leaves the typed value in the cell, drawn `twm-dt-cell--error` with the message as its `title` and `data-error`, until a later commit of that cell succeeds, the value the row holds is committed back (no call: nothing to save), or new rows arrive. While the Promise is out the cell is `twm-dt-cell--pending`. The editor always opens with the value the cell shows, a refused or pending one included. |
|
|
1332
1332
|
| While an editor is open | `render()`, and so `setData`, a sort or a page, waits and runs when the editor closes, so what is being typed is never thrown away. A commit that settles while another editor is open in its row marks its cell at once and draws the row when that editor closes. `updateRow` on the row the editor is in stores the data at once (`config.rows`, the window's row list) and returns `true`, and the row is drawn with it when the editor closes; the editor's cell is left as it is. A row that scrolls out of the virtual window commits its editor first. An editor with no `getValue` (a `{el}` around an element that is not an input, select or textarea) has no value the table can read: Enter, Tab and the focus leaving do nothing, and its row scrolling out cancels it, so what it holds is lost unless it has called `commit(value)` itself. |
|
|
1333
1333
|
| `headerMenu(colIdx, ctx) => items` | Items for the header's right-click menu, asked for the data column right-clicked (`ctx` is `{colIdx, header, hiddenColumns, table}`). Items are `{label, action, icon?, disabled?, danger?, title?}` or `{separator: true}`. Choosing one calls `onHeaderMenuAction(action, colIdx)`. With `columnChooser` the chooser's items follow behind a separator. `[]` or `null` shows only what would show without the hook: the chooser, or the page's own menu. Works in either selection mode. |
|
|
@@ -1338,6 +1338,100 @@ DOM), `CellEditorHost` (one cell's editor, opened and closed exactly once) and
|
|
|
1338
1338
|
`createTextEditor` (the default text box). The table's own half of cell mode
|
|
1339
1339
|
is internal.
|
|
1340
1340
|
|
|
1341
|
+
#### Built-in cell editors (0.17.1)
|
|
1342
|
+
|
|
1343
|
+
An editable column opens the editor for its type: `text`, `multitext`,
|
|
1344
|
+
`number`, `bool`, `choice` or `datetime`. Each reads what is typed with a
|
|
1345
|
+
parse, and a value that does not parse is refused on the cell. `editors`
|
|
1346
|
+
replaces any of them, for its type or its column only.
|
|
1347
|
+
|
|
1348
|
+
```js
|
|
1349
|
+
const grid = DataTable.mount(host, {
|
|
1350
|
+
headers: ['Item', 'Qty', 'Colour', 'Note'],
|
|
1351
|
+
rows,
|
|
1352
|
+
selectionMode: 'cells',
|
|
1353
|
+
editable: true,
|
|
1354
|
+
editTypes: [null, 'number', { type: 'choice', options: ['red', 'green'] }, 'multitext'],
|
|
1355
|
+
locale: 'de-DE', // '1.234,56' is 1234.56
|
|
1356
|
+
editorMessages: { number: 'Keine Zahl' },
|
|
1357
|
+
editors: { 3: myNoteEditor }, // by column index or by type
|
|
1358
|
+
onCellCommit: ({ value }) => save(value), // Qty commits a Number
|
|
1359
|
+
});
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
| Option | What it does |
|
|
1363
|
+
|---|---|
|
|
1364
|
+
| The built-in editors | `text` a text box; `multitext` a text area (Enter is a new line, Ctrl/Cmd+Enter commits); `number` a text box that commits a Number; `bool` a checkbox that commits `true`/`false` when ticked or unticked (a space that opens it ticks it over); `choice` an `AutocompleteField` over the column's `options` that commits the option's value; `datetime` a text box that commits a Date. A blank number, date or choice commits `null`. A value opened and left as it was commits nothing. |
|
|
1365
|
+
| `editTypes` | Each column's editor type, by position (an array) or by data column index (an object): a name (`'text'`, `'multitext'`, `'number'`, `'bool'`, `'choice'`, `'datetime'`, or any name `editors` is keyed by) or `{type, options?, allowOther?, rows?}`. Unset, it is the column's `columnTypes` type, else the detected one, so a numeric column opens the number editor and a `date` column the datetime editor. `choice` offers `options` (values, or `{value, label}`), filtered as you type; a pick commits its value, and typed text matching an option's label or value (any case) commits that value. Other text is refused unless `allowOther`, when it is the value. With the list open, Escape closes the list only. `rows` is a `multitext` area's height in lines (3). Only editing reads `editTypes`: drawing, sorting and filtering are as without it. |
|
|
1366
|
+
| `locale` | The locale the number and datetime editors read and show text in (Intl's; unset: the runtime's). `'1.234,56'` is 1234.56 under `de-DE` and `'1,234.56'` is under `en-US`, and `'1 234,5'` is 1234.5 under `fr-FR` (any space, a no-break one included, where the locale groups with a space); a group separator anywhere but between groups of three digits, or a space inside the number anywhere else, is refused, never misread. Spaces around the text are ignored. A number opens without groups, in the locale's decimal separator. A datetime reads ISO 8601 (`2026-12-31`, `2026-12-31T08:30`, with `Z` or an offset an exact instant) in any locale, and day, month and year in the locale's order with a four-digit year and an optional time (`31/12/2026 08:30` under `en-GB`); when the locale's order makes no date but the day-month swap does, the swap is read. A date with no zone is in `dateTimeZone`'s. It opens as ISO. |
|
|
1367
|
+
| A value that does not parse | `abc` in a number, `31/02/2026` in a date, a choice outside the options: Enter and Tab are refused, the editor stays open, and the cell carries the message (`twm-dt-cell--error`, `title`, `data-error`) until the next keystroke. Nothing is committed. If the focus leaves (or the row scrolls out of the window) with it, the editor closes and the cell keeps the text, marked as a refused commit is, and `onCellCommit` is not called. Under `staged` nothing is staged; anything staged into the cell afterwards (a typed value, a fill, the value the row holds) ends the refusal. The words are `EDITOR_MESSAGES`, overridden by `editorMessages: {number, datetime, bool, choice}`. |
|
|
1368
|
+
| `editors` (0.17.1) | The editor for a column, looked up by column index and then by type (`editTypes`, else `columnTypes`, else the detected `num`/`text`; as named, then as the built-in editors name it: `num` is `number`, `date` is `datetime`). A factory gets `{value, row, rowIdx, colIdx, column: {index, header, type, ...the editTypes entry}, locale, utc, messages, initialText, commit(value, move?), cancel()}` and returns an element or `{el, getValue?, parse?, multiline?, focus?, destroy?}`. An input, select or textarea commits its `value` on Enter and Tab; any other editor calls `commit(value)` itself. With `parse()` (`=> {ok: true, value} \| {ok: false, error}`) Enter, Tab and the focus leaving commit what it reads, and a refusal is handled as the built-in editors' is. `multiline` keeps Enter for a new line (Ctrl/Cmd+Enter commits). An injected editor replaces the built-in one for its type (or its column) only. Without one, a column gets the built-in editor for its type, else a text box (`createTextEditor`). |
|
|
1369
|
+
|
|
1370
|
+
Exported from cell_editors.js for a consumer that builds its own grid or
|
|
1371
|
+
reads pasted text: the six editors as `CELL_EDITORS` (`{text, multitext,
|
|
1372
|
+
number, bool, choice, datetime}`, each `{create, parse}`: `create` a factory,
|
|
1373
|
+
`parse(text, {locale, utc, options, allowOther, messages})` the same reading
|
|
1374
|
+
of plain text, for a paste) with their parts: `parseNumber`, `parseDateTime`,
|
|
1375
|
+
`parseBool`, `parseChoice`, `formatNumberForEdit`, `formatDateTimeForEdit`,
|
|
1376
|
+
`numberSeparators`, `dateOrder`, `normalizeOptions`, `editorTypeOf`,
|
|
1377
|
+
`EDITOR_MESSAGES` and `createNumberEditor`, `createMultiTextEditor`,
|
|
1378
|
+
`createBoolEditor`, `createChoiceEditor`, `createDateTimeEditor`.
|
|
1379
|
+
`CellEditorHost` takes an `onRefuse(session, error, text, reason)` beside
|
|
1380
|
+
`onCommit` and `onCancel`, called when an editor closes holding a value that
|
|
1381
|
+
does not parse (without it, such a close is a cancel).
|
|
1382
|
+
|
|
1383
|
+
#### Copying cells as tab-separated values (0.17.1)
|
|
1384
|
+
|
|
1385
|
+
In cell mode Ctrl+C copies the cells selected, as the tab-separated text Excel
|
|
1386
|
+
and Google Sheets paste into cells.
|
|
1387
|
+
|
|
1388
|
+
| What | What it does |
|
|
1389
|
+
|---|---|
|
|
1390
|
+
| Ctrl+C, the copy menu, `copyToClipboard()` | With `copyable` (the default), copy the CELLS selected, not their rows: a 3×4 range is 3 lines of 4 tab-separated values, in the order shown after sort and filter, its shown columns only (a hidden column inside it is left out), with no header line. With no range beyond the cursor, the cursor cell's value alone. A cell is its RAW value, not the text it shows: no `formatValue`, typed-column format, rounding or `nullDisplay` (a Price drawn `1,234.50`, value 1234.5, copies as `1234.5`); the value the cell holds now, a staged, pending or refused one included; `null` is empty. Dates copy as ISO 8601: a `Date` as `toISOString()`, in a column declared `'date'` as its day `YYYY-MM-DD` in the table's zone (`dateTimeZone`); a number in a declared `'date'`/`'datetime'` column is read as epoch ms the same way; text is copied as it is. A value holding a tab, a newline or a quote is quoted the way Excel quotes it (`"say ""hi"""`), so Excel and Google Sheets paste it back into one cell. Cell mode has no CSV copy: the right-click menu's copy item is "Copy Cells" alone (the same text as Ctrl+C), Ctrl+Shift+C is not bound (the key is left to the page), and `copyToClipboard('csv')` copies nothing and warns. Without cell mode, Ctrl+C, Ctrl+Shift+C and the copy menu ("Copy Row(s)", "Copy as CSV") copy the selected rows with a header line, as in 0.16. |
|
|
1391
|
+
|
|
1392
|
+
`toTSV(matrix)` and `tsvCell(value)` write tab-separated text with Excel's
|
|
1393
|
+
quoting (a cell holding a tab, a newline or a quote is quoted and its quotes
|
|
1394
|
+
doubled; `null` is empty; no newline after the last row): the writer cell mode
|
|
1395
|
+
copies with.
|
|
1396
|
+
|
|
1397
|
+
#### Pasting a block from Excel (0.17.1)
|
|
1398
|
+
|
|
1399
|
+
With `pasteable: true` beside `staged`, Ctrl+V on the focused table pastes the
|
|
1400
|
+
tab-separated block Excel and Google Sheets copy at the CURSOR cell: it fills
|
|
1401
|
+
right over the columns shown and down over the rows in the order shown, each
|
|
1402
|
+
value read by its column's editor, and every cell is STAGED as one undo entry.
|
|
1403
|
+
|
|
1404
|
+
```js
|
|
1405
|
+
const grid = DataTable.mount(host, {
|
|
1406
|
+
headers: ['Item', 'Qty', 'When'],
|
|
1407
|
+
rows,
|
|
1408
|
+
selectionMode: 'cells',
|
|
1409
|
+
editable: (colIdx, row) => colIdx !== 0 || !row.locked,
|
|
1410
|
+
editTypes: [null, 'number', 'datetime'],
|
|
1411
|
+
staged: true,
|
|
1412
|
+
pasteable: true,
|
|
1413
|
+
onPasteOverrun: ({ rows }) => confirm(`Add ${rows} row(s)?`),
|
|
1414
|
+
onRowsAppend: ({ rows, errors }) => store.addRows(rows),
|
|
1415
|
+
onCommit: ({ edits }) => api.saveBatch(edits),
|
|
1416
|
+
});
|
|
1417
|
+
await grid.paste('Bolt\t4\t2026-12-31\nNut\t9\t2027-01-02'); // what Ctrl+V does
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
| Option or method | What it does |
|
|
1421
|
+
|---|---|
|
|
1422
|
+
| `pasteable: true` | Needs `staged` (ignored with a warning without it). Ctrl+V on the focused table takes the browser's `paste` event (its `text/plain`, read by `fromTSV`); a paste inside an open editor or another field is that field's. The block lands at the cursor cell; a hidden column is stepped over. Each value is read by the parse of its column's editor: the built-in editor's for its type (`editTypes`, else `columnTypes`, else the detected one; `locale`, the zone and `editorMessages` as an editor reads them), or an injected `editors` factory's own `parse` property (`factory.parse = (text, ctx) => ({ok: true, value} \| {ok: false, error})`), else the text as it is. Every cell that reads is staged as ONE undo entry: one Ctrl+Z takes the whole paste back. A value equal to what the row holds is no change. The range then covers the block, the cursor on the cell it began at. Nothing is pasted while an editor is open, a commit is out or another paste waits. |
|
|
1423
|
+
| Columns past the last | Dropped. A console warning and a `'warn'` toast (`toast:show` on `services.eventBus`) name how many: "3 column(s) past the last column were not pasted." |
|
|
1424
|
+
| Rows past the last loaded row | The last loaded row is the last row in the order shown (filtered and sorted; for a server's page, the last row it sent). `onPasteOverrun({rows: n})` is asked first, `n` the rows past it. Anything but `true` (or a Promise of `true`) stages NOTHING; without the hook, such a paste stages nothing and warns. `true` hands the extra rows to `onRowsAppend({rows, errors})` and, once that settles, stages the rows that fit: `rows` are data rows (as long as `headers`, every pasted column read by its parse, the others `null`), and `errors` the values that did not read (`{row, colIdx, text, error}`, `row` an index into `rows`; that cell is `null`). Adding them is the consumer's: the table holds no row it was not given. A consumer that adds them with `setData({rows})` (the old rows first, the new ones after) keeps the paste's staged cells, because they are staged after it. Without `onRowsAppend` the extra rows are dropped with a warning. |
|
|
1425
|
+
| A value that does not parse | `abc` in a number column: its cell is marked as an editor's refusal is (`twm-dt-cell--error`, the message as `title` and `data-error`, the pasted text kept in the cell) and nothing is staged in it; the rest of the block still stages. Anything staged into the cell afterwards ends the mark. |
|
|
1426
|
+
| A cell `editable` refuses | Skipped: nothing is staged in it and its row is untouched. It is drawn `twm-dt-cell--paste-skipped` until the selection moves, the next paste or fill, or new rows. |
|
|
1427
|
+
| `paste(text)` | What Ctrl+V does, from code. Resolves `{rows, cols, staged, skipped, refused, droppedColumns, appended}` (`staged` the NUMBER of cells that changed; `skipped` `[{row, col, rowIdx}]`; `refused` the same with `text` and `error`; `appended` the rows handed to `onRowsAppend`), or `null` when nothing was pasted: no `pasteable`, empty text, no cursor, an editor open, a commit or another paste out, an overrun not accepted, or new rows while `onPasteOverrun` was asked. |
|
|
1428
|
+
|
|
1429
|
+
`fromTSV(text)` reads tab-separated text as a matrix of strings, as Excel
|
|
1430
|
+
writes it: character by character, so a quoted cell holding a tab, a newline
|
|
1431
|
+
or doubled quotes is one cell; a quote opens a quoted cell only at its start;
|
|
1432
|
+
CRLF lines read as LF ones; the newline after the last row (and any empty
|
|
1433
|
+
lines after it) is no row; empty text is `[]`. Exported beside `toTSV`.
|
|
1434
|
+
|
|
1341
1435
|
#### Staged edits, undo and redo, one commit (0.17.0)
|
|
1342
1436
|
|
|
1343
1437
|
With `staged: true` beside `editable`, a commit from an editor is STAGED
|
|
@@ -2086,7 +2180,7 @@ nor is a step whose type the palette does not offer (a start step) or that is
|
|
|
2086
2180
|
`unavailable`. What is copied is the graph's own text, `{nodes, connections}`
|
|
2087
2181
|
as `serialise()` writes it, so a graph from elsewhere pastes too. Ctrl/⌘+V
|
|
2088
2182
|
gives every pasted step a fresh id (as a new step gets one), keeps the lines
|
|
2089
|
-
between them, sets the copy off two grid steps right and down (each further
|
|
2183
|
+
between them (data lines too, under `connections` with `dataPorts: 'connect'`, from 0.17.1), sets the copy off two grid steps right and down (each further
|
|
2090
2184
|
paste of the same text one more), picks it, and is one `flow:paste` entry. A
|
|
2091
2185
|
paste right after Ctrl/⌘+X puts the steps back where they were; a cut is one
|
|
2092
2186
|
remove entry. A step whose title the flow already has gets a derived label,
|
|
@@ -2168,6 +2262,112 @@ Edge:
|
|
|
2168
2262
|
the step between its ends. Above the node limit the strip said so.
|
|
2169
2263
|
- A real pointer over a step highlighted its lines, and moving off cleared them.
|
|
2170
2264
|
|
|
2265
|
+
#### Building a modeler (0.17.1)
|
|
2266
|
+
|
|
2267
|
+
A typed node modeler (a dataflow, a signal chain, a diagram of blocks) is the
|
|
2268
|
+
canvas editor with its modeler options turned on. Every one of them is a
|
|
2269
|
+
`createCanvasEditor` option or a key on a catalogue type, off by default: an
|
|
2270
|
+
editor that sets none of them draws, binds and serialises exactly as before.
|
|
2271
|
+
None of them stores anything: the graph, the viewport and the values shown are
|
|
2272
|
+
the host's, handed in and handed back. Each edit they add is on the closed list
|
|
2273
|
+
`CANVAS_ACTIONS`, so one gesture is one undo entry. `demo/flow_modeler.html`
|
|
2274
|
+
(linked from `demo/index.html`) uses all of them together on a signal-flow
|
|
2275
|
+
diagram of sources, gains, a sum and displays.
|
|
2276
|
+
|
|
2277
|
+
| Option | What it gives | Details |
|
|
2278
|
+
|---|---|---|
|
|
2279
|
+
| `render`, `update`, `size`, `accent` (type keys) | A node that draws its own controls and values, at its own size, marked in a tone; `setCatalogue` adds a type while the editor is open | [Node bodies](#node-bodies-0170) |
|
|
2280
|
+
| `connections` (`dataPorts`, `policy`) with `accepts` / `provides` (port keys) | Data ports that connect, typed, under the host's rules, by drag or click; an empty drop picks a step that arrives connected | `connections` above |
|
|
2281
|
+
| `multiSelect` | Marquee, bulk move and delete, nudge, align and distribute | *Many picked* above |
|
|
2282
|
+
| `clipboard`, `prepareCopy` | Copy, cut, paste and duplicate of the picked steps and the lines among them | *Copy and paste* above |
|
|
2283
|
+
| `groups`, `notes` | Flat named frames round steps, and free-text notes | *Groups and notes* above |
|
|
2284
|
+
| `layout`, `rank` (type key) | Arrange by the kernel's layered layout, sources first and sinks last, or your own | `layout` above |
|
|
2285
|
+
| `edgeStyle`, `routing`, `hoverHighlight` | Lines in a tone, dashed, wider, with an arrow or a crow's foot and a label; bent round steps; lit while pointed at | `edgeStyle` above |
|
|
2286
|
+
| `viewport`, `onViewportChange`, `zoomControls`, `minimap` | Where the reader looks, kept by you; zoom buttons; an overview | `viewport` above |
|
|
2287
|
+
| `renameInPlace`, `inline` (type key) | A step's name and chosen settings edited on the node | [Edited on the node](#edited-on-the-node-0170) |
|
|
2288
|
+
| `setNodeValues`, `collapsible` | Values painted on the steps as often as you like; steps folded to their header | [Live values and folded steps](#live-values-and-folded-steps-0170) |
|
|
2289
|
+
|
|
2290
|
+
A type draws its own body (`render` once, `update` on every repaint), with its
|
|
2291
|
+
own `size` and `accent`; `inline` draws chosen settings on the node, and
|
|
2292
|
+
`rank` places it when Arrange runs the layered layout:
|
|
2293
|
+
|
|
2294
|
+
```js
|
|
2295
|
+
const IN = { name: 'in', direction: 'input', port_type: 'DATA', accepts: ['number'], maxConnections: 1 };
|
|
2296
|
+
const OUT = { name: 'out', direction: 'output', port_type: 'DATA', provides: ['number'] };
|
|
2297
|
+
const types = [
|
|
2298
|
+
{ type_id: 'source', label: 'Source', rank: 'source', inline: ['amplitude'], ports: [OUT],
|
|
2299
|
+
config_schema: { type: 'object', properties: { amplitude: { type: 'number', title: 'Amplitude' } } } },
|
|
2300
|
+
{ type_id: 'gain', label: 'Gain', size: { w: 224, h: 132 }, accent: 'amber', ports: [IN, OUT],
|
|
2301
|
+
render(body, node, ctx) {
|
|
2302
|
+
const range = Object.assign(document.createElement('input'), { type: 'range', min: -2, max: 2, step: 0.1 });
|
|
2303
|
+
range.addEventListener('change', () => ctx.setConfig({ k: Number(range.value) })); // one undo entry
|
|
2304
|
+
body.append(range);
|
|
2305
|
+
},
|
|
2306
|
+
update(body, node, ctx) { body.querySelector('input').value = node.config.k ?? 1; } },
|
|
2307
|
+
{ type_id: 'display', label: 'Display', rank: 'sink', ports: [IN],
|
|
2308
|
+
render: (body) => body.append(document.createElement('output')),
|
|
2309
|
+
update: (body, node, ctx) => { body.querySelector('output').textContent = ctx.values?.badge ?? '—'; } },
|
|
2310
|
+
];
|
|
2311
|
+
editor.setCatalogue(createStepCatalogue([...types, laterType], { categories })); // a type added after load
|
|
2312
|
+
```
|
|
2313
|
+
|
|
2314
|
+
The editor's options, each one independent of the others:
|
|
2315
|
+
|
|
2316
|
+
```js
|
|
2317
|
+
const editor = createCanvasEditor(host, {
|
|
2318
|
+
catalogue: createStepCatalogue(types, { categories }),
|
|
2319
|
+
connections: { // data ports connect, typed by accepts/provides
|
|
2320
|
+
dataPorts: 'connect',
|
|
2321
|
+
policy: ({ connection, graph }) => (closesLoop(graph, connection)
|
|
2322
|
+
? { ok: false, reason: 'That line would feed a block its own output.' } : null),
|
|
2323
|
+
},
|
|
2324
|
+
multiSelect: true, // marquee, bulk move, align, distribute
|
|
2325
|
+
clipboard: true, // or {read(), write(text)} to hold it yourself
|
|
2326
|
+
prepareCopy: (copy, { from, via }) => { copy.config.phase = 0; },
|
|
2327
|
+
groups: true, notes: true, // Ctrl+G frames, free-text notes
|
|
2328
|
+
layout: 'layered', // Arrange: columns by the lines, `rank` hints
|
|
2329
|
+
edgeStyle: (c, { source, target }) => ({ tone: 'teal', marker: 'arrow', width: 2,
|
|
2330
|
+
dashed: c.targetPort === 'b', label: source.label }),
|
|
2331
|
+
routing: 'obstacles', hoverHighlight: true,
|
|
2332
|
+
viewport: savedView, // opens there instead of fitting
|
|
2333
|
+
onViewportChange: (view) => { savedView = view; }, // once per finished pan or zoom
|
|
2334
|
+
zoomControls: true, minimap: true,
|
|
2335
|
+
renameInPlace: true, // double-click a title to rename it
|
|
2336
|
+
collapsible: true, // a fold button on every header
|
|
2337
|
+
});
|
|
2338
|
+
editor.load({ graph }, { viewport: savedView });
|
|
2339
|
+
setInterval(() => editor.setNodeValues({ mix: { badge: '0.42', tone: 'teal', ports: { out: '0.42' } } }), 250);
|
|
2340
|
+
```
|
|
2341
|
+
|
|
2342
|
+
`setNodeValues` records nothing and leaves the focus where it is, so a host
|
|
2343
|
+
can paint as often as its values change.
|
|
2344
|
+
|
|
2345
|
+
**Where it was checked.** `tests/flow_modeler_demo.test.mjs` mounts the demo
|
|
2346
|
+
under jsdom: what it shows, its live badges, and this section's list against
|
|
2347
|
+
the options. `tests/flow_kit_vocabulary.test.mjs` §4 holds every flow and
|
|
2348
|
+
canvas source the demo imports, and the demo itself, to the vocabulary gate.
|
|
2349
|
+
In headless Edge, `demo/flow_modeler_probe.mjs` opens the page by a real click
|
|
2350
|
+
on its link in `demo/index.html` and drives it with real input:
|
|
2351
|
+
- The bodies were drawn at their own size below their ports; every line left
|
|
2352
|
+
its output dot and its arrowhead ended on its input dot, within 1px; the
|
|
2353
|
+
group frame was round both sources and nothing else; a dashed line had a
|
|
2354
|
+
dash pattern and a label was the element at its own centre.
|
|
2355
|
+
- The badges changed in each of three seconds while a settings field, focused
|
|
2356
|
+
by a real click, kept the focus, and nothing was recorded.
|
|
2357
|
+
- A real drag from an output to an input added one line, dot to dot, as one
|
|
2358
|
+
undo entry. The gesture line appears above the canvas when a drag starts and
|
|
2359
|
+
moves the canvas down by its height, so the probe aims at the input where it
|
|
2360
|
+
is drawn once the line has started, as a reader would.
|
|
2361
|
+
- A real click and Shift+click and a real Ctrl+G drew a second frame round
|
|
2362
|
+
exactly the two steps; real Ctrl+C and Ctrl+V put two copies two grid steps
|
|
2363
|
+
right of and below the originals, with the line between them; a real click
|
|
2364
|
+
on Arrange ran every line left to right, the sources first and the displays
|
|
2365
|
+
last, with no two steps overlapping. Each was one undo entry.
|
|
2366
|
+
- A real Ctrl+Z after each, last first, put the steps back where they were on
|
|
2367
|
+
the canvas, took the copies, the second frame and the dragged line away, and
|
|
2368
|
+
left the graph the text it was before each step.
|
|
2369
|
+
|
|
2370
|
+
|
|
2171
2371
|
### The outline editor
|
|
2172
2372
|
|
|
2173
2373
|
`createOutlineEditor` draws a logic flow as a list of blocks: *If … otherwise*,
|
|
@@ -4140,6 +4340,24 @@ build so that a consumer with no bundler can load the library through a plain im
|
|
|
4140
4340
|
map. See `.gitignore` — it says so there too, because it is the kind of thing someone
|
|
4141
4341
|
"tidies up" later.
|
|
4142
4342
|
|
|
4343
|
+
### The `development` condition resolves in the installed package (0.17.1)
|
|
4344
|
+
|
|
4345
|
+
Every JavaScript entry in the `exports` map has two conditions: `import`, the
|
|
4346
|
+
prebuilt bundle under `dist/`, and `development`, the source barrel at the
|
|
4347
|
+
package root (`core.js`, `host.js`, `wm.js`, `widgets.js`, `charts.js`,
|
|
4348
|
+
`tiles.js`, `editor.js`, `canvas.js`, `flow.js`), which re-exports `src/`.
|
|
4349
|
+
Up to 0.17.0 those nine barrels were missing from `files`, so the published
|
|
4350
|
+
package did not contain them and a resolver that took the `development` branch
|
|
4351
|
+
(`require.resolve` under `--conditions=development`, for one) got a path to
|
|
4352
|
+
nothing. From 0.17.1 they ship, beside the `src/` they import.
|
|
4353
|
+
|
|
4354
|
+
Nothing about which file an `import` resolves to has changed: `import` is
|
|
4355
|
+
listed first, so an ES import still gets `dist/`, with or without the
|
|
4356
|
+
`development` condition.
|
|
4357
|
+
|
|
4358
|
+
A suite (`tests/exports_packed.test.mjs`) runs `npm pack --dry-run` and fails
|
|
4359
|
+
if any target of any condition in the `exports` map is not in the tarball.
|
|
4360
|
+
|
|
4143
4361
|
## Licence
|
|
4144
4362
|
|
|
4145
4363
|
MIT. See [`LICENSE`](LICENSE) and [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md).
|
package/canvas.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flexdesk/canvas — the canvas kernel: a pannable, zoomable surface with
|
|
3
|
+
* marquee selection, the edge router (orthogonal routing, crow's feet, a
|
|
4
|
+
* loop's return line, a layered Arrange) and a generic graph layer (nodes and
|
|
5
|
+
* their ports, connection legality with a policy hook, snapshot-free history
|
|
6
|
+
* over a closed action list).
|
|
7
|
+
*
|
|
8
|
+
* It knows nothing about what the nodes ARE. A consumer's own rules — which
|
|
9
|
+
* ports connect, what a collapsed node shows — are subclasses and policies it
|
|
10
|
+
* passes in.
|
|
11
|
+
*
|
|
12
|
+
* HAND-WRITTEN, not generated. The other barrels say "GENERATED by
|
|
13
|
+
* scripts/gen_barrels.py", and that script is not in this repository (nor is
|
|
14
|
+
* scripts/css_extract.py), so this one is kept by hand: add a module here when
|
|
15
|
+
* you add one under src/canvas/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export * from './src/canvas/index.js';
|
package/charts.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flexdesk/charts — Plotly-backed charts: the wrapper, the chart types, subplot layouts,
|
|
3
|
+
* downsampling, the plot grid, the plot-config editor, and the two plot windows.
|
|
4
|
+
*
|
|
5
|
+
* Plotly is a PEER dependency — 4.4 MB this bundle deliberately does NOT
|
|
6
|
+
* contain. Call setPlotlySource() with the path to your copy.
|
|
7
|
+
*
|
|
8
|
+
* GENERATED by scripts/gen_barrels.py. Do not hand-edit.
|
|
9
|
+
*
|
|
10
|
+
* 9 modules.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export * from './src/charts/chart_types.js';
|
|
14
|
+
export * from './src/charts/data_series_plot_window.js';
|
|
15
|
+
export * from './src/charts/downsample.js';
|
|
16
|
+
export * from './src/charts/plot_config.js';
|
|
17
|
+
export * from './src/charts/plot_config_editor.js';
|
|
18
|
+
export * from './src/charts/plot_grid.js';
|
|
19
|
+
export * from './src/charts/plot_popout_window.js';
|
|
20
|
+
export * from './src/charts/plotly_wrapper.js';
|
|
21
|
+
export * from './src/charts/series_transforms.js';
|
package/core.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flexdesk/core — The kernel: event bus, settings, logging, state machine, state guard.
|
|
3
|
+
*
|
|
4
|
+
* GENERATED by scripts/gen_barrels.py. Do not hand-edit.
|
|
5
|
+
*
|
|
6
|
+
* 6 modules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './src/core/event_bus.js';
|
|
10
|
+
export * from './src/core/logging.js';
|
|
11
|
+
export * from './src/core/settings.js';
|
|
12
|
+
export * from './src/core/state_guard.js';
|
|
13
|
+
export * from './src/core/state_guard_config.js';
|
|
14
|
+
export * from './src/core/state_machine.js';
|
package/css/flexdesk.css
CHANGED
|
@@ -862,6 +862,21 @@ body.twm-dt-col-resizing {
|
|
|
862
862
|
color: inherit;
|
|
863
863
|
font: inherit;
|
|
864
864
|
}
|
|
865
|
+
/* STORY-0087 the built-in editors: a checkbox keeps its own size, a text
|
|
866
|
+
* area grows with what it holds, and the choice box shows no empty helper
|
|
867
|
+
* line under it. */
|
|
868
|
+
.twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing .twm-dt-editor--bool {
|
|
869
|
+
width: auto;
|
|
870
|
+
margin: 4px 6px;
|
|
871
|
+
outline-offset: 1px;
|
|
872
|
+
}
|
|
873
|
+
.twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing textarea.twm-dt-editor {
|
|
874
|
+
resize: vertical;
|
|
875
|
+
min-height: 3.5em;
|
|
876
|
+
}
|
|
877
|
+
.twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing .twm-dt-editor--choice .twm-autocomplete-field__helper:empty {
|
|
878
|
+
display: none;
|
|
879
|
+
}
|
|
865
880
|
.twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--pending {
|
|
866
881
|
opacity: 0.6;
|
|
867
882
|
font-style: italic;
|
|
@@ -896,6 +911,11 @@ body.twm-dt-col-resizing {
|
|
|
896
911
|
background-image: repeating-linear-gradient(45deg,
|
|
897
912
|
color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
|
|
898
913
|
}
|
|
914
|
+
/* STORY-0090. A cell a paste skipped (not editable) carries the same mark. */
|
|
915
|
+
.twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--paste-skipped {
|
|
916
|
+
background-image: repeating-linear-gradient(45deg,
|
|
917
|
+
color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
|
|
918
|
+
}
|
|
899
919
|
/* While cells are staged, sort, filters and pages are held: the held control
|
|
900
920
|
* says why on itself (`data-refusal`). */
|
|
901
921
|
.twm-data-table-component.twm-dt--frozen th.sortable[data-refusal] {
|
|
@@ -1068,6 +1088,10 @@ body.twm-dt-col-resizing {
|
|
|
1068
1088
|
--twm-dt-pin-mark: repeating-linear-gradient(45deg,
|
|
1069
1089
|
color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
|
|
1070
1090
|
}
|
|
1091
|
+
.twm-preview-table.twm-dt-crosstab.twm-dt--cells tbody td.twm-dt-pin.twm-dt-cell--paste-skipped {
|
|
1092
|
+
--twm-dt-pin-mark: repeating-linear-gradient(45deg,
|
|
1093
|
+
color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
|
|
1094
|
+
}
|
|
1071
1095
|
.twm-preview-table.twm-dt-crosstab td.twm-dt-pin.twm-dt-cell--placeholder {
|
|
1072
1096
|
--twm-dt-pin-state: linear-gradient(rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0.015));
|
|
1073
1097
|
}
|
package/dist/charts.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AutocompleteField
|
|
3
|
-
} from "./chunk-QWQJUDPD.js";
|
|
4
1
|
import {
|
|
5
2
|
createTabs
|
|
6
3
|
} from "./chunk-HYJYYEPK.js";
|
|
@@ -58,10 +55,11 @@ import {
|
|
|
58
55
|
strideIndices,
|
|
59
56
|
uid,
|
|
60
57
|
updateChart
|
|
61
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-RMTRWWEO.js";
|
|
62
59
|
import {
|
|
60
|
+
AutocompleteField,
|
|
63
61
|
createRafResizeObserver
|
|
64
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-LWV23YVE.js";
|
|
65
63
|
import {
|
|
66
64
|
ColorField,
|
|
67
65
|
ManagedWindow,
|