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