@fieldia/widgets 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/index.d.mts +2 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +6 -1
- package/src/index.js.map +1 -1
- package/src/index.mjs +2 -0
- package/src/index.mjs.map +1 -1
- package/src/lib/badge.d.mts +8 -0
- package/src/lib/badge.d.ts +8 -0
- package/src/lib/badge.js +37 -0
- package/src/lib/badge.js.map +1 -0
- package/src/lib/badge.mjs +33 -0
- package/src/lib/badge.mjs.map +1 -0
- package/src/lib/calendar.d.mts +3 -1
- package/src/lib/calendar.d.ts +3 -1
- package/src/lib/calendar.js +17 -1
- package/src/lib/calendar.js.map +1 -1
- package/src/lib/calendar.mjs +17 -1
- package/src/lib/calendar.mjs.map +1 -1
- package/src/lib/cards.js +12 -4
- package/src/lib/cards.js.map +1 -1
- package/src/lib/cards.mjs +13 -5
- package/src/lib/cards.mjs.map +1 -1
- package/src/lib/choice-images.js +1 -1
- package/src/lib/choice-images.js.map +1 -1
- package/src/lib/choice-images.mjs +1 -1
- package/src/lib/choice-images.mjs.map +1 -1
- package/src/lib/choice-tags.js +6 -4
- package/src/lib/choice-tags.js.map +1 -1
- package/src/lib/choice-tags.mjs +6 -4
- package/src/lib/choice-tags.mjs.map +1 -1
- package/src/lib/colour.d.mts +11 -0
- package/src/lib/colour.d.ts +11 -0
- package/src/lib/colour.js +114 -0
- package/src/lib/colour.js.map +1 -0
- package/src/lib/colour.mjs +110 -0
- package/src/lib/colour.mjs.map +1 -0
- package/src/lib/copy.d.mts +8 -0
- package/src/lib/copy.d.ts +8 -0
- package/src/lib/copy.js +58 -0
- package/src/lib/copy.js.map +1 -0
- package/src/lib/copy.mjs +54 -0
- package/src/lib/copy.mjs.map +1 -0
- package/src/lib/date-range.d.mts +2 -0
- package/src/lib/date-range.d.ts +2 -0
- package/src/lib/date-range.js +220 -0
- package/src/lib/date-range.js.map +1 -0
- package/src/lib/date-range.mjs +216 -0
- package/src/lib/date-range.mjs.map +1 -0
- package/src/lib/display.d.mts +25 -1
- package/src/lib/display.d.ts +25 -1
- package/src/lib/display.js +42 -2
- package/src/lib/display.js.map +1 -1
- package/src/lib/display.mjs +39 -2
- package/src/lib/display.mjs.map +1 -1
- package/src/lib/distribution.d.mts +13 -0
- package/src/lib/distribution.d.ts +13 -0
- package/src/lib/distribution.js +159 -0
- package/src/lib/distribution.js.map +1 -0
- package/src/lib/distribution.mjs +152 -0
- package/src/lib/distribution.mjs.map +1 -0
- package/src/lib/duration.d.mts +18 -0
- package/src/lib/duration.d.ts +18 -0
- package/src/lib/duration.js +137 -0
- package/src/lib/duration.js.map +1 -0
- package/src/lib/duration.mjs +128 -0
- package/src/lib/duration.mjs.map +1 -0
- package/src/lib/embed.d.mts +3 -0
- package/src/lib/embed.d.ts +3 -0
- package/src/lib/embed.js +97 -0
- package/src/lib/embed.js.map +1 -0
- package/src/lib/embed.mjs +92 -0
- package/src/lib/embed.mjs.map +1 -0
- package/src/lib/extras.js +6 -0
- package/src/lib/extras.js.map +1 -1
- package/src/lib/extras.mjs +6 -0
- package/src/lib/extras.mjs.map +1 -1
- package/src/lib/files.d.mts +11 -0
- package/src/lib/files.d.ts +11 -0
- package/src/lib/files.js +19 -0
- package/src/lib/files.js.map +1 -1
- package/src/lib/files.mjs +18 -0
- package/src/lib/files.mjs.map +1 -1
- package/src/lib/float.d.mts +14 -0
- package/src/lib/float.d.ts +14 -0
- package/src/lib/float.js +91 -0
- package/src/lib/float.js.map +1 -0
- package/src/lib/float.mjs +88 -0
- package/src/lib/float.mjs.map +1 -0
- package/src/lib/kind-parts.d.mts +4 -3
- package/src/lib/kind-parts.d.ts +4 -3
- package/src/lib/kind-parts.js +5 -3
- package/src/lib/kind-parts.js.map +1 -1
- package/src/lib/kind-parts.mjs +5 -3
- package/src/lib/kind-parts.mjs.map +1 -1
- package/src/lib/labels.d.mts +39 -0
- package/src/lib/labels.d.ts +39 -0
- package/src/lib/labels.js +29 -0
- package/src/lib/labels.js.map +1 -1
- package/src/lib/labels.mjs +29 -0
- package/src/lib/labels.mjs.map +1 -1
- package/src/lib/layout-styles.d.mts +1 -1
- package/src/lib/layout-styles.d.ts +1 -1
- package/src/lib/layout-styles.js +12 -5
- package/src/lib/layout-styles.js.map +1 -1
- package/src/lib/layout-styles.mjs +12 -5
- package/src/lib/layout-styles.mjs.map +1 -1
- package/src/lib/lines.d.mts +12 -2
- package/src/lib/lines.d.ts +12 -2
- package/src/lib/lines.js +372 -25
- package/src/lib/lines.js.map +1 -1
- package/src/lib/lines.mjs +371 -26
- package/src/lib/lines.mjs.map +1 -1
- package/src/lib/links-table.d.mts +11 -0
- package/src/lib/links-table.d.ts +11 -0
- package/src/lib/links-table.js +116 -0
- package/src/lib/links-table.js.map +1 -0
- package/src/lib/links-table.mjs +112 -0
- package/src/lib/links-table.mjs.map +1 -0
- package/src/lib/locales/ar.js +29 -0
- package/src/lib/locales/ar.js.map +1 -1
- package/src/lib/locales/ar.mjs +29 -0
- package/src/lib/locales/ar.mjs.map +1 -1
- package/src/lib/locales/de.js +29 -0
- package/src/lib/locales/de.js.map +1 -1
- package/src/lib/locales/de.mjs +29 -0
- package/src/lib/locales/de.mjs.map +1 -1
- package/src/lib/locales/fr.js +29 -0
- package/src/lib/locales/fr.js.map +1 -1
- package/src/lib/locales/fr.mjs +29 -0
- package/src/lib/locales/fr.mjs.map +1 -1
- package/src/lib/numbers.d.mts +5 -1
- package/src/lib/numbers.d.ts +5 -1
- package/src/lib/numbers.js +15 -2
- package/src/lib/numbers.js.map +1 -1
- package/src/lib/numbers.mjs +13 -2
- package/src/lib/numbers.mjs.map +1 -1
- package/src/lib/payments.d.mts +2 -0
- package/src/lib/payments.d.ts +2 -0
- package/src/lib/payments.js +94 -0
- package/src/lib/payments.js.map +1 -0
- package/src/lib/payments.mjs +90 -0
- package/src/lib/payments.mjs.map +1 -0
- package/src/lib/popup.d.mts +11 -0
- package/src/lib/popup.d.ts +11 -0
- package/src/lib/popup.js +48 -0
- package/src/lib/popup.js.map +1 -0
- package/src/lib/popup.mjs +45 -0
- package/src/lib/popup.mjs.map +1 -0
- package/src/lib/priority.d.mts +10 -0
- package/src/lib/priority.d.ts +10 -0
- package/src/lib/priority.js +59 -0
- package/src/lib/priority.js.map +1 -0
- package/src/lib/priority.mjs +55 -0
- package/src/lib/priority.mjs.map +1 -0
- package/src/lib/progress.js +3 -0
- package/src/lib/progress.js.map +1 -1
- package/src/lib/progress.mjs +3 -0
- package/src/lib/progress.mjs.map +1 -1
- package/src/lib/properties.js +100 -9
- package/src/lib/properties.js.map +1 -1
- package/src/lib/properties.mjs +100 -9
- package/src/lib/properties.mjs.map +1 -1
- package/src/lib/read-text.d.mts +25 -0
- package/src/lib/read-text.d.ts +25 -0
- package/src/lib/read-text.js +102 -0
- package/src/lib/read-text.js.map +1 -0
- package/src/lib/read-text.mjs +98 -0
- package/src/lib/read-text.mjs.map +1 -0
- package/src/lib/relations.d.mts +12 -0
- package/src/lib/relations.d.ts +12 -0
- package/src/lib/relations.js +110 -10
- package/src/lib/relations.js.map +1 -1
- package/src/lib/relations.mjs +108 -10
- package/src/lib/relations.mjs.map +1 -1
- package/src/lib/slider.js +1 -1
- package/src/lib/slider.js.map +1 -1
- package/src/lib/slider.mjs +1 -1
- package/src/lib/slider.mjs.map +1 -1
- package/src/lib/state-dot.d.mts +2 -0
- package/src/lib/state-dot.d.ts +2 -0
- package/src/lib/state-dot.js +107 -0
- package/src/lib/state-dot.js.map +1 -0
- package/src/lib/state-dot.mjs +103 -0
- package/src/lib/state-dot.mjs.map +1 -0
- package/src/lib/statusbar.js +151 -14
- package/src/lib/statusbar.js.map +1 -1
- package/src/lib/statusbar.mjs +151 -14
- package/src/lib/statusbar.mjs.map +1 -1
- package/src/lib/styles-business.d.mts +8 -0
- package/src/lib/styles-business.d.ts +8 -0
- package/src/lib/styles-business.js +120 -0
- package/src/lib/styles-business.js.map +1 -0
- package/src/lib/styles-business.mjs +117 -0
- package/src/lib/styles-business.mjs.map +1 -0
- package/src/lib/styles-inputs.d.mts +1 -1
- package/src/lib/styles-inputs.d.ts +1 -1
- package/src/lib/styles-inputs.js +2 -0
- package/src/lib/styles-inputs.js.map +1 -1
- package/src/lib/styles-inputs.mjs +2 -0
- package/src/lib/styles-inputs.mjs.map +1 -1
- package/src/lib/styles-kinds.d.mts +1 -1
- package/src/lib/styles-kinds.d.ts +1 -1
- package/src/lib/styles-kinds.js +2 -2
- package/src/lib/styles-kinds.mjs +2 -2
- package/src/lib/styles-sheet.d.mts +6 -0
- package/src/lib/styles-sheet.d.ts +6 -0
- package/src/lib/styles-sheet.js +139 -0
- package/src/lib/styles-sheet.js.map +1 -0
- package/src/lib/styles-sheet.mjs +136 -0
- package/src/lib/styles-sheet.mjs.map +1 -0
- package/src/lib/styles.js +223 -27
- package/src/lib/styles.js.map +1 -1
- package/src/lib/styles.mjs +223 -27
- package/src/lib/styles.mjs.map +1 -1
- package/src/lib/tax-totals.d.mts +2 -0
- package/src/lib/tax-totals.d.ts +2 -0
- package/src/lib/tax-totals.js +92 -0
- package/src/lib/tax-totals.js.map +1 -0
- package/src/lib/tax-totals.mjs +88 -0
- package/src/lib/tax-totals.mjs.map +1 -0
- package/src/lib/themes.d.mts +29 -0
- package/src/lib/themes.d.ts +29 -0
- package/src/lib/themes.js +180 -0
- package/src/lib/themes.js.map +1 -0
- package/src/lib/themes.mjs +177 -0
- package/src/lib/themes.mjs.map +1 -0
- package/src/lib/timer.d.mts +4 -0
- package/src/lib/timer.d.ts +4 -0
- package/src/lib/timer.js +62 -0
- package/src/lib/timer.js.map +1 -0
- package/src/lib/timer.mjs +57 -0
- package/src/lib/timer.mjs.map +1 -0
- package/src/lib/widgets.d.mts +18 -3
- package/src/lib/widgets.d.ts +18 -3
- package/src/lib/widgets.js +89 -10
- package/src/lib/widgets.js.map +1 -1
- package/src/lib/widgets.mjs +90 -11
- package/src/lib/widgets.mjs.map +1 -1
- package/src/lib/yes-no.js +1 -1
- package/src/lib/yes-no.js.map +1 -1
- package/src/lib/yes-no.mjs +1 -1
- package/src/lib/yes-no.mjs.map +1 -1
package/src/lib/lines.mjs
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { isEmpty, lineKind } from '@fieldia/core';
|
|
2
|
-
import {
|
|
2
|
+
import { cellText, currencyOf } from './display.mjs';
|
|
3
|
+
import { drawIcon } from './icons.mjs';
|
|
3
4
|
import { askFirst, fillIn, maker } from './kind-parts.mjs';
|
|
4
5
|
import { WIDGET_LABELS } from './labels.mjs';
|
|
5
6
|
import { moveLineTo } from './line-moves.mjs';
|
|
6
7
|
import { createWidget } from './widgets.mjs';
|
|
8
|
+
/**
|
|
9
|
+
* A one2many as an editable table. Each cell is an ordinary widget; a small
|
|
10
|
+
* adapter routes its edits to `updateLine` and its searches to `searchLine`,
|
|
11
|
+
* so a many2one inside a line searches with that line's own values. Rows are
|
|
12
|
+
* matched by line key, so adding or removing a line never rebuilds the others.
|
|
13
|
+
* When the field declares `lineKinds`, a section or a note is one wide row of
|
|
14
|
+
* its text.
|
|
15
|
+
*
|
|
16
|
+
* A line moves by the grip at its start, or by Alt+↑/↓ from inside it, said
|
|
17
|
+
* aloud. The node's `options`: `min` lines at least — a new table starts with
|
|
18
|
+
* them — and `max` at most; `addLabel`, the Add button's words (and
|
|
19
|
+
* `addSectionLabel`, `addNoteLabel`, its section's and note's); `emptyLabel`,
|
|
20
|
+
* a sentence while there is no line; `confirmDelete`, asking before a line
|
|
21
|
+
* with something in it goes.
|
|
22
|
+
*/
|
|
23
|
+
/** The kinds of field whose column is as wide as its values, so the columns of words and links have the rest. */
|
|
24
|
+
/** Columns of numbers, which grow past their kind's width for a long value. */
|
|
25
|
+
const NUMERIC = new Set(['integer', 'float', 'monetary']);
|
|
26
|
+
const FIXED_WIDTH = new Set(['boolean', 'integer', 'float', 'monetary', 'date', 'datetime']);
|
|
7
27
|
const count = (value) => (typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined);
|
|
8
28
|
/** The field a section or note is typed into: one line for a heading, a growing box for a note. */
|
|
9
29
|
export function kindTextField(def, kind) {
|
|
@@ -16,11 +36,57 @@ export function lineForm(form, field, key) {
|
|
|
16
36
|
...form,
|
|
17
37
|
getState: () => ({ ...form.getState(), values: values() }),
|
|
18
38
|
setValue: (name, value) => form.updateLine(field, key, name, value),
|
|
19
|
-
search: (name, query, limit) => form.searchLine(field, key, name, query, limit),
|
|
39
|
+
search: (name, query, limit, options) => form.searchLine(field, key, name, query, limit, options),
|
|
20
40
|
canCreate: (name) => form.canCreateLine(field, name),
|
|
21
|
-
quickCreate: (name, text) => form.quickCreateLine(field, name, text),
|
|
41
|
+
quickCreate: (name, text) => form.quickCreateLine(field, name, text, key),
|
|
42
|
+
createValues: (name) => form.createValues(name, { lines: field, key }),
|
|
22
43
|
};
|
|
23
44
|
}
|
|
45
|
+
/** A line's money in its record's currency (`parent.…`), as a field of its own: in that currency, fixed — for a dialog with no record round it. */
|
|
46
|
+
export function ownCurrency(sub, parent) {
|
|
47
|
+
if (sub.type !== 'monetary' || !sub.currencyField?.startsWith('parent.'))
|
|
48
|
+
return sub;
|
|
49
|
+
const { currencyField: _, ...rest } = sub;
|
|
50
|
+
const code = currencyOf(sub, {}, parent);
|
|
51
|
+
return code && /^[A-Z]{3}$/.test(code) ? { ...rest, currency: code } : rest;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A line opened in a dialog: with `lineOpens: "record"`, a saved line's own
|
|
55
|
+
* record by the table's model — what its page saved is written back to the
|
|
56
|
+
* line's fields of the same names — else, or for a line not saved yet, every
|
|
57
|
+
* one of its fields, recalculated as they change and written back on Save &
|
|
58
|
+
* Close. Shared with @fieldia/grid.
|
|
59
|
+
*/
|
|
60
|
+
export async function openLineDialog(form, name, node, line, dialogs, readonly) {
|
|
61
|
+
const def = form.page.fields[name];
|
|
62
|
+
const lines = () => form.getState().values[name] ?? [];
|
|
63
|
+
const write = (values) => form.setValue(name, lines().map((l) => (l.key === line.key ? { ...l, values: { ...l.values, ...values } } : l)));
|
|
64
|
+
if (node.lineOpens === 'record' && line.id !== undefined && dialogs.canOpen(def.relation)) {
|
|
65
|
+
const saved = await dialogs.openRecord(def.relation, { recordId: line.id, title: def.label, withValues: true });
|
|
66
|
+
const back = Object.fromEntries(Object.entries(saved?.values ?? {}).filter(([key]) => key in def.fields && key !== def.sequenceField));
|
|
67
|
+
if (Object.keys(back).length)
|
|
68
|
+
write(back);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const fields = Object.fromEntries(Object.entries(def.fields)
|
|
72
|
+
.filter(([key]) => key !== def.lineKinds?.field && key !== def.sequenceField)
|
|
73
|
+
.map(([key, sub]) => [key, ownCurrency(sub, form.getState().values)]));
|
|
74
|
+
// The dialog shows what the onchange makes of the line as it is edited; the line itself waits for Save & Close.
|
|
75
|
+
// Laid out by the table's lineForm when it has one, its conditions reading the line, its record as parent.
|
|
76
|
+
const state = form.getState();
|
|
77
|
+
const values = await dialogs.editValues({
|
|
78
|
+
title: def.label,
|
|
79
|
+
fields,
|
|
80
|
+
values: line.values,
|
|
81
|
+
readonly,
|
|
82
|
+
recompute: (edited) => form.previewLine(name, line.key, edited),
|
|
83
|
+
parent: { values: state.values, fields: form.page.fields, id: state.recordId },
|
|
84
|
+
...(node.lineForm ? { layout: node.lineForm.children } : {}),
|
|
85
|
+
});
|
|
86
|
+
// Written in one go, so the form recalculates once.
|
|
87
|
+
if (values)
|
|
88
|
+
write(values);
|
|
89
|
+
}
|
|
24
90
|
export const linesWidget = ({ form, name, field, node, id, document, labels = WIDGET_LABELS.en, locale, dialogs }) => {
|
|
25
91
|
const def = field;
|
|
26
92
|
const kinds = def.lineKinds;
|
|
@@ -31,10 +97,20 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
31
97
|
const words = (key) => (typeof options[key] === 'string' && options[key].trim() ? options[key] : null);
|
|
32
98
|
// The fields that say what a line is and keep the lines' order never show as columns.
|
|
33
99
|
// Optional columns the page starts hidden stay out of the plain table.
|
|
34
|
-
const
|
|
100
|
+
const heads = new Map();
|
|
101
|
+
// The table's own rules, read by the form line by line; a table with none asks nothing.
|
|
102
|
+
const ruled = !!(node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons || node.lineDelete !== undefined);
|
|
103
|
+
const columns = (node.columns ?? Object.keys(def.fields)).filter(
|
|
104
|
+
// Lines a line holds are edited in its form, never drawn in its row.
|
|
105
|
+
(column) => def.fields[column] && def.fields[column].type !== 'one2many' && column !== kinds?.field && column !== def.sequenceField && node.optionalColumns?.[column] !== 'hide');
|
|
35
106
|
const element = document.createElement('div');
|
|
36
107
|
element.className = 'fd-lines';
|
|
37
108
|
element.id = id;
|
|
109
|
+
// Lines as cards on a narrow form, or always; columns as wide as they hold: the stylesheet draws them so.
|
|
110
|
+
if (node.cards)
|
|
111
|
+
element.dataset['cards'] = node.cards;
|
|
112
|
+
if (node.fit)
|
|
113
|
+
element.dataset['fit'] = node.fit;
|
|
38
114
|
element.setAttribute('role', 'group');
|
|
39
115
|
const scroller = document.createElement('div');
|
|
40
116
|
scroller.className = 'fd-lines-scroll';
|
|
@@ -42,11 +118,23 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
42
118
|
table.className = 'fd-lines-table';
|
|
43
119
|
const head = document.createElement('thead');
|
|
44
120
|
const headRow = document.createElement('tr');
|
|
45
|
-
|
|
121
|
+
// Lines chosen for the table's buttons for them: a tick on each line, one in the head for every line, and a bar over the table.
|
|
122
|
+
const choosing = !!node.selectedButtons?.length;
|
|
123
|
+
const chosen = new Set();
|
|
124
|
+
const pickAll = choosing ? make('input', { type: 'checkbox', class: 'fd-checkbox fd-line-pick fd-line-pick-all', 'aria-label': labels.chooseAllLines }) : null;
|
|
125
|
+
headRow.append(make('th', { class: 'fd-lines-grip' }, ...(pickAll ? [pickAll] : [])));
|
|
46
126
|
for (const column of columns) {
|
|
47
127
|
const th = document.createElement('th');
|
|
48
128
|
th.scope = 'col';
|
|
129
|
+
th.dataset['column'] = column;
|
|
49
130
|
th.textContent = def.fields[column].label;
|
|
131
|
+
const width = node.cells?.[column]?.width;
|
|
132
|
+
if (width)
|
|
133
|
+
th.style.width = `${width}ch`;
|
|
134
|
+
// Its kind, which sizes a column of yes/no, numbers, money or dates to its values, as Flectra's lists do.
|
|
135
|
+
else if (FIXED_WIDTH.has(def.fields[column].type))
|
|
136
|
+
th.dataset['fit'] = def.fields[column].type;
|
|
137
|
+
heads.set(column, th);
|
|
50
138
|
headRow.append(th);
|
|
51
139
|
}
|
|
52
140
|
const lastTh = document.createElement('th');
|
|
@@ -58,6 +146,7 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
58
146
|
// Number columns the page asks to add up, in a row under the lines.
|
|
59
147
|
const totals = (node.totals ?? []).filter((column) => columns.includes(column));
|
|
60
148
|
const sums = new Map();
|
|
149
|
+
const footCells = new Map();
|
|
61
150
|
if (totals.length) {
|
|
62
151
|
const foot = document.createElement('tfoot');
|
|
63
152
|
const row = document.createElement('tr');
|
|
@@ -65,6 +154,8 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
65
154
|
row.append(document.createElement('td'));
|
|
66
155
|
columns.forEach((column, i) => {
|
|
67
156
|
const td = document.createElement('td');
|
|
157
|
+
td.dataset['column'] = column;
|
|
158
|
+
footCells.set(column, td);
|
|
68
159
|
if (totals.includes(column))
|
|
69
160
|
sums.set(column, td);
|
|
70
161
|
else if (i === 0)
|
|
@@ -96,19 +187,82 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
96
187
|
};
|
|
97
188
|
addButton(words('addLabel') ?? labels.addLine, {}, 'line');
|
|
98
189
|
if (kinds) {
|
|
99
|
-
addButton(labels.addSection, { [kinds.field]: kinds.section ?? 'section' }, 'section');
|
|
100
|
-
addButton(labels.addNote, { [kinds.field]: kinds.note ?? 'note' }, 'note');
|
|
190
|
+
addButton(words('addSectionLabel') ?? labels.addSection, { [kinds.field]: kinds.section ?? 'section' }, 'section');
|
|
191
|
+
addButton(words('addNoteLabel') ?? labels.addNote, { [kinds.field]: kinds.note ?? 'note' }, 'note');
|
|
101
192
|
}
|
|
193
|
+
// Buttons in the control row, beside the add buttons: the record's, shown by a condition on it (Flectra's <control>).
|
|
194
|
+
const controls = (node.controlButtons ?? []).map((own) => {
|
|
195
|
+
const icon = drawIcon(document, own.icon);
|
|
196
|
+
const button = make('button', { type: 'button', class: 'fd-button fd-button-link fd-lines-add fd-lines-control', 'data-node': own.id }, ...(icon ? [icon] : []), own.label);
|
|
197
|
+
button.addEventListener('click', () => void pressed(button, () => form.runAction(own.id)));
|
|
198
|
+
adds.append(button);
|
|
199
|
+
return { id: own.id, button };
|
|
200
|
+
});
|
|
201
|
+
// The bar of buttons for the lines chosen, over the table while any is.
|
|
202
|
+
const chosenCount = make('span', { class: 'fd-lines-chosen-count', role: 'status' });
|
|
203
|
+
const chosenButtons = (node.selectedButtons ?? []).map((own) => {
|
|
204
|
+
const icon = drawIcon(document, own.icon);
|
|
205
|
+
const button = make('button', { type: 'button', class: `fd-button fd-button-${own.style ?? 'secondary'} fd-lines-chosen-button`, 'data-node': own.id }, ...(icon ? [icon] : []), own.label);
|
|
206
|
+
// In the table's order, whatever order they were ticked in.
|
|
207
|
+
button.addEventListener('click', () => void pressed(button, () => form.runLinesAction(node.id, own.id, current().filter((line) => chosen.has(line.key)).map((line) => line.key))));
|
|
208
|
+
return { id: own.id, button };
|
|
209
|
+
});
|
|
210
|
+
const bar = choosing ? make('div', { class: 'fd-lines-chosen', hidden: '' }, chosenCount, ...chosenButtons.map((b) => b.button)) : null;
|
|
102
211
|
const emptyWords = words('emptyLabel');
|
|
103
212
|
const empty = emptyWords ? make('div', { class: 'fd-help fd-lines-empty' }, emptyWords) : null;
|
|
104
213
|
// Removing a line with something in it asks first, in place: "Remove line 2? Remove · Keep".
|
|
105
214
|
const confirm = askFirst(make, labels);
|
|
106
215
|
// Where a line went, said politely; not the page's own announcer, which a page looks for by its class.
|
|
107
216
|
const voice = make('div', { class: 'fd-sr-only', role: 'status' });
|
|
108
|
-
element.append(scroller, ...(empty ? [empty] : []), adds, confirm.element, voice);
|
|
217
|
+
element.append(...(bar ? [bar] : []), scroller, ...(empty ? [empty] : []), adds, confirm.element, voice);
|
|
218
|
+
/** A press that waits for its run, and is not pressed again meanwhile. */
|
|
219
|
+
async function pressed(button, run) {
|
|
220
|
+
if (button.hasAttribute('aria-busy'))
|
|
221
|
+
return;
|
|
222
|
+
button.setAttribute('aria-busy', 'true');
|
|
223
|
+
try {
|
|
224
|
+
await run();
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
button.removeAttribute('aria-busy');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** The bar and the head's tick, as the lines chosen are now. */
|
|
231
|
+
function showChosen() {
|
|
232
|
+
if (!bar || !pickAll)
|
|
233
|
+
return;
|
|
234
|
+
const items = current().filter((line) => !lineKind(def, line.values));
|
|
235
|
+
for (const key of [...chosen])
|
|
236
|
+
if (!items.some((line) => line.key === key))
|
|
237
|
+
chosen.delete(key);
|
|
238
|
+
bar.hidden = readonly || chosen.size === 0;
|
|
239
|
+
chosenCount.textContent = fillIn(labels.linesChosen, { n: chosen.size });
|
|
240
|
+
for (const { id, button } of chosenButtons)
|
|
241
|
+
button.hidden = form.node(id).invisible;
|
|
242
|
+
pickAll.checked = items.length > 0 && chosen.size === items.length;
|
|
243
|
+
pickAll.indeterminate = chosen.size > 0 && chosen.size < items.length;
|
|
244
|
+
pickAll.disabled = readonly || !items.length;
|
|
245
|
+
for (const [key, row] of rows) {
|
|
246
|
+
if (!row.pick)
|
|
247
|
+
continue;
|
|
248
|
+
row.pick.checked = chosen.has(key);
|
|
249
|
+
row.pick.disabled = readonly;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
pickAll?.addEventListener('change', () => {
|
|
253
|
+
const items = current().filter((line) => !lineKind(def, line.values));
|
|
254
|
+
if (pickAll.checked)
|
|
255
|
+
for (const line of items)
|
|
256
|
+
chosen.add(line.key);
|
|
257
|
+
else
|
|
258
|
+
chosen.clear();
|
|
259
|
+
showChosen();
|
|
260
|
+
});
|
|
109
261
|
const rows = new Map();
|
|
110
262
|
let focusNew = null;
|
|
111
263
|
let readonly = false;
|
|
264
|
+
/** The lines the columns were last sized for, by their keys. */
|
|
265
|
+
let columnsSizedFor = null;
|
|
112
266
|
const current = () => form.getState().values[name] ?? [];
|
|
113
267
|
// A new table starts with the lines it needs at least.
|
|
114
268
|
for (let have = current().length; have < min; have++)
|
|
@@ -126,6 +280,65 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
126
280
|
focused.focus();
|
|
127
281
|
voice.textContent = fillIn(labels.movedTo, { label: typeof first === 'string' && first.trim() ? first : fillIn(labels.lineN, { n: from + 1 }), n: to + 1, total: current().length });
|
|
128
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* A line lifted by its grip: it follows the pointer, the lines it passes
|
|
285
|
+
* slide out of its way, and the table's value changes once, when it is let
|
|
286
|
+
* go — as a grid's row drags. Escape puts it back.
|
|
287
|
+
*/
|
|
288
|
+
function lift(tr, key, startY) {
|
|
289
|
+
const all = [...body.children];
|
|
290
|
+
const from = all.indexOf(tr);
|
|
291
|
+
const others = all.filter((row) => row !== tr);
|
|
292
|
+
const box = tr.getBoundingClientRect();
|
|
293
|
+
const middles = others.map((row) => {
|
|
294
|
+
const at = row.getBoundingClientRect();
|
|
295
|
+
return at.top + at.height / 2;
|
|
296
|
+
});
|
|
297
|
+
let to = from;
|
|
298
|
+
let lifted = false;
|
|
299
|
+
const escape = (event) => {
|
|
300
|
+
if (event.key !== 'Escape')
|
|
301
|
+
return;
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
settle();
|
|
304
|
+
};
|
|
305
|
+
function settle() {
|
|
306
|
+
for (const row of all)
|
|
307
|
+
row.style.transform = '';
|
|
308
|
+
tr.classList.remove('fd-line-lifted');
|
|
309
|
+
body.classList.remove('fd-lines-dragging');
|
|
310
|
+
document.removeEventListener('keydown', escape, true);
|
|
311
|
+
lifted = false;
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
follow(y) {
|
|
315
|
+
const by = y - startY;
|
|
316
|
+
if (!lifted) {
|
|
317
|
+
// A press that hardly moves is not a drag yet.
|
|
318
|
+
if (Math.abs(by) < 4 || readonly)
|
|
319
|
+
return;
|
|
320
|
+
lifted = true;
|
|
321
|
+
tr.classList.add('fd-line-lifted');
|
|
322
|
+
body.classList.add('fd-lines-dragging');
|
|
323
|
+
document.addEventListener('keydown', escape, true);
|
|
324
|
+
}
|
|
325
|
+
tr.style.transform = `translateY(${by}px)`;
|
|
326
|
+
// Its place: after every other line whose middle its own middle has passed.
|
|
327
|
+
const middle = box.top + box.height / 2 + by;
|
|
328
|
+
to = middles.filter((at) => at < middle).length;
|
|
329
|
+
others.forEach((row, i) => {
|
|
330
|
+
row.style.transform = i >= from && i < to ? `translateY(${-box.height}px)` : i < from && i >= to ? `translateY(${box.height}px)` : '';
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
drop() {
|
|
334
|
+
const moved = lifted && to !== from;
|
|
335
|
+
settle();
|
|
336
|
+
if (moved)
|
|
337
|
+
move(key, to);
|
|
338
|
+
},
|
|
339
|
+
settle,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
129
342
|
body.addEventListener('keydown', (event) => {
|
|
130
343
|
const key = event.target.closest('tr')?.dataset['line'];
|
|
131
344
|
if (!key || !event.altKey || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown'))
|
|
@@ -135,10 +348,19 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
135
348
|
});
|
|
136
349
|
function makeCell(tr, line, column, sub, span = 1) {
|
|
137
350
|
const cellId = `${id}-${line.key}-${column}`;
|
|
138
|
-
|
|
351
|
+
// A cell the page draws as a pill in its tone (Flectra's widget="badge"), never edited, or with a widget of its own.
|
|
352
|
+
const rules = lineKind(def, line.values) ? undefined : node.cells?.[column];
|
|
353
|
+
// Its settings reach its widget even without a widget named: a calendar's week numbers.
|
|
354
|
+
const look = rules?.badge ? { widget: 'badge' } : rules?.widget ? { widget: rules.widget, ...(rules.options ? { options: rules.options } : {}) } : rules?.options ? { options: rules.options } : {};
|
|
355
|
+
const subNode = { type: 'field', id: `${node.id}.${line.key}.${column}`, field: column, ...look };
|
|
139
356
|
const widget = createWidget({ form: lineForm(form, name, line.key), name: column, field: sub, node: subNode, id: cellId, document, labels, locale, dialogs });
|
|
140
357
|
const td = document.createElement('td');
|
|
141
358
|
td.colSpan = span;
|
|
359
|
+
td.dataset['column'] = column;
|
|
360
|
+
if (FIXED_WIDTH.has(sub.type))
|
|
361
|
+
td.dataset['fit'] = sub.type;
|
|
362
|
+
// Its column's label, which a card shows before its value.
|
|
363
|
+
td.dataset['label'] = sub.label;
|
|
142
364
|
const label = document.createElement('label');
|
|
143
365
|
label.className = 'fd-sr-only';
|
|
144
366
|
label.htmlFor = cellId;
|
|
@@ -148,7 +370,7 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
148
370
|
error.hidden = true;
|
|
149
371
|
td.append(label, widget.element, error);
|
|
150
372
|
tr.append(td);
|
|
151
|
-
return { name: column, def: sub, widget, error };
|
|
373
|
+
return { name: column, def: sub, widget, error, td };
|
|
152
374
|
}
|
|
153
375
|
function makeRow(line) {
|
|
154
376
|
const tr = document.createElement('tr');
|
|
@@ -156,25 +378,35 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
156
378
|
const kind = lineKind(def, line.values);
|
|
157
379
|
if (kind)
|
|
158
380
|
tr.className = `fd-line-${kind}`;
|
|
159
|
-
// Dragged by its grip, the line
|
|
381
|
+
// Dragged by its grip, the line follows the pointer and takes its place when let go.
|
|
160
382
|
const grip = make('span', { class: 'fd-line-grip fd-rank-grip', 'aria-hidden': 'true' });
|
|
161
|
-
let
|
|
383
|
+
let drag = null;
|
|
162
384
|
grip.addEventListener('pointerdown', (event) => {
|
|
163
385
|
if (readonly || event.button !== 0)
|
|
164
386
|
return;
|
|
165
387
|
event.preventDefault();
|
|
166
388
|
grip.setPointerCapture?.(event.pointerId);
|
|
167
|
-
|
|
389
|
+
drag = lift(tr, line.key, event.clientY);
|
|
168
390
|
});
|
|
169
|
-
grip.addEventListener('pointermove', (event) =>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
391
|
+
grip.addEventListener('pointermove', (event) => drag?.follow(event.clientY));
|
|
392
|
+
grip.addEventListener('pointerup', () => {
|
|
393
|
+
drag?.drop();
|
|
394
|
+
drag = null;
|
|
395
|
+
});
|
|
396
|
+
grip.addEventListener('pointercancel', () => {
|
|
397
|
+
drag?.settle();
|
|
398
|
+
drag = null;
|
|
174
399
|
});
|
|
175
|
-
for
|
|
176
|
-
|
|
177
|
-
|
|
400
|
+
// Its tick for the table's buttons for chosen lines, before the grip.
|
|
401
|
+
const pick = choosing && !kind ? make('input', { type: 'checkbox', class: 'fd-checkbox fd-line-pick' }) : null;
|
|
402
|
+
pick?.addEventListener('change', () => {
|
|
403
|
+
if (pick.checked)
|
|
404
|
+
chosen.add(line.key);
|
|
405
|
+
else
|
|
406
|
+
chosen.delete(line.key);
|
|
407
|
+
showChosen();
|
|
408
|
+
});
|
|
409
|
+
tr.append(make('td', { class: 'fd-lines-grip' }, ...(pick ? [make('span', { class: 'fd-line-lead' }, pick, grip)] : [grip])));
|
|
178
410
|
const cells = kind && kinds
|
|
179
411
|
? [makeCell(tr, line, kinds.text, kindTextField(def.fields[kinds.text], kind), columns.length)]
|
|
180
412
|
: columns.map((column) => makeCell(tr, line, column, def.fields[column]));
|
|
@@ -199,9 +431,55 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
199
431
|
(next ? rows.get(next.key)?.remove : adds.querySelector('button'))?.focus();
|
|
200
432
|
});
|
|
201
433
|
});
|
|
434
|
+
// The line's own buttons, each shown by its condition on the line; a press runs with the line.
|
|
435
|
+
const buttons = new Map();
|
|
436
|
+
for (const own of kind ? [] : (node.rowButtons ?? [])) {
|
|
437
|
+
const icon = drawIcon(document, own.icon);
|
|
438
|
+
const button = make('button', { type: 'button', class: `fd-button fd-button-link fd-line-button${icon ? ' fd-line-button-icon' : ''}`, 'data-row-button': own.id, 'aria-label': own.label, title: own.label });
|
|
439
|
+
button.append(icon ?? own.label);
|
|
440
|
+
button.addEventListener('click', async () => {
|
|
441
|
+
if (button.hasAttribute('aria-busy'))
|
|
442
|
+
return;
|
|
443
|
+
button.setAttribute('aria-busy', 'true');
|
|
444
|
+
try {
|
|
445
|
+
await form.runRowAction(node.id, own.id, line.key);
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
button.removeAttribute('aria-busy');
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
buttons.set(own.id, button);
|
|
452
|
+
tools.append(button);
|
|
453
|
+
}
|
|
454
|
+
// A copy of the line, its values too, right after it: never its saved id or its place in the order.
|
|
455
|
+
const copy = options['copy'] === true && !kind ? make('button', { type: 'button', class: 'fd-line-copy' }, '⧉') : null;
|
|
456
|
+
copy?.addEventListener('click', () => {
|
|
457
|
+
const all = current();
|
|
458
|
+
const at = all.findIndex((l) => l.key === line.key);
|
|
459
|
+
if (readonly || at < 0 || all.length >= max)
|
|
460
|
+
return;
|
|
461
|
+
const values = { ...all[at].values };
|
|
462
|
+
if (def.sequenceField)
|
|
463
|
+
delete values[def.sequenceField];
|
|
464
|
+
const made = form.addLine(name, values);
|
|
465
|
+
moveLineTo(form, name, def, made, at + 1);
|
|
466
|
+
rows.get(made)?.cells[0]?.widget.focus();
|
|
467
|
+
});
|
|
468
|
+
if (copy)
|
|
469
|
+
tools.append(copy);
|
|
470
|
+
// Its own page, its own form (lineForm), or its every field, in a dialog (lineOpens).
|
|
471
|
+
if ((node.lineOpens || node.lineForm) && dialogs && !kind) {
|
|
472
|
+
const open = make('button', { type: 'button', class: 'fd-line-open', 'aria-label': labels.openLine, title: labels.openLine }, '↗');
|
|
473
|
+
open.addEventListener('click', () => {
|
|
474
|
+
const now = current().find((l) => l.key === line.key);
|
|
475
|
+
if (now)
|
|
476
|
+
void openLineDialog(form, name, node, now, dialogs, readonly);
|
|
477
|
+
});
|
|
478
|
+
tools.append(open);
|
|
479
|
+
}
|
|
202
480
|
tools.append(remove);
|
|
203
481
|
tr.append(tools);
|
|
204
|
-
return { element: tr, kind, cells, remove, grip };
|
|
482
|
+
return { element: tr, kind, cells, remove, grip, buttons, pick, copy };
|
|
205
483
|
}
|
|
206
484
|
return {
|
|
207
485
|
element,
|
|
@@ -215,6 +493,37 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
215
493
|
if (empty)
|
|
216
494
|
empty.hidden = current.length > 0;
|
|
217
495
|
const errors = form.getState().errors;
|
|
496
|
+
// Columns the record hides now: their head, cells and total go with them.
|
|
497
|
+
const gone = new Set(ruled ? columns.filter((column) => form.columnHidden(node.id, column)) : []);
|
|
498
|
+
for (const [column, th] of heads)
|
|
499
|
+
th.hidden = gone.has(column);
|
|
500
|
+
for (const [column, td] of footCells)
|
|
501
|
+
td.hidden = gone.has(column);
|
|
502
|
+
// A column of words or links as wide as its longest value, as a list of words is: a long name has the room a code does not need.
|
|
503
|
+
// Measured as lines come and go, not as a cell is typed in: the columns never move under the person's hands.
|
|
504
|
+
const sizedFor = current.map((line) => line.key).join(' ');
|
|
505
|
+
const resize = sizedFor !== columnsSizedFor;
|
|
506
|
+
columnsSizedFor = sizedFor;
|
|
507
|
+
for (const [column, th] of resize ? heads : new Map()) {
|
|
508
|
+
if (node.cells?.[column]?.width)
|
|
509
|
+
continue;
|
|
510
|
+
const sub = def.fields[column];
|
|
511
|
+
if (th.dataset['fit']) {
|
|
512
|
+
// A number keeps its kind's width at the least, and has room for its longest value too ("E£ 21,600,000.00"), never cut.
|
|
513
|
+
if (!NUMERIC.has(sub.type))
|
|
514
|
+
continue;
|
|
515
|
+
const widest = current.reduce((most, line) => (lineKind(def, line.values) ? most : Math.max(most, cellText(sub, line.values[column], line.values, locale).length)), 0);
|
|
516
|
+
th.style.setProperty('--fd-fit-need', `${widest + 4}ch`);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const longest = current.reduce((most, line) => (lineKind(def, line.values) ? most : Math.max(most, cellText(sub, line.values[column], line.values, locale).length)), 0);
|
|
520
|
+
// A link's box holds its open and clear buttons too.
|
|
521
|
+
const buttons = sub.type === 'many2one' ? 5 : 0;
|
|
522
|
+
// Its head may wrap: a long label over short codes does not widen it.
|
|
523
|
+
th.style.width = `${Math.min(Math.max(longest, Math.min(sub.label.length, 10), 6), 32) + 4 + buttons}ch`;
|
|
524
|
+
// Short values keep their whole width when the table is tight: the long ones give way.
|
|
525
|
+
th.style.minWidth = `${Math.min(Math.max(longest, 6), 14) + 4}ch`;
|
|
526
|
+
}
|
|
218
527
|
const keep = new Set(current.map((line) => line.key));
|
|
219
528
|
for (const [key, row] of rows) {
|
|
220
529
|
if (!keep.has(key)) {
|
|
@@ -235,30 +544,59 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
235
544
|
}
|
|
236
545
|
if (body.children[index] !== row.element)
|
|
237
546
|
body.insertBefore(row.element, body.children[index] ?? null);
|
|
238
|
-
|
|
547
|
+
const now = ruled ? form.lineState(node.id, line.key) : null;
|
|
548
|
+
// A line the table keeps (lineDelete) shows no ×.
|
|
549
|
+
row.remove.hidden = readonly || current.length <= min || now?.deletable === false;
|
|
239
550
|
row.grip.hidden = readonly;
|
|
551
|
+
// Named by where it is now: it moves.
|
|
552
|
+
const lineName = fillIn(labels.lineN, { n: index + 1 });
|
|
553
|
+
row.pick?.setAttribute('aria-label', fillIn(labels.chooseLine, { name: lineName }));
|
|
554
|
+
if (row.copy) {
|
|
555
|
+
row.copy.setAttribute('aria-label', fillIn(labels.copy, { name: lineName }));
|
|
556
|
+
row.copy.title = row.copy.getAttribute('aria-label');
|
|
557
|
+
row.copy.hidden = readonly || current.length >= max;
|
|
558
|
+
}
|
|
240
559
|
if (readonly)
|
|
241
560
|
row.remove.remove();
|
|
242
561
|
else if (!row.remove.isConnected)
|
|
243
562
|
row.element.lastElementChild?.append(row.remove);
|
|
563
|
+
setData(row.element, 'tone', now?.tone);
|
|
564
|
+
row.element.classList.toggle('fd-line-bold', !!now?.bold);
|
|
565
|
+
// A line's buttons act on the saved line, so a read-only table keeps them, as the grid and Flectra's lists do.
|
|
566
|
+
for (const [id, button] of row.buttons)
|
|
567
|
+
button.hidden = !now?.buttons[id];
|
|
568
|
+
// A section's or note's one cell spans the columns shown.
|
|
569
|
+
if (row.kind)
|
|
570
|
+
row.cells[0].td.colSpan = columns.length - gone.size;
|
|
244
571
|
for (const cell of row.cells) {
|
|
245
572
|
const message = errors[`${name}.${line.key}.${cell.name}`];
|
|
573
|
+
const rules = row.kind ? undefined : now?.cells[cell.name];
|
|
574
|
+
if (!row.kind)
|
|
575
|
+
cell.td.hidden = gone.has(cell.name);
|
|
576
|
+
// Hidden by its line, the cell stays, empty, so the column lines up.
|
|
577
|
+
cell.widget.element.hidden = !!rules?.invisible;
|
|
578
|
+
setData(cell.td, 'tone', rules?.tone);
|
|
579
|
+
cell.td.classList.toggle('fd-cell-bold', !!rules?.bold);
|
|
246
580
|
cell.error.hidden = !message;
|
|
247
581
|
cell.error.textContent = message ?? '';
|
|
248
582
|
cell.widget.update({
|
|
249
583
|
value: line.values[cell.name],
|
|
250
584
|
values: line.values,
|
|
585
|
+
parent: state.values,
|
|
251
586
|
// A value worked out from the line's others is shown, never typed.
|
|
252
|
-
readonly: readonly || cell.def.readonly === true || cell.def.compute !== undefined,
|
|
253
|
-
required: cell.def.required === true,
|
|
587
|
+
readonly: readonly || cell.def.readonly === true || cell.def.compute !== undefined || !!rules?.readonly,
|
|
588
|
+
required: cell.def.required === true || !!rules?.required,
|
|
254
589
|
invalid: !!message,
|
|
255
590
|
});
|
|
256
591
|
}
|
|
257
592
|
});
|
|
258
593
|
for (const [column, td] of sums) {
|
|
259
594
|
const sum = current.filter((line) => !lineKind(def, line.values)).reduce((total, line) => total + Number(line.values[column] ?? 0), 0);
|
|
260
|
-
td.textContent =
|
|
595
|
+
td.textContent = cellText(def.fields[column], sum, current[0]?.values ?? {}, locale, state.values, node.cells?.[column]);
|
|
261
596
|
}
|
|
597
|
+
for (const { id, button } of controls)
|
|
598
|
+
button.hidden = form.node(id).invisible;
|
|
599
|
+
showChosen();
|
|
262
600
|
if (focusNew && rows.has(focusNew)) {
|
|
263
601
|
rows.get(focusNew)?.cells[0]?.widget.focus();
|
|
264
602
|
focusNew = null;
|
|
@@ -266,4 +604,11 @@ export const linesWidget = ({ form, name, field, node, id, document, labels = WI
|
|
|
266
604
|
},
|
|
267
605
|
};
|
|
268
606
|
};
|
|
607
|
+
/** A data attribute set, or taken away when there is nothing to say. */
|
|
608
|
+
function setData(element, key, value) {
|
|
609
|
+
if (value)
|
|
610
|
+
element.dataset[key] = value;
|
|
611
|
+
else
|
|
612
|
+
delete element.dataset[key];
|
|
613
|
+
}
|
|
269
614
|
//# sourceMappingURL=lines.mjs.map
|