flexdesk 0.17.0 → 0.17.2
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 +41 -0
- package/README.md +399 -3
- package/canvas.js +18 -0
- package/charts.js +21 -0
- package/core.js +14 -0
- package/css/base.css +23 -1
- package/css/flexdesk.css +61 -1
- 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 +61 -1
- package/dist/flow.js +2 -2
- package/dist/flow.js.map +2 -2
- package/dist/tiles.js +2 -2
- package/dist/widgets.js +186 -55
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +90 -10
- package/dist/wm.js.map +2 -2
- 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/tiling/command_palette.js +46 -7
- package/src/tiling/content_registry.js +3 -0
- package/src/tiling/entity_sources.js +10 -0
- package/src/tiling/wm.js +65 -3
- 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/settings_view.js +192 -51
- package/src/ui/components/staged_edits.js +19 -1
- package/src/ui/components/tsv.js +74 -0
- package/src/ui/notification_center.js +5 -5
- 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
|
@@ -3,6 +3,612 @@ import {
|
|
|
3
3
|
isRowControl
|
|
4
4
|
} from "./chunk-CVMCZZX2.js";
|
|
5
5
|
|
|
6
|
+
// src/ui/components/autocomplete_field.js
|
|
7
|
+
var AutocompleteField = class {
|
|
8
|
+
constructor({
|
|
9
|
+
fieldId,
|
|
10
|
+
field = {},
|
|
11
|
+
provider = null,
|
|
12
|
+
onChange = null,
|
|
13
|
+
logger = null,
|
|
14
|
+
namespace = null,
|
|
15
|
+
namespaceOptions = [],
|
|
16
|
+
showNamespaceSelect = false,
|
|
17
|
+
variant = null,
|
|
18
|
+
showNamespaceChip = false,
|
|
19
|
+
allowScopeToggle = false,
|
|
20
|
+
namespaceLabel = null,
|
|
21
|
+
scopeMode = "current",
|
|
22
|
+
skipNamespacePrefix = false,
|
|
23
|
+
// Material symbol per completion type, merged over the generic set. The
|
|
24
|
+
// framework's map used to include `sector: 'domain'` — an EcoAgent
|
|
25
|
+
// completion type — so the widget knew one application's ontology.
|
|
26
|
+
typeIcons = {}
|
|
27
|
+
} = {}) {
|
|
28
|
+
if (!fieldId) {
|
|
29
|
+
throw new Error("AutocompleteField requires a fieldId");
|
|
30
|
+
}
|
|
31
|
+
this.fieldId = fieldId;
|
|
32
|
+
this.field = field;
|
|
33
|
+
this.provider = typeof provider === "function" ? provider : () => [];
|
|
34
|
+
this.onChange = typeof onChange === "function" ? onChange : null;
|
|
35
|
+
this.logger = logger;
|
|
36
|
+
this.typeIcons = typeIcons ?? {};
|
|
37
|
+
this.namespace = namespace;
|
|
38
|
+
this.namespaceOptions = Array.isArray(namespaceOptions) ? [...namespaceOptions] : [];
|
|
39
|
+
if (this.namespace && !this.namespaceOptions.some((opt) => opt.value === this.namespace)) {
|
|
40
|
+
const fallbackLabel = this.field.namespaceLabel || "This Tab";
|
|
41
|
+
this.namespaceOptions.push({ value: this.namespace, label: fallbackLabel, token: fallbackLabel });
|
|
42
|
+
}
|
|
43
|
+
this.showNamespaceSelect = Boolean(showNamespaceSelect);
|
|
44
|
+
this.variant = variant;
|
|
45
|
+
this.showNamespaceChip = Boolean(showNamespaceChip);
|
|
46
|
+
this.allowScopeToggle = Boolean(allowScopeToggle);
|
|
47
|
+
this.namespaceLabel = namespaceLabel;
|
|
48
|
+
this.scope = scopeMode === "all" ? "all" : "current";
|
|
49
|
+
this.skipNamespacePrefix = Boolean(skipNamespacePrefix);
|
|
50
|
+
this.disabled = Boolean(field.disabled);
|
|
51
|
+
const parsed = this.#parseInitialNamespace(field.value ?? "");
|
|
52
|
+
this.selectedNamespace = parsed.initialNamespace;
|
|
53
|
+
this.initialFragment = parsed.initialFragment;
|
|
54
|
+
this._lastDropdownRect = null;
|
|
55
|
+
this.root = null;
|
|
56
|
+
this.input = null;
|
|
57
|
+
this.dropdown = null;
|
|
58
|
+
this.helperEl = null;
|
|
59
|
+
this.scopeControl = null;
|
|
60
|
+
this.namespaceSelect = null;
|
|
61
|
+
this.ghost = null;
|
|
62
|
+
this._items = [];
|
|
63
|
+
this._activeIndex = -1;
|
|
64
|
+
this._isOpen = false;
|
|
65
|
+
this._disposers = [];
|
|
66
|
+
this._suggestSeq = 0;
|
|
67
|
+
}
|
|
68
|
+
getValue() {
|
|
69
|
+
return this.#composeValue();
|
|
70
|
+
}
|
|
71
|
+
setComposedValue(value) {
|
|
72
|
+
if (!this.input) return;
|
|
73
|
+
const safeValue = typeof value === "string" ? value : "";
|
|
74
|
+
if (this.skipNamespacePrefix) {
|
|
75
|
+
this.input.value = safeValue;
|
|
76
|
+
this.initialFragment = safeValue;
|
|
77
|
+
this.#renderGhost();
|
|
78
|
+
this.#close();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const parsed = this.#parseInitialNamespace(safeValue);
|
|
82
|
+
this.selectedNamespace = parsed.initialNamespace;
|
|
83
|
+
this.initialFragment = parsed.initialFragment;
|
|
84
|
+
if (this.namespaceSelect) {
|
|
85
|
+
this.namespaceSelect.value = this.selectedNamespace;
|
|
86
|
+
}
|
|
87
|
+
this.#updateNamespaceChip();
|
|
88
|
+
this.input.value = parsed.initialFragment;
|
|
89
|
+
this.#renderGhost();
|
|
90
|
+
this.#close();
|
|
91
|
+
}
|
|
92
|
+
#parseInitialNamespace(rawValue) {
|
|
93
|
+
const value = typeof rawValue === "string" ? rawValue.trim() : "";
|
|
94
|
+
const options = this.namespaceOptions;
|
|
95
|
+
const defaultNs = this.namespace ? options.find((opt) => opt.value === this.namespace)?.value ?? this.namespace : options[0]?.value ?? "__ALL__";
|
|
96
|
+
if (!options.length) {
|
|
97
|
+
return { initialNamespace: defaultNs, initialFragment: value };
|
|
98
|
+
}
|
|
99
|
+
if (!value) {
|
|
100
|
+
return { initialNamespace: defaultNs, initialFragment: "" };
|
|
101
|
+
}
|
|
102
|
+
const dotIndex = value.indexOf(".");
|
|
103
|
+
if (dotIndex > 0) {
|
|
104
|
+
const nsCandidate = value.slice(0, dotIndex);
|
|
105
|
+
const match = options.find(
|
|
106
|
+
(opt) => (opt.value || "").toLowerCase() === nsCandidate.toLowerCase() || (opt.label || "").toLowerCase() === nsCandidate.toLowerCase()
|
|
107
|
+
);
|
|
108
|
+
if (match) {
|
|
109
|
+
return { initialNamespace: match.value, initialFragment: value.slice(dotIndex + 1) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { initialNamespace: defaultNs, initialFragment: value };
|
|
113
|
+
}
|
|
114
|
+
#resolveNamespaceLabel() {
|
|
115
|
+
if (this.scope === "all") {
|
|
116
|
+
return "All Namespaces";
|
|
117
|
+
}
|
|
118
|
+
if (typeof this.namespaceLabel === "string" && this.namespaceLabel.trim()) {
|
|
119
|
+
return this.namespaceLabel.trim();
|
|
120
|
+
}
|
|
121
|
+
if (typeof this.field.namespaceLabel === "string" && this.field.namespaceLabel.trim()) {
|
|
122
|
+
return this.field.namespaceLabel.trim();
|
|
123
|
+
}
|
|
124
|
+
if (typeof this.namespace === "string" && this.namespace.trim()) {
|
|
125
|
+
return this.namespace.trim();
|
|
126
|
+
}
|
|
127
|
+
return "This Tab";
|
|
128
|
+
}
|
|
129
|
+
#toggleScope() {
|
|
130
|
+
if (this.disabled) return;
|
|
131
|
+
this.scope = this.scope === "all" ? "current" : "all";
|
|
132
|
+
this.root?.classList?.toggle("twm-autocomplete-field--scope-all", this.scope === "all");
|
|
133
|
+
this.#updateNamespaceChip();
|
|
134
|
+
this.#updateSuggestions();
|
|
135
|
+
}
|
|
136
|
+
#updateNamespaceChip() {
|
|
137
|
+
if (!this.scopeControl) return;
|
|
138
|
+
this.scopeControl.textContent = this.#resolveNamespaceLabel();
|
|
139
|
+
this.scopeControl.dataset.scope = this.scope;
|
|
140
|
+
this.scopeControl.title = this.scope === "all" ? "Searching across all namespaces (click to limit to this tab)" : "Searching this tab (click to search all namespaces)";
|
|
141
|
+
}
|
|
142
|
+
render() {
|
|
143
|
+
if (this.root) {
|
|
144
|
+
return this.root;
|
|
145
|
+
}
|
|
146
|
+
this.root = document.createElement("div");
|
|
147
|
+
this.root.className = "twm-autocomplete-field";
|
|
148
|
+
if (this.variant) {
|
|
149
|
+
this.root.classList.add(`twm-autocomplete-field--${this.variant}`);
|
|
150
|
+
}
|
|
151
|
+
this.root.classList.toggle("twm-autocomplete-field--scope-all", this.scope === "all");
|
|
152
|
+
const inputWrapper = document.createElement("div");
|
|
153
|
+
inputWrapper.className = "twm-autocomplete-field__input-wrapper";
|
|
154
|
+
if (this.showNamespaceSelect && this.namespaceOptions.length) {
|
|
155
|
+
const select = document.createElement("select");
|
|
156
|
+
select.className = "twm-autocomplete-field__namespace-select";
|
|
157
|
+
this.namespaceOptions.forEach((opt) => {
|
|
158
|
+
const option = document.createElement("option");
|
|
159
|
+
option.value = opt.value;
|
|
160
|
+
option.textContent = opt.label ?? opt.value;
|
|
161
|
+
if (opt.value === this.selectedNamespace) {
|
|
162
|
+
option.selected = true;
|
|
163
|
+
}
|
|
164
|
+
select.appendChild(option);
|
|
165
|
+
});
|
|
166
|
+
this.namespaceSelect = select;
|
|
167
|
+
inputWrapper.appendChild(select);
|
|
168
|
+
}
|
|
169
|
+
if (this.showNamespaceChip) {
|
|
170
|
+
const nsChip = document.createElement(this.allowScopeToggle ? "button" : "span");
|
|
171
|
+
nsChip.className = "twm-autocomplete-field__namespace";
|
|
172
|
+
if (this.allowScopeToggle) {
|
|
173
|
+
nsChip.setAttribute("type", "button");
|
|
174
|
+
}
|
|
175
|
+
if (this.allowScopeToggle) {
|
|
176
|
+
nsChip.classList.add("twm-autocomplete-field__namespace--toggle");
|
|
177
|
+
nsChip.addEventListener("click", () => this.#toggleScope());
|
|
178
|
+
}
|
|
179
|
+
this.scopeControl = nsChip;
|
|
180
|
+
this.#updateNamespaceChip();
|
|
181
|
+
inputWrapper.appendChild(nsChip);
|
|
182
|
+
}
|
|
183
|
+
this.input = document.createElement("input");
|
|
184
|
+
this.input.type = "text";
|
|
185
|
+
this.input.className = "twm-autocomplete-field__input";
|
|
186
|
+
this.input.id = this.fieldId;
|
|
187
|
+
this.input.autocomplete = "off";
|
|
188
|
+
this.input.spellcheck = false;
|
|
189
|
+
if (this.field.placeholder) {
|
|
190
|
+
this.input.placeholder = this.field.placeholder;
|
|
191
|
+
}
|
|
192
|
+
if (this.field.value != null) {
|
|
193
|
+
this.input.value = this.initialFragment;
|
|
194
|
+
}
|
|
195
|
+
if (this.disabled) {
|
|
196
|
+
this.input.disabled = true;
|
|
197
|
+
this.root.classList.add("twm-autocomplete-field--disabled");
|
|
198
|
+
}
|
|
199
|
+
inputWrapper.appendChild(this.input);
|
|
200
|
+
this.ghost = null;
|
|
201
|
+
this.dropdown = document.createElement("div");
|
|
202
|
+
this.dropdown.className = "twm-autocomplete-field__dropdown";
|
|
203
|
+
this.dropdown.setAttribute("role", "listbox");
|
|
204
|
+
document.body.appendChild(this.dropdown);
|
|
205
|
+
this.helperEl = document.createElement("div");
|
|
206
|
+
this.helperEl.className = "twm-autocomplete-field__helper twm-config-hint";
|
|
207
|
+
if (this.field.helperText) {
|
|
208
|
+
this.helperEl.textContent = this.field.helperText;
|
|
209
|
+
}
|
|
210
|
+
this.root.appendChild(inputWrapper);
|
|
211
|
+
this.root.appendChild(this.helperEl);
|
|
212
|
+
this.#wireEvents();
|
|
213
|
+
this.#renderGhost();
|
|
214
|
+
return this.root;
|
|
215
|
+
}
|
|
216
|
+
#wireEvents() {
|
|
217
|
+
const onInput = () => {
|
|
218
|
+
if (this.disabled) return;
|
|
219
|
+
this.#syncNamespaceFromInput();
|
|
220
|
+
this.#renderGhost();
|
|
221
|
+
this.#updateSuggestions();
|
|
222
|
+
};
|
|
223
|
+
const onChange = () => {
|
|
224
|
+
this.onChange?.(this.#composeValue(), { immediate: false, namespace: this.selectedNamespace });
|
|
225
|
+
};
|
|
226
|
+
const onPaste = () => {
|
|
227
|
+
};
|
|
228
|
+
const onKeyDown = (e) => {
|
|
229
|
+
if (this.disabled) return;
|
|
230
|
+
this.#handleKeyDown(e);
|
|
231
|
+
};
|
|
232
|
+
const onFocus = () => {
|
|
233
|
+
if (this.disabled) return;
|
|
234
|
+
this.#updateSuggestions();
|
|
235
|
+
};
|
|
236
|
+
const onBlur = (e) => {
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
if (!this.root?.contains(document.activeElement)) {
|
|
239
|
+
this.#close();
|
|
240
|
+
this.onChange?.(this.#composeValue(), { immediate: false, namespace: this.selectedNamespace });
|
|
241
|
+
}
|
|
242
|
+
}, 150);
|
|
243
|
+
};
|
|
244
|
+
const onDropdownMouseDown = (e) => {
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
};
|
|
247
|
+
const onDropdownClick = (e) => {
|
|
248
|
+
const item = e.target.closest(".twm-autocomplete-field__item");
|
|
249
|
+
if (item) {
|
|
250
|
+
const index = parseInt(item.dataset.index, 10);
|
|
251
|
+
if (!isNaN(index)) {
|
|
252
|
+
this.#selectItem(index);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
this.input.addEventListener("input", onInput);
|
|
257
|
+
this.input.addEventListener("change", onChange);
|
|
258
|
+
this.input.addEventListener("paste", onPaste);
|
|
259
|
+
this.input.addEventListener("keydown", onKeyDown);
|
|
260
|
+
this.input.addEventListener("focus", onFocus);
|
|
261
|
+
this.input.addEventListener("blur", onBlur);
|
|
262
|
+
this.dropdown.addEventListener("mousedown", onDropdownMouseDown);
|
|
263
|
+
this.dropdown.addEventListener("click", onDropdownClick);
|
|
264
|
+
if (this.namespaceSelect) {
|
|
265
|
+
const onSelectChange = () => {
|
|
266
|
+
this.selectedNamespace = this.namespaceSelect.value;
|
|
267
|
+
this.#updateSuggestions();
|
|
268
|
+
this.onChange?.(this.#composeValue(), {
|
|
269
|
+
immediate: false,
|
|
270
|
+
namespace: this.selectedNamespace,
|
|
271
|
+
viewNamespace: this.selectedNamespace
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
this.namespaceSelect.addEventListener("change", onSelectChange);
|
|
275
|
+
this._disposers.push(() => this.namespaceSelect.removeEventListener("change", onSelectChange));
|
|
276
|
+
}
|
|
277
|
+
const onWindowResize = () => {
|
|
278
|
+
if (this._isOpen) {
|
|
279
|
+
this.#positionDropdown();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
window.addEventListener("resize", onWindowResize);
|
|
283
|
+
const onScroll = (e) => {
|
|
284
|
+
if (!this._isOpen) return;
|
|
285
|
+
if (this.dropdown?.contains(e.target)) return;
|
|
286
|
+
this.#close();
|
|
287
|
+
};
|
|
288
|
+
document.addEventListener("scroll", onScroll, true);
|
|
289
|
+
this._disposers.push(() => {
|
|
290
|
+
document.removeEventListener("scroll", onScroll, true);
|
|
291
|
+
});
|
|
292
|
+
const onDocumentClick = (e) => {
|
|
293
|
+
if (!this._isOpen) return;
|
|
294
|
+
const clickedInRoot = this.root?.contains(e.target);
|
|
295
|
+
const clickedInDropdown = this.dropdown?.contains(e.target);
|
|
296
|
+
if (!clickedInRoot && !clickedInDropdown) {
|
|
297
|
+
this.#close();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
document.addEventListener("click", onDocumentClick, true);
|
|
301
|
+
this._disposers.push(() => {
|
|
302
|
+
window.removeEventListener("resize", onWindowResize);
|
|
303
|
+
});
|
|
304
|
+
this._disposers.push(() => {
|
|
305
|
+
document.removeEventListener("click", onDocumentClick, true);
|
|
306
|
+
});
|
|
307
|
+
this._disposers.push(() => {
|
|
308
|
+
this.input.removeEventListener("input", onInput);
|
|
309
|
+
this.input.removeEventListener("change", onChange);
|
|
310
|
+
this.input.removeEventListener("paste", onPaste);
|
|
311
|
+
this.input.removeEventListener("keydown", onKeyDown);
|
|
312
|
+
this.input.removeEventListener("focus", onFocus);
|
|
313
|
+
this.input.removeEventListener("blur", onBlur);
|
|
314
|
+
this.dropdown.removeEventListener("mousedown", onDropdownMouseDown);
|
|
315
|
+
this.dropdown.removeEventListener("click", onDropdownClick);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
#handleKeyDown(e) {
|
|
319
|
+
if (!this._isOpen) {
|
|
320
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
321
|
+
this.#updateSuggestions();
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
switch (e.key) {
|
|
327
|
+
case "ArrowDown":
|
|
328
|
+
e.preventDefault();
|
|
329
|
+
this.#moveSelection(1);
|
|
330
|
+
break;
|
|
331
|
+
case "ArrowUp":
|
|
332
|
+
e.preventDefault();
|
|
333
|
+
this.#moveSelection(-1);
|
|
334
|
+
break;
|
|
335
|
+
case "Enter":
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
if (this._activeIndex >= 0) {
|
|
338
|
+
this.#selectItem(this._activeIndex);
|
|
339
|
+
}
|
|
340
|
+
this.input?.blur();
|
|
341
|
+
break;
|
|
342
|
+
case "Tab":
|
|
343
|
+
if (this._activeIndex >= 0) {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
this.#selectItem(this._activeIndex);
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
case "Escape":
|
|
349
|
+
e.preventDefault();
|
|
350
|
+
this.#close();
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
#updateSuggestions() {
|
|
355
|
+
const fragment = this.#getInputFragment();
|
|
356
|
+
const request = ++this._suggestSeq;
|
|
357
|
+
let produced;
|
|
358
|
+
try {
|
|
359
|
+
produced = this.provider({
|
|
360
|
+
value: fragment,
|
|
361
|
+
scope: this.scope,
|
|
362
|
+
namespace: this.selectedNamespace
|
|
363
|
+
});
|
|
364
|
+
} catch (err) {
|
|
365
|
+
this.logger?.warn?.("autocomplete", "Provider error", { err });
|
|
366
|
+
this._items = [];
|
|
367
|
+
this.#close();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (produced && typeof produced.then === "function") {
|
|
371
|
+
produced.then(
|
|
372
|
+
(items) => {
|
|
373
|
+
if (request !== this._suggestSeq) return;
|
|
374
|
+
this.#applySuggestions(items);
|
|
375
|
+
},
|
|
376
|
+
(err) => {
|
|
377
|
+
this.logger?.warn?.("autocomplete", "Provider error", { err });
|
|
378
|
+
if (request !== this._suggestSeq) return;
|
|
379
|
+
this._items = [];
|
|
380
|
+
this.#close();
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
this.#applySuggestions(produced);
|
|
386
|
+
}
|
|
387
|
+
/** Draw whatever the provider produced. Split out of `#updateSuggestions`
|
|
388
|
+
* so the synchronous and the awaited paths cannot drift apart. */
|
|
389
|
+
#applySuggestions(produced) {
|
|
390
|
+
const items = Array.isArray(produced) ? produced : [];
|
|
391
|
+
this._items = items;
|
|
392
|
+
this._activeIndex = items.length > 0 ? 0 : -1;
|
|
393
|
+
this.#renderDropdown();
|
|
394
|
+
if (items.length > 0) {
|
|
395
|
+
this.#open();
|
|
396
|
+
} else {
|
|
397
|
+
this.#close();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
#renderDropdown() {
|
|
401
|
+
this.dropdown.innerHTML = "";
|
|
402
|
+
const showingAll = this.selectedNamespace === "__ALL__";
|
|
403
|
+
this._items.forEach((item, index) => {
|
|
404
|
+
const el = document.createElement("div");
|
|
405
|
+
el.className = "twm-autocomplete-field__item";
|
|
406
|
+
el.setAttribute("role", "option");
|
|
407
|
+
el.dataset.index = index;
|
|
408
|
+
el.dataset.type = item.type || item.iconType || "variable";
|
|
409
|
+
if (index === this._activeIndex) {
|
|
410
|
+
el.classList.add("active");
|
|
411
|
+
}
|
|
412
|
+
const icon = document.createElement("span");
|
|
413
|
+
icon.className = "twm-autocomplete-field__icon material-symbols-outlined";
|
|
414
|
+
icon.textContent = this.#getIconForType(item.type || item.iconType);
|
|
415
|
+
el.appendChild(icon);
|
|
416
|
+
const label = document.createElement("span");
|
|
417
|
+
label.className = "twm-autocomplete-field__label";
|
|
418
|
+
label.textContent = item.label || item.insertText || "";
|
|
419
|
+
el.appendChild(label);
|
|
420
|
+
if (showingAll && item.metadata?.namespaceId) {
|
|
421
|
+
const nsLabel = this.#resolveNamespaceToken(item.metadata.namespaceId);
|
|
422
|
+
if (nsLabel) {
|
|
423
|
+
const nsIndicator = document.createElement("span");
|
|
424
|
+
nsIndicator.className = "twm-autocomplete-field__namespace-indicator";
|
|
425
|
+
nsIndicator.textContent = nsLabel;
|
|
426
|
+
el.appendChild(nsIndicator);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (item.type && !showingAll) {
|
|
430
|
+
const badge = document.createElement("span");
|
|
431
|
+
badge.className = "twm-autocomplete-field__badge";
|
|
432
|
+
badge.textContent = item.type;
|
|
433
|
+
el.appendChild(badge);
|
|
434
|
+
}
|
|
435
|
+
this.dropdown.appendChild(el);
|
|
436
|
+
});
|
|
437
|
+
if (this._isOpen) {
|
|
438
|
+
this.#positionDropdown();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
#resolveNamespaceToken(namespaceId) {
|
|
442
|
+
if (!namespaceId) return null;
|
|
443
|
+
const opt = this.namespaceOptions.find((o) => o.value === namespaceId);
|
|
444
|
+
return opt?.token || opt?.label || null;
|
|
445
|
+
}
|
|
446
|
+
#getIconForType(type) {
|
|
447
|
+
const icons = {
|
|
448
|
+
variable: "functions",
|
|
449
|
+
"variable-qualified": "link",
|
|
450
|
+
stock: "account_balance",
|
|
451
|
+
account: "savings",
|
|
452
|
+
function: "code",
|
|
453
|
+
constant: "numbers",
|
|
454
|
+
operator: "calculate",
|
|
455
|
+
snippet: "content_paste",
|
|
456
|
+
...this.typeIcons
|
|
457
|
+
};
|
|
458
|
+
return icons[type] || "label";
|
|
459
|
+
}
|
|
460
|
+
#moveSelection(delta) {
|
|
461
|
+
if (this._items.length === 0) return;
|
|
462
|
+
let newIndex = this._activeIndex + delta;
|
|
463
|
+
if (newIndex < 0) newIndex = this._items.length - 1;
|
|
464
|
+
if (newIndex >= this._items.length) newIndex = 0;
|
|
465
|
+
this._activeIndex = newIndex;
|
|
466
|
+
this.#renderDropdown();
|
|
467
|
+
this.#scrollToActive();
|
|
468
|
+
}
|
|
469
|
+
#scrollToActive() {
|
|
470
|
+
const activeEl = this.dropdown.querySelector(".twm-autocomplete-field__item.active");
|
|
471
|
+
if (activeEl) {
|
|
472
|
+
activeEl.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
#selectItem(index) {
|
|
476
|
+
const item = this._items[index];
|
|
477
|
+
if (!item) return;
|
|
478
|
+
const itemNamespace = item.metadata?.namespaceId || null;
|
|
479
|
+
const insertText = item.insertText || item.label || "";
|
|
480
|
+
this.input.value = insertText;
|
|
481
|
+
const viewNamespace = this.selectedNamespace;
|
|
482
|
+
const resolvedNamespace = viewNamespace === "__ALL__" && itemNamespace ? itemNamespace : viewNamespace;
|
|
483
|
+
this.#close();
|
|
484
|
+
this.input.focus();
|
|
485
|
+
this.onChange?.(this.#composeValue(), {
|
|
486
|
+
immediate: true,
|
|
487
|
+
namespace: resolvedNamespace,
|
|
488
|
+
viewNamespace,
|
|
489
|
+
metadata: item.metadata || null
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
#open() {
|
|
493
|
+
if (this._isOpen) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
this._isOpen = true;
|
|
497
|
+
this.#positionDropdown();
|
|
498
|
+
this.dropdown.classList.add("visible");
|
|
499
|
+
}
|
|
500
|
+
#close() {
|
|
501
|
+
if (!this._isOpen) return;
|
|
502
|
+
this._isOpen = false;
|
|
503
|
+
this.dropdown.classList.remove("visible");
|
|
504
|
+
this._activeIndex = -1;
|
|
505
|
+
}
|
|
506
|
+
#syncNamespaceFromInput() {
|
|
507
|
+
if (!this.namespaceOptions.length) return;
|
|
508
|
+
const value = this.input?.value || "";
|
|
509
|
+
const dotIndex = value.indexOf(".");
|
|
510
|
+
if (dotIndex > 0) {
|
|
511
|
+
const nsCandidate = value.slice(0, dotIndex);
|
|
512
|
+
const match = this.namespaceOptions.find(
|
|
513
|
+
(opt) => opt.value !== "__ALL__" && ((opt.value || "").toLowerCase() === nsCandidate.toLowerCase() || (opt.label || "").toLowerCase() === nsCandidate.toLowerCase() || (opt.token || "").toLowerCase() === nsCandidate.toLowerCase())
|
|
514
|
+
);
|
|
515
|
+
if (match && this.namespaceSelect && this.namespaceSelect.value !== match.value) {
|
|
516
|
+
this.namespaceSelect.value = match.value;
|
|
517
|
+
this.selectedNamespace = match.value;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
#getInputFragment() {
|
|
522
|
+
const value = this.input?.value || "";
|
|
523
|
+
const dotIndex = value.indexOf(".");
|
|
524
|
+
if (dotIndex > 0) {
|
|
525
|
+
const nsCandidate = value.slice(0, dotIndex);
|
|
526
|
+
const match = this.namespaceOptions.find(
|
|
527
|
+
(opt) => opt.value !== "__ALL__" && ((opt.value || "").toLowerCase() === nsCandidate.toLowerCase() || (opt.label || "").toLowerCase() === nsCandidate.toLowerCase())
|
|
528
|
+
);
|
|
529
|
+
if (match) {
|
|
530
|
+
return value.slice(dotIndex + 1);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
535
|
+
#composeValue() {
|
|
536
|
+
const rawInput = this.input?.value || "";
|
|
537
|
+
if (!rawInput) return "";
|
|
538
|
+
if (this.skipNamespacePrefix) {
|
|
539
|
+
return rawInput;
|
|
540
|
+
}
|
|
541
|
+
const dotIndex = rawInput.indexOf(".");
|
|
542
|
+
if (dotIndex > 0) {
|
|
543
|
+
const nsCandidate = rawInput.slice(0, dotIndex);
|
|
544
|
+
const match = this.namespaceOptions.find(
|
|
545
|
+
(opt) => opt.value !== "__ALL__" && ((opt.value || "").toLowerCase() === nsCandidate.toLowerCase() || (opt.label || "").toLowerCase() === nsCandidate.toLowerCase())
|
|
546
|
+
);
|
|
547
|
+
if (match) {
|
|
548
|
+
return rawInput;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const ns = this.selectedNamespace;
|
|
552
|
+
if (!ns || ns === "__ALL__") {
|
|
553
|
+
return rawInput;
|
|
554
|
+
}
|
|
555
|
+
const nsOption = this.namespaceOptions.find(
|
|
556
|
+
(opt) => opt.value === ns || (opt.value || "").toLowerCase() === (ns || "").toLowerCase()
|
|
557
|
+
);
|
|
558
|
+
const nsToken = nsOption?.token || nsOption?.label || "";
|
|
559
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
560
|
+
if (ns === "__ALL__" || (nsToken || "").toUpperCase() === "__ALL__") {
|
|
561
|
+
return rawInput;
|
|
562
|
+
}
|
|
563
|
+
if (!nsToken || uuidPattern.test(nsToken)) {
|
|
564
|
+
return rawInput;
|
|
565
|
+
}
|
|
566
|
+
return `${nsToken}.${rawInput}`;
|
|
567
|
+
}
|
|
568
|
+
#renderGhost() {
|
|
569
|
+
if (this.ghost) {
|
|
570
|
+
this.ghost.style.display = "none";
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
#positionDropdown() {
|
|
574
|
+
if (!this.dropdown || !this.root) return;
|
|
575
|
+
const wrapper = this.root.querySelector(".twm-autocomplete-field__input-wrapper");
|
|
576
|
+
if (!wrapper) return;
|
|
577
|
+
const rect = wrapper.getBoundingClientRect();
|
|
578
|
+
this._lastDropdownRect = rect;
|
|
579
|
+
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
580
|
+
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
581
|
+
const width = Math.min(rect.width, viewportWidth - rect.left - 8);
|
|
582
|
+
const maxHeight = Math.max(120, viewportHeight - rect.bottom - 12);
|
|
583
|
+
Object.assign(this.dropdown.style, {
|
|
584
|
+
position: "fixed",
|
|
585
|
+
top: `${rect.bottom}px`,
|
|
586
|
+
left: `${rect.left}px`,
|
|
587
|
+
width: `${width}px`,
|
|
588
|
+
maxWidth: `${width}px`,
|
|
589
|
+
maxHeight: `${maxHeight}px`
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
dispose() {
|
|
593
|
+
this._suggestSeq += 1;
|
|
594
|
+
this.#close();
|
|
595
|
+
this._disposers.forEach((fn) => {
|
|
596
|
+
try {
|
|
597
|
+
fn();
|
|
598
|
+
} catch (e) {
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
this._disposers = [];
|
|
602
|
+
if (this.dropdown && this.dropdown.parentNode) {
|
|
603
|
+
this.dropdown.parentNode.removeChild(this.dropdown);
|
|
604
|
+
}
|
|
605
|
+
this.root = null;
|
|
606
|
+
this.input = null;
|
|
607
|
+
this.dropdown = null;
|
|
608
|
+
this.helperEl = null;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
6
612
|
// src/ui/components/cell_editor_host.js
|
|
7
613
|
function createTextEditor({ value, initialText = null }) {
|
|
8
614
|
const input = document.createElement("input");
|
|
@@ -33,13 +639,29 @@ function normalise(made) {
|
|
|
33
639
|
const own = isNode ? {} : made;
|
|
34
640
|
let getValue = typeof own.getValue === "function" ? own.getValue : null;
|
|
35
641
|
if (!getValue && el.matches?.("input, select, textarea")) getValue = () => el.value;
|
|
642
|
+
const parse = typeof own.parse === "function" ? own.parse : null;
|
|
643
|
+
let read = null;
|
|
644
|
+
if (parse) {
|
|
645
|
+
read = () => {
|
|
646
|
+
let r;
|
|
647
|
+
try {
|
|
648
|
+
r = parse();
|
|
649
|
+
} catch (err) {
|
|
650
|
+
r = { ok: false, error: err?.message || String(err) };
|
|
651
|
+
}
|
|
652
|
+
return r && r.ok === false ? { ok: false, error: String(r.error ?? "Not valid") } : { ok: true, value: r ? r.value : void 0 };
|
|
653
|
+
};
|
|
654
|
+
} else if (getValue) read = () => ({ ok: true, value: getValue() });
|
|
36
655
|
return {
|
|
37
656
|
el,
|
|
38
657
|
getValue,
|
|
658
|
+
read,
|
|
659
|
+
multiline: !!own.multiline,
|
|
39
660
|
focus: typeof own.focus === "function" ? own.focus : null,
|
|
40
661
|
destroy: typeof own.destroy === "function" ? own.destroy : null
|
|
41
662
|
};
|
|
42
663
|
}
|
|
664
|
+
var MOVES = /* @__PURE__ */ new Set(["down", "up", "right", "left"]);
|
|
43
665
|
var CellEditorHost = class {
|
|
44
666
|
/**
|
|
45
667
|
* @param {object} o
|
|
@@ -47,17 +669,22 @@ var CellEditorHost = class {
|
|
|
47
669
|
* cell is drawn as it was again; `move` is 'down', 'up', 'right', 'left'
|
|
48
670
|
* or null; `reason` is 'key', 'blur', 'editor' or 'scroll'.
|
|
49
671
|
* @param {(session, reason) => void} o.onCancel
|
|
672
|
+
* @param {(session, error, text, reason) => void} [o.onRefuse] a value
|
|
673
|
+
* that did not parse, when the editor closed with it (the focus left,
|
|
674
|
+
* `commit()` from outside); without it such a close is a cancel.
|
|
50
675
|
*/
|
|
51
|
-
constructor({ onCommit, onCancel }) {
|
|
676
|
+
constructor({ onCommit, onCancel, onRefuse = null }) {
|
|
52
677
|
this._onCommit = onCommit;
|
|
53
678
|
this._onCancel = onCancel;
|
|
679
|
+
this._onRefuse = onRefuse;
|
|
54
680
|
this.session = null;
|
|
55
681
|
}
|
|
56
682
|
/**
|
|
57
|
-
* Open an editor in `td
|
|
683
|
+
* Open an editor in `td`; `args` go to the factory beside the cell's own
|
|
684
|
+
* (DataTable's `locale`, `utc`, `messages`). Returns whether one opened (a factory that
|
|
58
685
|
* throws or returns nothing opens none, and the cell is untouched).
|
|
59
686
|
*/
|
|
60
|
-
open({ td, factory, value, row, rowIdx, colIdx, column, initialText = null, ...extra }) {
|
|
687
|
+
open({ td, factory, value, row, rowIdx, colIdx, column, initialText = null, args = {}, ...extra }) {
|
|
61
688
|
if (this.session) return false;
|
|
62
689
|
const s = {
|
|
63
690
|
...extra,
|
|
@@ -76,13 +703,14 @@ var CellEditorHost = class {
|
|
|
76
703
|
let made = null;
|
|
77
704
|
try {
|
|
78
705
|
made = factory({
|
|
706
|
+
...args,
|
|
79
707
|
value,
|
|
80
708
|
row,
|
|
81
709
|
rowIdx,
|
|
82
710
|
colIdx,
|
|
83
711
|
column,
|
|
84
712
|
initialText,
|
|
85
|
-
commit: (v) => this._finish(s, v, null, "editor"),
|
|
713
|
+
commit: (v, move = null) => this._finish(s, v, MOVES.has(move) ? move : null, "editor"),
|
|
86
714
|
cancel: () => this._cancel(s, "editor")
|
|
87
715
|
});
|
|
88
716
|
} catch (err) {
|
|
@@ -94,6 +722,10 @@ var CellEditorHost = class {
|
|
|
94
722
|
s.editor = editor;
|
|
95
723
|
s.saved = [...td.childNodes];
|
|
96
724
|
s.savedTitle = td.getAttribute("title");
|
|
725
|
+
s.savedError = {
|
|
726
|
+
on: td.classList.contains("twm-dt-cell--error"),
|
|
727
|
+
message: td.getAttribute("data-error")
|
|
728
|
+
};
|
|
97
729
|
this.session = s;
|
|
98
730
|
td.classList.add("twm-dt-cell--editing");
|
|
99
731
|
td.replaceChildren(editor.el);
|
|
@@ -107,24 +739,25 @@ var CellEditorHost = class {
|
|
|
107
739
|
if (ev.key === "Escape") {
|
|
108
740
|
ev.preventDefault();
|
|
109
741
|
this._cancel(s, "key");
|
|
110
|
-
} else if (ev.key === "Enter" && !ev.altKey &&
|
|
742
|
+
} else if (ev.key === "Enter" && !ev.altKey && editor.read && (editor.multiline ? ev.ctrlKey || ev.metaKey : !(ev.ctrlKey || ev.metaKey))) {
|
|
111
743
|
ev.preventDefault();
|
|
112
|
-
this.
|
|
113
|
-
} else if (ev.key === "Tab" && !ev.altKey && !ev.ctrlKey && !ev.metaKey && editor.
|
|
744
|
+
this._tryKey(s, ev.shiftKey ? "up" : "down");
|
|
745
|
+
} else if (ev.key === "Tab" && !ev.altKey && !ev.ctrlKey && !ev.metaKey && editor.read) {
|
|
114
746
|
ev.preventDefault();
|
|
115
|
-
this.
|
|
747
|
+
this._tryKey(s, ev.shiftKey ? "left" : "right");
|
|
116
748
|
}
|
|
117
749
|
});
|
|
750
|
+
on("input", () => this._unmark(s));
|
|
118
751
|
for (const type of ["click", "dblclick", "mousedown", "contextmenu"]) {
|
|
119
752
|
on(type, (ev) => ev.stopPropagation());
|
|
120
753
|
}
|
|
121
754
|
on("focusout", (ev) => {
|
|
122
|
-
if (s.closed || !editor.
|
|
755
|
+
if (s.closed || !editor.read) return;
|
|
123
756
|
const to = ev.relatedTarget;
|
|
124
757
|
if (to && editor.el.contains(to)) return;
|
|
125
758
|
const doc = editor.el.ownerDocument;
|
|
126
759
|
if (!to && doc && typeof doc.hasFocus === "function" && !doc.hasFocus()) return;
|
|
127
|
-
this.
|
|
760
|
+
this._settle(s, null, "blur");
|
|
128
761
|
});
|
|
129
762
|
try {
|
|
130
763
|
if (editor.focus) editor.focus();
|
|
@@ -137,11 +770,51 @@ var CellEditorHost = class {
|
|
|
137
770
|
commit(move = null, reason = "editor") {
|
|
138
771
|
const s = this.session;
|
|
139
772
|
if (!s) return false;
|
|
140
|
-
if (!s.editor.
|
|
773
|
+
if (!s.editor.read) {
|
|
141
774
|
this._cancel(s, reason);
|
|
142
775
|
return false;
|
|
143
776
|
}
|
|
144
|
-
return this.
|
|
777
|
+
return this._settle(s, move, reason);
|
|
778
|
+
}
|
|
779
|
+
/** Enter or Tab: commit what parses; a refusal keeps the editor open,
|
|
780
|
+
* the message on its cell. */
|
|
781
|
+
_tryKey(s, move) {
|
|
782
|
+
const r = s.editor.read();
|
|
783
|
+
if (r.ok) return this._finish(s, r.value, move, "key");
|
|
784
|
+
s.td.classList.add("twm-dt-cell--error");
|
|
785
|
+
s.td.setAttribute("data-error", r.error);
|
|
786
|
+
s.td.setAttribute("title", r.error);
|
|
787
|
+
s.marked = true;
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
_unmark(s) {
|
|
791
|
+
if (!s.marked) return;
|
|
792
|
+
s.marked = false;
|
|
793
|
+
this._restoreMark(s);
|
|
794
|
+
}
|
|
795
|
+
_restoreMark(s) {
|
|
796
|
+
const { on, message } = s.savedError;
|
|
797
|
+
s.td.classList.toggle("twm-dt-cell--error", on);
|
|
798
|
+
if (message == null) s.td.removeAttribute("data-error");
|
|
799
|
+
else s.td.setAttribute("data-error", message);
|
|
800
|
+
if (s.savedTitle == null) s.td.removeAttribute("title");
|
|
801
|
+
else s.td.setAttribute("title", s.savedTitle);
|
|
802
|
+
}
|
|
803
|
+
/** The editor closing with what it holds (the focus left, `commit()`):
|
|
804
|
+
* committed when it parses, else handed to `onRefuse`. */
|
|
805
|
+
_settle(s, move, reason) {
|
|
806
|
+
const r = s.editor.read();
|
|
807
|
+
if (r.ok) return this._finish(s, r.value, move, reason);
|
|
808
|
+
if (s.closed) return false;
|
|
809
|
+
let text = "";
|
|
810
|
+
try {
|
|
811
|
+
text = s.editor.getValue ? s.editor.getValue() : "";
|
|
812
|
+
} catch (_) {
|
|
813
|
+
}
|
|
814
|
+
this._close(s);
|
|
815
|
+
if (this._onRefuse) this._onRefuse(s, r.error, text, reason);
|
|
816
|
+
else this._onCancel?.(s, reason);
|
|
817
|
+
return true;
|
|
145
818
|
}
|
|
146
819
|
/** Cancel the open session: the cell is drawn as it was. */
|
|
147
820
|
cancel(reason = "editor") {
|
|
@@ -167,8 +840,7 @@ var CellEditorHost = class {
|
|
|
167
840
|
}
|
|
168
841
|
s.td.classList.remove("twm-dt-cell--editing");
|
|
169
842
|
s.td.replaceChildren(...s.saved);
|
|
170
|
-
|
|
171
|
-
else s.td.setAttribute("title", s.savedTitle);
|
|
843
|
+
this._restoreMark(s);
|
|
172
844
|
}
|
|
173
845
|
_finish(s, value, move, reason) {
|
|
174
846
|
if (s.closed) return false;
|
|
@@ -184,6 +856,363 @@ var CellEditorHost = class {
|
|
|
184
856
|
}
|
|
185
857
|
};
|
|
186
858
|
|
|
859
|
+
// src/ui/components/cell_editors.js
|
|
860
|
+
var EDITOR_MESSAGES = Object.freeze({
|
|
861
|
+
number: "Not a number",
|
|
862
|
+
datetime: "Not a date",
|
|
863
|
+
bool: "Not true or false",
|
|
864
|
+
choice: "Not one of the options"
|
|
865
|
+
});
|
|
866
|
+
var ok = (value) => ({ ok: true, value });
|
|
867
|
+
var refuse = (ctx, type) => ({ ok: false, error: (ctx?.messages || {})[type] || EDITOR_MESSAGES[type] });
|
|
868
|
+
var isBlank = (text) => text == null || String(text).trim() === "";
|
|
869
|
+
var ALIASES = { num: "number", date: "datetime" };
|
|
870
|
+
function editorTypeOf(type) {
|
|
871
|
+
const t = type == null ? "text" : String(type);
|
|
872
|
+
return ALIASES[t] || t;
|
|
873
|
+
}
|
|
874
|
+
function safeLocale(locale) {
|
|
875
|
+
if (!locale) return void 0;
|
|
876
|
+
try {
|
|
877
|
+
return Intl.NumberFormat.supportedLocalesOf(locale).length ? locale : void 0;
|
|
878
|
+
} catch (_) {
|
|
879
|
+
return void 0;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function numberSeparators(locale) {
|
|
883
|
+
const parts = new Intl.NumberFormat(safeLocale(locale)).formatToParts(12345675e-1);
|
|
884
|
+
return {
|
|
885
|
+
group: parts.find((p) => p.type === "group")?.value ?? ",",
|
|
886
|
+
decimal: parts.find((p) => p.type === "decimal")?.value ?? "."
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
var SPACES = /[\s\u00a0\u202f]/g;
|
|
890
|
+
function parseNumber(text, ctx = {}) {
|
|
891
|
+
if (isBlank(text)) return ok(null);
|
|
892
|
+
const { group, decimal } = numberSeparators(ctx.locale);
|
|
893
|
+
let s = String(text).trim().replace(/\u2212/g, "-");
|
|
894
|
+
let sign = "";
|
|
895
|
+
if (s[0] === "-" || s[0] === "+") {
|
|
896
|
+
sign = s[0] === "-" ? "-" : "";
|
|
897
|
+
s = s.slice(1);
|
|
898
|
+
}
|
|
899
|
+
const halves = s.split(decimal);
|
|
900
|
+
if (halves.length > 2) return refuse(ctx, "number");
|
|
901
|
+
let [whole, frac = null] = halves;
|
|
902
|
+
let exp = "";
|
|
903
|
+
const tail = frac ?? whole;
|
|
904
|
+
const e = /[eE][+-]?\d+$/.exec(tail);
|
|
905
|
+
if (e) {
|
|
906
|
+
exp = e[0];
|
|
907
|
+
if (frac != null) frac = frac.slice(0, -exp.length);
|
|
908
|
+
else whole = whole.slice(0, -exp.length);
|
|
909
|
+
}
|
|
910
|
+
const spaceGroup = group !== "" && group.replace(SPACES, "") === "";
|
|
911
|
+
const g = spaceGroup ? " " : group;
|
|
912
|
+
if (spaceGroup) whole = whole.replace(SPACES, " ");
|
|
913
|
+
if (g && whole.includes(g)) {
|
|
914
|
+
const esc = g.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
915
|
+
if (!new RegExp(`^\\d{1,3}(${esc}\\d{3})+$`).test(whole)) return refuse(ctx, "number");
|
|
916
|
+
whole = whole.split(g).join("");
|
|
917
|
+
}
|
|
918
|
+
if (!/^\d*$/.test(whole) || frac != null && !/^\d*$/.test(frac)) return refuse(ctx, "number");
|
|
919
|
+
if (!whole && !frac) return refuse(ctx, "number");
|
|
920
|
+
const n = Number(`${sign}${whole || "0"}.${frac || "0"}${exp}`);
|
|
921
|
+
return Number.isFinite(n) ? ok(n) : refuse(ctx, "number");
|
|
922
|
+
}
|
|
923
|
+
function formatNumberForEdit(value, ctx = {}) {
|
|
924
|
+
if (value == null) return "";
|
|
925
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return String(value);
|
|
926
|
+
const { decimal } = numberSeparators(ctx.locale);
|
|
927
|
+
return String(value).replace(".", decimal);
|
|
928
|
+
}
|
|
929
|
+
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "on", "x", "\u2713", "\u2714"]);
|
|
930
|
+
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "off", ""]);
|
|
931
|
+
function parseBool(text, ctx = {}) {
|
|
932
|
+
if (typeof text === "boolean") return ok(text);
|
|
933
|
+
const t = String(text ?? "").trim().toLowerCase();
|
|
934
|
+
if (TRUE_WORDS.has(t)) return ok(true);
|
|
935
|
+
if (FALSE_WORDS.has(t)) return ok(false);
|
|
936
|
+
return refuse(ctx, "bool");
|
|
937
|
+
}
|
|
938
|
+
function truthy(value) {
|
|
939
|
+
if (typeof value === "boolean") return value;
|
|
940
|
+
if (value == null) return false;
|
|
941
|
+
const r = parseBool(String(value));
|
|
942
|
+
return r.ok ? r.value : true;
|
|
943
|
+
}
|
|
944
|
+
function dateOrder(locale) {
|
|
945
|
+
let loc;
|
|
946
|
+
try {
|
|
947
|
+
loc = Intl.DateTimeFormat.supportedLocalesOf(locale || []).length ? locale : void 0;
|
|
948
|
+
} catch (_) {
|
|
949
|
+
loc = void 0;
|
|
950
|
+
}
|
|
951
|
+
return new Intl.DateTimeFormat(loc, { year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(new Date(2026, 11, 31)).map((p) => p.type).filter((t) => t === "day" || t === "month" || t === "year");
|
|
952
|
+
}
|
|
953
|
+
function daysIn(y, m) {
|
|
954
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
955
|
+
}
|
|
956
|
+
function makeDate({ y, mo, d, h = 0, mi = 0, s = 0, ms = 0 }, utc, offsetMin = null) {
|
|
957
|
+
if (!(mo >= 1 && mo <= 12) || !(d >= 1 && d <= daysIn(y, mo))) return null;
|
|
958
|
+
if (h > 23 || mi > 59 || s > 59) return null;
|
|
959
|
+
if (offsetMin != null) return new Date(Date.UTC(y, mo - 1, d, h, mi, s, ms) - offsetMin * 6e4);
|
|
960
|
+
if (utc) return new Date(Date.UTC(y, mo - 1, d, h, mi, s, ms));
|
|
961
|
+
const date = new Date(y, mo - 1, d, h, mi, s, ms);
|
|
962
|
+
date.setFullYear(y);
|
|
963
|
+
return date;
|
|
964
|
+
}
|
|
965
|
+
var ISO = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,3})\d*)?)?\s*(Z|[+-]\d{2}:?\d{2})?)?$/i;
|
|
966
|
+
var LOCAL = /^(\d{1,4})[./-](\d{1,2})[./-](\d{1,4})\.?(?:[,\sT]+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*([ap])?\.?m?\.?)?$/i;
|
|
967
|
+
function parseDateTime(text, ctx = {}) {
|
|
968
|
+
if (isBlank(text)) return ok(null);
|
|
969
|
+
const t = String(text).trim();
|
|
970
|
+
const iso = ISO.exec(t);
|
|
971
|
+
if (iso) {
|
|
972
|
+
const [, y, mo, d, h2, mi2, s, ms, zone] = iso;
|
|
973
|
+
let offset = null;
|
|
974
|
+
if (zone) {
|
|
975
|
+
if (zone.toUpperCase() === "Z") offset = 0;
|
|
976
|
+
else {
|
|
977
|
+
const z = zone.replace(":", "");
|
|
978
|
+
offset = (z[0] === "-" ? -1 : 1) * (Number(z.slice(1, 3)) * 60 + Number(z.slice(3, 5)));
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const date2 = makeDate({
|
|
982
|
+
y: +y,
|
|
983
|
+
mo: +mo,
|
|
984
|
+
d: +d,
|
|
985
|
+
h: +(h2 || 0),
|
|
986
|
+
mi: +(mi2 || 0),
|
|
987
|
+
s: +(s || 0),
|
|
988
|
+
ms: ms ? Number(ms.padEnd(3, "0")) : 0
|
|
989
|
+
}, !!ctx.utc, offset);
|
|
990
|
+
return date2 ? ok(date2) : refuse(ctx, "datetime");
|
|
991
|
+
}
|
|
992
|
+
const m = LOCAL.exec(t);
|
|
993
|
+
if (!m) return refuse(ctx, "datetime");
|
|
994
|
+
const [, a, b, c, hh, mi, ss, ap] = m;
|
|
995
|
+
let h = hh == null ? 0 : +hh;
|
|
996
|
+
if (ap) {
|
|
997
|
+
if (h < 1 || h > 12) return refuse(ctx, "datetime");
|
|
998
|
+
h = h % 12 + (ap.toLowerCase() === "p" ? 12 : 0);
|
|
999
|
+
}
|
|
1000
|
+
const time = { h, mi: +(mi || 0), s: +(ss || 0) };
|
|
1001
|
+
const order = dateOrder(ctx.locale);
|
|
1002
|
+
const nums = [a, b, c];
|
|
1003
|
+
const read = (ord) => {
|
|
1004
|
+
const at = {};
|
|
1005
|
+
ord.forEach((type, i) => {
|
|
1006
|
+
at[type] = nums[i];
|
|
1007
|
+
});
|
|
1008
|
+
if (!at.year || at.year.length !== 4) return null;
|
|
1009
|
+
return makeDate({ y: +at.year, mo: +at.month, d: +at.day, ...time }, !!ctx.utc);
|
|
1010
|
+
};
|
|
1011
|
+
let date = read(order);
|
|
1012
|
+
if (!date) {
|
|
1013
|
+
const swapped = order.map((p) => p === "day" ? "month" : p === "month" ? "day" : p);
|
|
1014
|
+
date = read(swapped);
|
|
1015
|
+
}
|
|
1016
|
+
return date ? ok(date) : refuse(ctx, "datetime");
|
|
1017
|
+
}
|
|
1018
|
+
var pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
1019
|
+
function formatDateTimeForEdit(value, ctx = {}) {
|
|
1020
|
+
if (value == null || value === "") return "";
|
|
1021
|
+
if (!(value instanceof Date)) return String(value);
|
|
1022
|
+
if (Number.isNaN(value.getTime())) return "";
|
|
1023
|
+
const u = !!ctx.utc;
|
|
1024
|
+
const y = u ? value.getUTCFullYear() : value.getFullYear();
|
|
1025
|
+
const mo = (u ? value.getUTCMonth() : value.getMonth()) + 1;
|
|
1026
|
+
const d = u ? value.getUTCDate() : value.getDate();
|
|
1027
|
+
const h = u ? value.getUTCHours() : value.getHours();
|
|
1028
|
+
const mi = u ? value.getUTCMinutes() : value.getMinutes();
|
|
1029
|
+
const s = u ? value.getUTCSeconds() : value.getSeconds();
|
|
1030
|
+
const day = `${pad(y, 4)}-${pad(mo)}-${pad(d)}`;
|
|
1031
|
+
if (!h && !mi && !s) return day;
|
|
1032
|
+
return `${day} ${pad(h)}:${pad(mi)}${s ? `:${pad(s)}` : ""}`;
|
|
1033
|
+
}
|
|
1034
|
+
function normalizeOptions(options) {
|
|
1035
|
+
if (!Array.isArray(options)) return [];
|
|
1036
|
+
return options.map((o) => o && typeof o === "object" ? { value: o.value, label: String(o.label ?? o.value ?? "") } : { value: o, label: String(o ?? "") });
|
|
1037
|
+
}
|
|
1038
|
+
function parseChoice(text, ctx = {}) {
|
|
1039
|
+
if (isBlank(text)) return ok(null);
|
|
1040
|
+
const t = String(text).trim();
|
|
1041
|
+
const lower = t.toLowerCase();
|
|
1042
|
+
const opts = normalizeOptions(ctx.options);
|
|
1043
|
+
const hit = opts.find((o) => o.label.trim().toLowerCase() === lower) || opts.find((o) => String(o.value ?? "").trim().toLowerCase() === lower);
|
|
1044
|
+
if (hit) return ok(hit.value);
|
|
1045
|
+
return ctx.allowOther ? ok(t) : refuse(ctx, "choice");
|
|
1046
|
+
}
|
|
1047
|
+
function labelOf(value, ctx) {
|
|
1048
|
+
if (value == null) return "";
|
|
1049
|
+
const hit = normalizeOptions(ctx.options).find((o) => Object.is(o.value, value) || String(o.value) === String(value));
|
|
1050
|
+
return hit ? hit.label : String(value);
|
|
1051
|
+
}
|
|
1052
|
+
function contextOf(args) {
|
|
1053
|
+
const col = args.column || {};
|
|
1054
|
+
return {
|
|
1055
|
+
locale: args.locale,
|
|
1056
|
+
utc: !!args.utc,
|
|
1057
|
+
messages: args.messages,
|
|
1058
|
+
options: col.options,
|
|
1059
|
+
allowOther: !!col.allowOther
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function parsedField(el, args, start, parse) {
|
|
1063
|
+
const { value, initialText = null } = args;
|
|
1064
|
+
el.value = initialText != null ? String(initialText) : start;
|
|
1065
|
+
const ctx = contextOf(args);
|
|
1066
|
+
return {
|
|
1067
|
+
el,
|
|
1068
|
+
getValue: () => el.value,
|
|
1069
|
+
parse: () => initialText == null && el.value === start ? ok(value) : parse(el.value, ctx),
|
|
1070
|
+
focus: () => {
|
|
1071
|
+
el.focus({ preventScroll: true });
|
|
1072
|
+
const end = el.value.length;
|
|
1073
|
+
try {
|
|
1074
|
+
el.setSelectionRange(end, end);
|
|
1075
|
+
} catch (_) {
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
function textInput(kind) {
|
|
1081
|
+
const input = document.createElement("input");
|
|
1082
|
+
input.type = "text";
|
|
1083
|
+
input.className = `twm-dt-editor twm-dt-editor--${kind}`;
|
|
1084
|
+
input.spellcheck = false;
|
|
1085
|
+
input.setAttribute("autocomplete", "off");
|
|
1086
|
+
return input;
|
|
1087
|
+
}
|
|
1088
|
+
function createNumberEditor(args) {
|
|
1089
|
+
const input = textInput("number");
|
|
1090
|
+
input.inputMode = "decimal";
|
|
1091
|
+
return parsedField(input, args, formatNumberForEdit(args.value, contextOf(args)), parseNumber);
|
|
1092
|
+
}
|
|
1093
|
+
function createDateTimeEditor(args) {
|
|
1094
|
+
const input = textInput("datetime");
|
|
1095
|
+
return parsedField(input, args, formatDateTimeForEdit(args.value, contextOf(args)), parseDateTime);
|
|
1096
|
+
}
|
|
1097
|
+
function createMultiTextEditor(args) {
|
|
1098
|
+
const area = document.createElement("textarea");
|
|
1099
|
+
area.className = "twm-dt-editor twm-dt-editor--multitext";
|
|
1100
|
+
area.spellcheck = false;
|
|
1101
|
+
area.rows = Number(args.column?.rows) > 0 ? Number(args.column.rows) : 3;
|
|
1102
|
+
const start = args.value == null ? "" : String(args.value);
|
|
1103
|
+
const field = parsedField(area, args, start, (text) => ok(text));
|
|
1104
|
+
return { ...field, multiline: true };
|
|
1105
|
+
}
|
|
1106
|
+
function createBoolEditor(args) {
|
|
1107
|
+
const { value, initialText = null, commit } = args;
|
|
1108
|
+
const box = document.createElement("input");
|
|
1109
|
+
box.type = "checkbox";
|
|
1110
|
+
box.className = "twm-dt-editor twm-dt-editor--bool";
|
|
1111
|
+
const start = truthy(value);
|
|
1112
|
+
box.checked = start;
|
|
1113
|
+
if (initialText === " ") box.checked = !start;
|
|
1114
|
+
else if (initialText != null) {
|
|
1115
|
+
const r = parseBool(initialText);
|
|
1116
|
+
if (r.ok) box.checked = r.value;
|
|
1117
|
+
}
|
|
1118
|
+
box.addEventListener("change", () => commit(box.checked));
|
|
1119
|
+
const read = () => box.checked === start && initialText == null ? ok(value) : ok(box.checked);
|
|
1120
|
+
return {
|
|
1121
|
+
el: box,
|
|
1122
|
+
getValue: () => box.checked,
|
|
1123
|
+
parse: read,
|
|
1124
|
+
focus: () => box.focus({ preventScroll: true })
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
var uid = 0;
|
|
1128
|
+
function createChoiceEditor(args) {
|
|
1129
|
+
const { value, initialText = null, commit } = args;
|
|
1130
|
+
const ctx = contextOf(args);
|
|
1131
|
+
const opts = normalizeOptions(ctx.options);
|
|
1132
|
+
const start = labelOf(value, ctx);
|
|
1133
|
+
const shown = initialText != null ? String(initialText) : start;
|
|
1134
|
+
let picked = null;
|
|
1135
|
+
let keying = null;
|
|
1136
|
+
let done = false;
|
|
1137
|
+
const ac = new AutocompleteField({
|
|
1138
|
+
fieldId: `twm-dt-choice-${++uid}`,
|
|
1139
|
+
field: { value: shown },
|
|
1140
|
+
skipNamespacePrefix: true,
|
|
1141
|
+
typeIcons: { option: "list" },
|
|
1142
|
+
provider: ({ value: term }) => {
|
|
1143
|
+
const q = String(term ?? "").trim().toLowerCase();
|
|
1144
|
+
const all = !q || initialText == null && term === start;
|
|
1145
|
+
return opts.filter((o) => all || o.label.toLowerCase().includes(q)).map((o) => ({ label: o.label, insertText: o.label, iconType: "option", metadata: { value: o.value } }));
|
|
1146
|
+
},
|
|
1147
|
+
onChange: (text, meta) => {
|
|
1148
|
+
if (done || !meta?.immediate || !meta.metadata) return;
|
|
1149
|
+
picked = { label: String(text ?? ""), value: meta.metadata.value };
|
|
1150
|
+
const move = keying ? keying.key === "Tab" ? keying.shift ? "left" : "right" : keying.shift ? "up" : "down" : null;
|
|
1151
|
+
commit(picked.value, move);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
const root = ac.render();
|
|
1155
|
+
root.classList.add("twm-dt-editor--choice");
|
|
1156
|
+
const input = ac.input;
|
|
1157
|
+
input.classList.add("twm-dt-editor");
|
|
1158
|
+
let browsed = false;
|
|
1159
|
+
const onCapture = (ev) => {
|
|
1160
|
+
const commitKey = (ev.key === "Enter" || ev.key === "Tab") && !ev.ctrlKey && !ev.metaKey && !ev.altKey;
|
|
1161
|
+
keying = commitKey ? { key: ev.key, shift: ev.shiftKey } : null;
|
|
1162
|
+
if (ev.key === "ArrowDown" || ev.key === "ArrowUp") browsed = true;
|
|
1163
|
+
if (commitKey && !browsed && !String(input.value).trim() && !ev.isComposing) {
|
|
1164
|
+
ev.preventDefault();
|
|
1165
|
+
ev.stopPropagation();
|
|
1166
|
+
const move = ev.key === "Tab" ? ev.shiftKey ? "left" : "right" : ev.shiftKey ? "up" : "down";
|
|
1167
|
+
commit(null, move);
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
const onInput = () => {
|
|
1171
|
+
browsed = false;
|
|
1172
|
+
};
|
|
1173
|
+
const onAfter = (ev) => {
|
|
1174
|
+
keying = null;
|
|
1175
|
+
if (ev.key === "Escape" && ev.defaultPrevented) ev.stopPropagation();
|
|
1176
|
+
};
|
|
1177
|
+
root.addEventListener("keydown", onCapture, true);
|
|
1178
|
+
input.addEventListener("keydown", onAfter);
|
|
1179
|
+
input.addEventListener("input", onInput);
|
|
1180
|
+
return {
|
|
1181
|
+
el: root,
|
|
1182
|
+
getValue: () => ac.input?.value ?? "",
|
|
1183
|
+
parse: () => {
|
|
1184
|
+
const text = ac.input?.value ?? "";
|
|
1185
|
+
if (initialText == null && text === start) return ok(value);
|
|
1186
|
+
if (picked && text === picked.label) return ok(picked.value);
|
|
1187
|
+
return parseChoice(text, ctx);
|
|
1188
|
+
},
|
|
1189
|
+
focus: () => {
|
|
1190
|
+
ac.input?.focus({ preventScroll: true });
|
|
1191
|
+
const end = ac.input?.value.length ?? 0;
|
|
1192
|
+
try {
|
|
1193
|
+
ac.input?.setSelectionRange(end, end);
|
|
1194
|
+
} catch (_) {
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
destroy: () => {
|
|
1198
|
+
done = true;
|
|
1199
|
+
root.removeEventListener("keydown", onCapture, true);
|
|
1200
|
+
input.removeEventListener("keydown", onAfter);
|
|
1201
|
+
input.removeEventListener("input", onInput);
|
|
1202
|
+
ac.dispose();
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
var identity = (text) => ok(text == null ? "" : String(text));
|
|
1207
|
+
var CELL_EDITORS = Object.freeze({
|
|
1208
|
+
text: Object.freeze({ create: createTextEditor, parse: identity }),
|
|
1209
|
+
multitext: Object.freeze({ create: createMultiTextEditor, parse: identity }),
|
|
1210
|
+
number: Object.freeze({ create: createNumberEditor, parse: parseNumber }),
|
|
1211
|
+
bool: Object.freeze({ create: createBoolEditor, parse: parseBool }),
|
|
1212
|
+
choice: Object.freeze({ create: createChoiceEditor, parse: parseChoice }),
|
|
1213
|
+
datetime: Object.freeze({ create: createDateTimeEditor, parse: parseDateTime })
|
|
1214
|
+
});
|
|
1215
|
+
|
|
187
1216
|
// src/ui/components/cell_selection.js
|
|
188
1217
|
var CellSelection = class {
|
|
189
1218
|
constructor() {
|
|
@@ -406,7 +1435,7 @@ var WEEKDAYS = [
|
|
|
406
1435
|
var ISO_DATE = "YYYY-MM-DD";
|
|
407
1436
|
var ISO_DATETIME = "YYYY-MM-DD HH:mm";
|
|
408
1437
|
var ISO_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?)?\s*(Z|[+-]\d{2}(?::?\d{2})?)?$/i;
|
|
409
|
-
var
|
|
1438
|
+
var pad2 = (n, w = 2) => String(Math.trunc(Math.abs(n))).padStart(w, "0");
|
|
410
1439
|
function daysInMonth(y, mo) {
|
|
411
1440
|
if (mo !== 1) return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][mo];
|
|
412
1441
|
return y % 4 === 0 && y % 100 !== 0 || y % 400 === 0 ? 29 : 28;
|
|
@@ -483,15 +1512,15 @@ function formatDate(value, pattern = ISO_DATE, { utc = false } = {}) {
|
|
|
483
1512
|
if (literal !== void 0) return literal;
|
|
484
1513
|
switch (tok) {
|
|
485
1514
|
case "YYYY":
|
|
486
|
-
return
|
|
1515
|
+
return pad2(get.y, 4);
|
|
487
1516
|
case "YY":
|
|
488
|
-
return
|
|
1517
|
+
return pad2(get.y % 100);
|
|
489
1518
|
case "MMMM":
|
|
490
1519
|
return MONTHS[get.mo];
|
|
491
1520
|
case "MMM":
|
|
492
1521
|
return MONTHS[get.mo].slice(0, 3);
|
|
493
1522
|
case "MM":
|
|
494
|
-
return
|
|
1523
|
+
return pad2(get.mo + 1);
|
|
495
1524
|
case "M":
|
|
496
1525
|
return String(get.mo + 1);
|
|
497
1526
|
case "dddd":
|
|
@@ -499,27 +1528,27 @@ function formatDate(value, pattern = ISO_DATE, { utc = false } = {}) {
|
|
|
499
1528
|
case "ddd":
|
|
500
1529
|
return WEEKDAYS[get.wd].slice(0, 3);
|
|
501
1530
|
case "DD":
|
|
502
|
-
return
|
|
1531
|
+
return pad2(get.d);
|
|
503
1532
|
case "D":
|
|
504
1533
|
return String(get.d);
|
|
505
1534
|
case "HH":
|
|
506
|
-
return
|
|
1535
|
+
return pad2(get.h);
|
|
507
1536
|
case "H":
|
|
508
1537
|
return String(get.h);
|
|
509
1538
|
case "hh":
|
|
510
|
-
return
|
|
1539
|
+
return pad2(h12);
|
|
511
1540
|
case "h":
|
|
512
1541
|
return String(h12);
|
|
513
1542
|
case "mm":
|
|
514
|
-
return
|
|
1543
|
+
return pad2(get.mi);
|
|
515
1544
|
case "m":
|
|
516
1545
|
return String(get.mi);
|
|
517
1546
|
case "ss":
|
|
518
|
-
return
|
|
1547
|
+
return pad2(get.s);
|
|
519
1548
|
case "s":
|
|
520
1549
|
return String(get.s);
|
|
521
1550
|
case "SSS":
|
|
522
|
-
return
|
|
1551
|
+
return pad2(get.ms, 3);
|
|
523
1552
|
case "A":
|
|
524
1553
|
return get.h < 12 ? "AM" : "PM";
|
|
525
1554
|
case "a":
|
|
@@ -620,7 +1649,7 @@ function formatDuration(value, pattern = "auto") {
|
|
|
620
1649
|
const h = Math.floor(total % 86400 / 3600);
|
|
621
1650
|
const mi = Math.floor(total % 3600 / 60);
|
|
622
1651
|
const s = total % 60;
|
|
623
|
-
out = `${d ? `${d}.${
|
|
1652
|
+
out = `${d ? `${d}.${pad2(h)}` : h}:${pad2(mi)}:${pad2(s)}`;
|
|
624
1653
|
} else if (a < 1e3) {
|
|
625
1654
|
out = `${Math.round(a)} ms`;
|
|
626
1655
|
} else if (a < 59950) {
|
|
@@ -744,12 +1773,12 @@ function createChipsFilter(ctx = {}, spec = {}) {
|
|
|
744
1773
|
};
|
|
745
1774
|
const chosen = () => multiple ? value.length === 0 : value === null;
|
|
746
1775
|
for (const opt of options) {
|
|
747
|
-
const
|
|
748
|
-
valueOf.set(
|
|
1776
|
+
const key4 = keyOf(opt.value);
|
|
1777
|
+
valueOf.set(key4, opt.value);
|
|
749
1778
|
const btn = document.createElement("button");
|
|
750
1779
|
btn.type = "button";
|
|
751
1780
|
btn.className = "twm-dt-filter-chip";
|
|
752
|
-
btn.dataset.value =
|
|
1781
|
+
btn.dataset.value = key4;
|
|
753
1782
|
if (opt.title) btn.title = String(opt.title);
|
|
754
1783
|
if (opt.icon) {
|
|
755
1784
|
const icon = document.createElement("span");
|
|
@@ -760,7 +1789,7 @@ function createChipsFilter(ctx = {}, spec = {}) {
|
|
|
760
1789
|
}
|
|
761
1790
|
const label = document.createElement("span");
|
|
762
1791
|
label.className = "twm-dt-filter-chip__label";
|
|
763
|
-
label.textContent = String(opt.label ??
|
|
1792
|
+
label.textContent = String(opt.label ?? key4);
|
|
764
1793
|
btn.appendChild(label);
|
|
765
1794
|
let count = null;
|
|
766
1795
|
if (showCounts) {
|
|
@@ -771,21 +1800,21 @@ function createChipsFilter(ctx = {}, spec = {}) {
|
|
|
771
1800
|
}
|
|
772
1801
|
btn.addEventListener("click", (ev) => {
|
|
773
1802
|
ev.stopPropagation();
|
|
774
|
-
pick(
|
|
1803
|
+
pick(key4);
|
|
775
1804
|
});
|
|
776
|
-
chips.set(
|
|
1805
|
+
chips.set(key4, { btn, count });
|
|
777
1806
|
el.appendChild(btn);
|
|
778
1807
|
}
|
|
779
1808
|
function outward() {
|
|
780
1809
|
if (multiple) return value.length ? value.map((k) => valueOf.get(k)) : null;
|
|
781
1810
|
return value === null ? null : valueOf.get(value);
|
|
782
1811
|
}
|
|
783
|
-
function pick(
|
|
784
|
-
if (isAll(
|
|
1812
|
+
function pick(key4) {
|
|
1813
|
+
if (isAll(key4)) value = multiple ? [] : null;
|
|
785
1814
|
else if (multiple) {
|
|
786
|
-
value = value.includes(
|
|
1815
|
+
value = value.includes(key4) ? value.filter((k) => k !== key4) : [...value, key4];
|
|
787
1816
|
} else {
|
|
788
|
-
value = value ===
|
|
1817
|
+
value = value === key4 ? null : key4;
|
|
789
1818
|
}
|
|
790
1819
|
paint();
|
|
791
1820
|
const out = outward();
|
|
@@ -797,8 +1826,8 @@ function createChipsFilter(ctx = {}, spec = {}) {
|
|
|
797
1826
|
}
|
|
798
1827
|
}
|
|
799
1828
|
function paint() {
|
|
800
|
-
for (const [
|
|
801
|
-
const on = isAll(
|
|
1829
|
+
for (const [key4, { btn }] of chips) {
|
|
1830
|
+
const on = isAll(key4) ? chosen() : multiple ? value.includes(key4) : value === key4;
|
|
802
1831
|
btn.classList.toggle("twm-dt-filter-chip--on", on);
|
|
803
1832
|
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
804
1833
|
}
|
|
@@ -808,8 +1837,8 @@ function createChipsFilter(ctx = {}, spec = {}) {
|
|
|
808
1837
|
paint();
|
|
809
1838
|
}
|
|
810
1839
|
function setCounts(counts) {
|
|
811
|
-
for (const [
|
|
812
|
-
const n = counts == null ? void 0 : counts[
|
|
1840
|
+
for (const [key4, { btn, count }] of chips) {
|
|
1841
|
+
const n = counts == null ? void 0 : counts[key4];
|
|
813
1842
|
const known = typeof n === "number" && Number.isFinite(n);
|
|
814
1843
|
if (count) count.textContent = known ? n.toLocaleString() : "\u2013";
|
|
815
1844
|
btn.classList.toggle("twm-dt-filter-chip--empty", known && n === 0);
|
|
@@ -966,10 +1995,10 @@ var ISO_DATE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
966
1995
|
var TRAILING_NUMBER = /^(.*?)(\d+)(\D*)$/;
|
|
967
1996
|
var NUMERIC_TEXT = /^[-+]?(\d+\.?\d*|\.\d+)$/;
|
|
968
1997
|
var DAY_MS = 864e5;
|
|
969
|
-
var
|
|
1998
|
+
var isBlank2 = (v) => v === null || v === void 0 || v === "";
|
|
970
1999
|
function detectSeries(values) {
|
|
971
2000
|
if (!Array.isArray(values) || values.length === 0) return null;
|
|
972
|
-
if (values.every(
|
|
2001
|
+
if (values.every(isBlank2)) return null;
|
|
973
2002
|
if (values.length === 1) return { kind: "copy", step: 0 };
|
|
974
2003
|
const numbers = values.map(toNumber);
|
|
975
2004
|
if (numbers.every((n) => n !== null)) {
|
|
@@ -1071,6 +2100,53 @@ function nearlyEqual(a, b) {
|
|
|
1071
2100
|
return Math.abs(a - b) < 1e-9;
|
|
1072
2101
|
}
|
|
1073
2102
|
|
|
2103
|
+
// src/ui/components/tsv.js
|
|
2104
|
+
function tsvCell(value) {
|
|
2105
|
+
const text = value === null || value === void 0 ? "" : String(value);
|
|
2106
|
+
if (/[\t\n\r"]/.test(text)) return `"${text.replace(/"/g, '""')}"`;
|
|
2107
|
+
return text;
|
|
2108
|
+
}
|
|
2109
|
+
function toTSV(matrix) {
|
|
2110
|
+
if (!Array.isArray(matrix)) return "";
|
|
2111
|
+
return matrix.map((row) => (Array.isArray(row) ? row : [row]).map(tsvCell).join(" ")).join("\n");
|
|
2112
|
+
}
|
|
2113
|
+
function fromTSV(text) {
|
|
2114
|
+
if (text === null || text === void 0 || text === "") return [];
|
|
2115
|
+
const src = String(text);
|
|
2116
|
+
const rows = [];
|
|
2117
|
+
let row = [];
|
|
2118
|
+
let cell = "";
|
|
2119
|
+
let quoted = false;
|
|
2120
|
+
for (let i = 0; i < src.length; i++) {
|
|
2121
|
+
const ch = src[i];
|
|
2122
|
+
if (quoted) {
|
|
2123
|
+
if (ch === '"' && src[i + 1] === '"') {
|
|
2124
|
+
cell += '"';
|
|
2125
|
+
i++;
|
|
2126
|
+
} else if (ch === '"') quoted = false;
|
|
2127
|
+
else cell += ch;
|
|
2128
|
+
} else if (ch === '"' && cell === "") quoted = true;
|
|
2129
|
+
else if (ch === " ") {
|
|
2130
|
+
row.push(cell);
|
|
2131
|
+
cell = "";
|
|
2132
|
+
} else if (ch === "\r") {
|
|
2133
|
+
} else if (ch === "\n") {
|
|
2134
|
+
row.push(cell);
|
|
2135
|
+
rows.push(row);
|
|
2136
|
+
row = [];
|
|
2137
|
+
cell = "";
|
|
2138
|
+
} else cell += ch;
|
|
2139
|
+
}
|
|
2140
|
+
row.push(cell);
|
|
2141
|
+
rows.push(row);
|
|
2142
|
+
while (rows.length > 1) {
|
|
2143
|
+
const last = rows[rows.length - 1];
|
|
2144
|
+
if (last.length === 1 && last[0] === "") rows.pop();
|
|
2145
|
+
else break;
|
|
2146
|
+
}
|
|
2147
|
+
return rows;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
1074
2150
|
// src/ui/components/inline_renamer.js
|
|
1075
2151
|
function assertLabel(label) {
|
|
1076
2152
|
if (!label || typeof label.appendChild !== "function") {
|
|
@@ -1240,7 +2316,12 @@ var StagedEdits = class {
|
|
|
1240
2316
|
* Returns whether anything changed.
|
|
1241
2317
|
*/
|
|
1242
2318
|
stage(rowIdx, colIdx, value) {
|
|
1243
|
-
if (this.committing
|
|
2319
|
+
if (this.committing) return false;
|
|
2320
|
+
const refusal = this._dropRefusal(rowIdx, colIdx);
|
|
2321
|
+
if (!this._stageOne(rowIdx, colIdx, value)) {
|
|
2322
|
+
if (refusal) this.grid._redraw(rowIdx, colIdx);
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
1244
2325
|
this.grid._redraw(rowIdx, colIdx);
|
|
1245
2326
|
this._changed();
|
|
1246
2327
|
return true;
|
|
@@ -1257,10 +2338,11 @@ var StagedEdits = class {
|
|
|
1257
2338
|
this.history.beginGroup(label);
|
|
1258
2339
|
try {
|
|
1259
2340
|
for (const { rowIdx, colIdx, value } of cells) {
|
|
2341
|
+
const refusal = this._dropRefusal(rowIdx, colIdx);
|
|
1260
2342
|
if (this._stageOne(rowIdx, colIdx, value)) {
|
|
1261
2343
|
changed++;
|
|
1262
2344
|
rows.add(rowIdx);
|
|
1263
|
-
}
|
|
2345
|
+
} else if (refusal) rows.add(rowIdx);
|
|
1264
2346
|
}
|
|
1265
2347
|
} finally {
|
|
1266
2348
|
this.history.endGroup();
|
|
@@ -1269,6 +2351,16 @@ var StagedEdits = class {
|
|
|
1269
2351
|
if (changed) this._changed();
|
|
1270
2352
|
return changed;
|
|
1271
2353
|
}
|
|
2354
|
+
/** STORY-0087. A text that did not parse stays in its cell, marked, in
|
|
2355
|
+
* the grid's own marks; anything staged into the cell ends that refusal,
|
|
2356
|
+
* the value its row holds included. Whether there was one. */
|
|
2357
|
+
_dropRefusal(rowIdx, colIdx) {
|
|
2358
|
+
const k = key(rowIdx, colIdx);
|
|
2359
|
+
if (!this.grid.errors.has(k)) return false;
|
|
2360
|
+
this.grid.errors.delete(k);
|
|
2361
|
+
this.grid.latest.delete(k);
|
|
2362
|
+
return true;
|
|
2363
|
+
}
|
|
1272
2364
|
/** One cell's new state and its delta in the ring; whether it changed. */
|
|
1273
2365
|
_stageOne(rowIdx, colIdx, value) {
|
|
1274
2366
|
const k = key(rowIdx, colIdx);
|
|
@@ -1762,6 +2854,210 @@ var FillHandle = class {
|
|
|
1762
2854
|
}
|
|
1763
2855
|
};
|
|
1764
2856
|
|
|
2857
|
+
// src/ui/components/cell_paste.js
|
|
2858
|
+
var key3 = (rowIdx, colIdx) => `${rowIdx}:${colIdx}`;
|
|
2859
|
+
var CellPaste = class {
|
|
2860
|
+
/** @param {import('./cell_grid.js').CellGrid} grid */
|
|
2861
|
+
constructor(grid) {
|
|
2862
|
+
this.grid = grid;
|
|
2863
|
+
this.skipped = /* @__PURE__ */ new Set();
|
|
2864
|
+
this.busy = false;
|
|
2865
|
+
}
|
|
2866
|
+
// ── painting (called by the grid for every cell it paints) ───────────
|
|
2867
|
+
paintCell(td, col) {
|
|
2868
|
+
const rowIdx = td.parentNode?.__rowIndex;
|
|
2869
|
+
td.classList.toggle("twm-dt-cell--paste-skipped", this.skipped.has(key3(rowIdx, col)));
|
|
2870
|
+
}
|
|
2871
|
+
/** The selection moved, or new rows came: the skipped marks are over. */
|
|
2872
|
+
clearMarks() {
|
|
2873
|
+
this.skipped.clear();
|
|
2874
|
+
}
|
|
2875
|
+
// ── reading a value ──────────────────────────────────────────────────
|
|
2876
|
+
/**
|
|
2877
|
+
* The parse a column's pasted text is read with: an injected editor's
|
|
2878
|
+
* own (`editors[k].parse`, looked up as the editor is), else the built-in
|
|
2879
|
+
* editor's for the column's type, else the text as it is.
|
|
2880
|
+
*/
|
|
2881
|
+
_parserFor(col) {
|
|
2882
|
+
const g = this.grid;
|
|
2883
|
+
const editors = g._cfg().editors;
|
|
2884
|
+
if (editors && typeof editors === "object") {
|
|
2885
|
+
for (const k of [col, g._rawType(col), g._typeOf(col)]) {
|
|
2886
|
+
const f = editors[k];
|
|
2887
|
+
if (typeof f === "function") {
|
|
2888
|
+
if (typeof f.parse === "function") return f.parse;
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
const type = g._typeOf(col);
|
|
2894
|
+
return Object.prototype.hasOwnProperty.call(CELL_EDITORS, type) ? CELL_EDITORS[type].parse : CELL_EDITORS.text.parse;
|
|
2895
|
+
}
|
|
2896
|
+
/** One column's reader: `(text) => {ok: true, value} | {ok: false, error}`. */
|
|
2897
|
+
_reader(col) {
|
|
2898
|
+
const g = this.grid;
|
|
2899
|
+
const cfg = g._cfg();
|
|
2900
|
+
const spec = g._editSpec(col) || {};
|
|
2901
|
+
const parse = this._parserFor(col);
|
|
2902
|
+
const ctx = {
|
|
2903
|
+
locale: cfg.locale ?? void 0,
|
|
2904
|
+
utc: g.table._utc(),
|
|
2905
|
+
messages: cfg.editorMessages ?? void 0,
|
|
2906
|
+
options: spec.options,
|
|
2907
|
+
allowOther: !!spec.allowOther
|
|
2908
|
+
};
|
|
2909
|
+
return (text) => {
|
|
2910
|
+
let r;
|
|
2911
|
+
try {
|
|
2912
|
+
r = parse(text, ctx);
|
|
2913
|
+
} catch (err) {
|
|
2914
|
+
return { ok: false, error: err?.message || String(err ?? "Not pasted") };
|
|
2915
|
+
}
|
|
2916
|
+
if (r && r.ok === true) return { ok: true, value: r.value };
|
|
2917
|
+
if (r && r.ok === false) return { ok: false, error: String(r.error ?? "Not pasted") };
|
|
2918
|
+
return { ok: false, error: "Not pasted" };
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
/** The position of the last loaded row: the rows held in the order shown
|
|
2922
|
+
* (all of them filtered and sorted; a server's page, the rows it sent). */
|
|
2923
|
+
_lastLoaded() {
|
|
2924
|
+
const t = this.grid.table;
|
|
2925
|
+
if (t._isServerSide()) return this.grid.span()[1];
|
|
2926
|
+
return t._getProcessedRows().length - 1;
|
|
2927
|
+
}
|
|
2928
|
+
_warn(message) {
|
|
2929
|
+
console.warn(`[DataTable] paste: ${message}`);
|
|
2930
|
+
this.grid.table._notify("Paste", message, "warn");
|
|
2931
|
+
}
|
|
2932
|
+
// ── the paste ────────────────────────────────────────────────────────
|
|
2933
|
+
/**
|
|
2934
|
+
* Paste tab-separated `text` at the cursor. Resolves `{rows, cols, staged,
|
|
2935
|
+
* skipped, refused, droppedColumns, appended}` — `staged` the NUMBER of
|
|
2936
|
+
* cells that changed, `skipped` the cells `editable` refused and `refused`
|
|
2937
|
+
* the ones whose text did not parse (`{row, col, rowIdx}`, `refused` with
|
|
2938
|
+
* its `text` and `error`), `droppedColumns` the columns past the last one,
|
|
2939
|
+
* `appended` the rows handed to `onRowsAppend` — or null when nothing was
|
|
2940
|
+
* pasted: empty text, no cursor, an editor open, a commit or another paste
|
|
2941
|
+
* out, an overrun declined, or new rows while `onPasteOverrun` was asked.
|
|
2942
|
+
*/
|
|
2943
|
+
async paste(text) {
|
|
2944
|
+
const g = this.grid;
|
|
2945
|
+
const matrix = fromTSV(text);
|
|
2946
|
+
if (!matrix.length || this.busy) return null;
|
|
2947
|
+
g._sync();
|
|
2948
|
+
const at = g.sel.focus ? { ...g.sel.focus } : null;
|
|
2949
|
+
if (!at || g.isEditing() || g.staged.committing) return null;
|
|
2950
|
+
const cfg = g._cfg();
|
|
2951
|
+
const cols = g.columns();
|
|
2952
|
+
const width = Math.max(...matrix.map((line) => line.length));
|
|
2953
|
+
const targetCols = cols.slice(cols.indexOf(at.col), cols.indexOf(at.col) + width);
|
|
2954
|
+
const droppedColumns = width - targetCols.length;
|
|
2955
|
+
const last = this._lastLoaded();
|
|
2956
|
+
const fit = Math.max(0, Math.min(matrix.length, last - at.row + 1));
|
|
2957
|
+
const extra = matrix.length - fit;
|
|
2958
|
+
const generation = g.generation;
|
|
2959
|
+
if (extra > 0) {
|
|
2960
|
+
const ask = cfg.onPasteOverrun;
|
|
2961
|
+
let answer = false;
|
|
2962
|
+
if (typeof ask === "function") {
|
|
2963
|
+
this.busy = true;
|
|
2964
|
+
try {
|
|
2965
|
+
answer = await ask({ rows: extra });
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
console.error("[DataTable] onPasteOverrun threw", err);
|
|
2968
|
+
answer = false;
|
|
2969
|
+
} finally {
|
|
2970
|
+
this.busy = false;
|
|
2971
|
+
}
|
|
2972
|
+
} else {
|
|
2973
|
+
console.warn(`[DataTable] paste: ${extra} row(s) past the last loaded row and no \`onPasteOverrun\`; nothing pasted.`);
|
|
2974
|
+
}
|
|
2975
|
+
if (answer !== true) return null;
|
|
2976
|
+
if (generation !== g.generation || g.isEditing() || g.staged.committing) return null;
|
|
2977
|
+
}
|
|
2978
|
+
const cells = [];
|
|
2979
|
+
const skipped = [];
|
|
2980
|
+
const refused = [];
|
|
2981
|
+
const readers = new Map(targetCols.map((col) => [col, this._reader(col)]));
|
|
2982
|
+
for (let r = 0; r < fit; r++) {
|
|
2983
|
+
const pos = at.row + r;
|
|
2984
|
+
const rowIdx = g.rowIndexAt(pos);
|
|
2985
|
+
targetCols.forEach((col, c) => {
|
|
2986
|
+
if (c >= matrix[r].length) return;
|
|
2987
|
+
if (!g.canEdit(pos, col)) {
|
|
2988
|
+
skipped.push({ row: pos, col, rowIdx });
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const text2 = matrix[r][c];
|
|
2992
|
+
const read = readers.get(col)(text2);
|
|
2993
|
+
if (read.ok) cells.push({ rowIdx, colIdx: col, value: read.value });
|
|
2994
|
+
else refused.push({ row: pos, col, rowIdx, text: text2, error: read.error });
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
let appended = 0;
|
|
2998
|
+
if (extra > 0) {
|
|
2999
|
+
const n = (cfg.headers || []).length;
|
|
3000
|
+
const rows = [];
|
|
3001
|
+
const errors = [];
|
|
3002
|
+
for (let r = fit; r < matrix.length; r++) {
|
|
3003
|
+
const row = new Array(n).fill(null);
|
|
3004
|
+
targetCols.forEach((col, c) => {
|
|
3005
|
+
if (c >= matrix[r].length) return;
|
|
3006
|
+
const text2 = matrix[r][c];
|
|
3007
|
+
const read = readers.get(col)(text2);
|
|
3008
|
+
if (read.ok) row[col] = read.value;
|
|
3009
|
+
else errors.push({ row: rows.length, colIdx: col, text: text2, error: read.error });
|
|
3010
|
+
});
|
|
3011
|
+
rows.push(row);
|
|
3012
|
+
}
|
|
3013
|
+
const append = cfg.onRowsAppend;
|
|
3014
|
+
if (typeof append === "function") {
|
|
3015
|
+
appended = rows.length;
|
|
3016
|
+
this.busy = true;
|
|
3017
|
+
try {
|
|
3018
|
+
await append({ rows, errors });
|
|
3019
|
+
} catch (err) {
|
|
3020
|
+
console.error("[DataTable] onRowsAppend threw", err);
|
|
3021
|
+
} finally {
|
|
3022
|
+
this.busy = false;
|
|
3023
|
+
}
|
|
3024
|
+
} else {
|
|
3025
|
+
this._warn(`${rows.length} row(s) past the last loaded row were not pasted (no \`onRowsAppend\`).`);
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
const staged = g.staged.stageMany(cells, `paste ${cells.length} cell(s)`);
|
|
3029
|
+
const lastCol = targetCols[targetCols.length - 1] ?? at.col;
|
|
3030
|
+
const lastRow = at.row + Math.max(0, fit - 1);
|
|
3031
|
+
g.set({ row: at.row, col: at.col }, { row: lastRow, col: lastCol });
|
|
3032
|
+
for (const s of skipped) this.skipped.add(key3(s.rowIdx, s.col));
|
|
3033
|
+
const redraw = /* @__PURE__ */ new Set();
|
|
3034
|
+
for (const f of refused) {
|
|
3035
|
+
const k = key3(f.rowIdx, f.col);
|
|
3036
|
+
g.errors.set(k, { value: f.text, message: f.error });
|
|
3037
|
+
g.latest.set(k, {});
|
|
3038
|
+
g.pending.delete(k);
|
|
3039
|
+
redraw.add(f.rowIdx);
|
|
3040
|
+
}
|
|
3041
|
+
for (const rowIdx of redraw) g._redraw(rowIdx);
|
|
3042
|
+
if (skipped.length) g.paint();
|
|
3043
|
+
if (droppedColumns > 0) {
|
|
3044
|
+
this._warn(`${droppedColumns} column(s) past the last column were not pasted.`);
|
|
3045
|
+
}
|
|
3046
|
+
return {
|
|
3047
|
+
rows: matrix.length,
|
|
3048
|
+
cols: width,
|
|
3049
|
+
staged,
|
|
3050
|
+
skipped,
|
|
3051
|
+
refused,
|
|
3052
|
+
droppedColumns,
|
|
3053
|
+
appended
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
dispose() {
|
|
3057
|
+
this.skipped.clear();
|
|
3058
|
+
}
|
|
3059
|
+
};
|
|
3060
|
+
|
|
1765
3061
|
// src/ui/components/cell_grid.js
|
|
1766
3062
|
function isFilledValue(value) {
|
|
1767
3063
|
if (value == null) return false;
|
|
@@ -1778,6 +3074,16 @@ var ARROWS = {
|
|
|
1778
3074
|
ArrowLeft: [0, -1],
|
|
1779
3075
|
ArrowRight: [0, 1]
|
|
1780
3076
|
};
|
|
3077
|
+
function copyText(value, { kind, utc }) {
|
|
3078
|
+
if (value === null || value === void 0) return "";
|
|
3079
|
+
const dated = kind === "date" || kind === "datetime";
|
|
3080
|
+
if (value instanceof Date || dated && typeof value === "number") {
|
|
3081
|
+
const t = value instanceof Date ? value.getTime() : value;
|
|
3082
|
+
if (!Number.isFinite(t)) return "";
|
|
3083
|
+
return kind === "date" ? formatDate(t, ISO_DATE, { utc }) : new Date(t).toISOString();
|
|
3084
|
+
}
|
|
3085
|
+
return String(value);
|
|
3086
|
+
}
|
|
1781
3087
|
var CellGrid = class {
|
|
1782
3088
|
/** @param {import('./data_table.js').DataTable} table */
|
|
1783
3089
|
constructor(table) {
|
|
@@ -1791,7 +3097,8 @@ var CellGrid = class {
|
|
|
1791
3097
|
this.generation = 0;
|
|
1792
3098
|
this.editor = new CellEditorHost({
|
|
1793
3099
|
onCommit: (s, value, move, reason) => this._committed(s, value, move, reason),
|
|
1794
|
-
onCancel: (s, reason) => this._closed(s, reason)
|
|
3100
|
+
onCancel: (s, reason) => this._closed(s, reason),
|
|
3101
|
+
onRefuse: (s, error, text, reason) => this._refused(s, error, text, reason)
|
|
1795
3102
|
});
|
|
1796
3103
|
this.staged = null;
|
|
1797
3104
|
if (this._cfg().staged) {
|
|
@@ -1803,6 +3110,11 @@ var CellGrid = class {
|
|
|
1803
3110
|
if (this.staged) this.fill = new FillHandle(this);
|
|
1804
3111
|
else console.warn("[DataTable] `fillHandle` needs `staged`; ignored.");
|
|
1805
3112
|
}
|
|
3113
|
+
this.paste = null;
|
|
3114
|
+
if (this._cfg().pasteable) {
|
|
3115
|
+
if (this.staged) this.paste = new CellPaste(this);
|
|
3116
|
+
else console.warn("[DataTable] `pasteable` needs `staged`; ignored.");
|
|
3117
|
+
}
|
|
1806
3118
|
}
|
|
1807
3119
|
// ── what the table is, read through its own helpers ──────────────────
|
|
1808
3120
|
_cfg() {
|
|
@@ -1847,11 +3159,11 @@ var CellGrid = class {
|
|
|
1847
3159
|
}
|
|
1848
3160
|
/** What a cell shows: a refused or pending commit's value, else the row's. */
|
|
1849
3161
|
shownValue(rowIdx, col) {
|
|
1850
|
-
const
|
|
1851
|
-
const err = this.errors.get(
|
|
3162
|
+
const key4 = `${rowIdx}:${col}`;
|
|
3163
|
+
const err = this.errors.get(key4);
|
|
1852
3164
|
if (err) return err.value;
|
|
1853
|
-
if (this.pending.has(
|
|
1854
|
-
if (this.staged?.has(
|
|
3165
|
+
if (this.pending.has(key4)) return this.pending.get(key4);
|
|
3166
|
+
if (this.staged?.has(key4)) return this.staged.value(key4);
|
|
1855
3167
|
const row = this.currentRow(rowIdx);
|
|
1856
3168
|
return row ? row[col] : void 0;
|
|
1857
3169
|
}
|
|
@@ -1940,6 +3252,28 @@ var CellGrid = class {
|
|
|
1940
3252
|
this.sel.clear();
|
|
1941
3253
|
this._changed();
|
|
1942
3254
|
}
|
|
3255
|
+
/**
|
|
3256
|
+
* STORY-0085. What a copy of the selection puts on the clipboard: the
|
|
3257
|
+
* range's cells as text, one array per row in the order SHOWN (after the
|
|
3258
|
+
* sort and the filters), its shown columns left to right (a hidden column
|
|
3259
|
+
* inside it is not one of them). With no range beyond the cursor, the
|
|
3260
|
+
* cursor cell alone. A cell is its RAW value as text (`copyText`), not
|
|
3261
|
+
* the text it is drawn as: the value the cell holds now, a staged,
|
|
3262
|
+
* pending or refused one included. Null with nothing selected.
|
|
3263
|
+
*/
|
|
3264
|
+
copyMatrix() {
|
|
3265
|
+
this._sync();
|
|
3266
|
+
const r = this.sel.range();
|
|
3267
|
+
if (!r) return null;
|
|
3268
|
+
const t = this.table;
|
|
3269
|
+
const utc = t._utc();
|
|
3270
|
+
const out = [];
|
|
3271
|
+
for (let p = r.top; p <= r.bottom; p++) {
|
|
3272
|
+
const rowIdx = this.rowIndexAt(p);
|
|
3273
|
+
out.push(r.columns.map((col) => copyText(this.shownValue(rowIdx, col), { kind: t._valueKind(col), utc })));
|
|
3274
|
+
}
|
|
3275
|
+
return out;
|
|
3276
|
+
}
|
|
1943
3277
|
/** New rows (`setData({rows})`): the marks were about the old ones. */
|
|
1944
3278
|
newRows() {
|
|
1945
3279
|
this.generation++;
|
|
@@ -1949,6 +3283,7 @@ var CellGrid = class {
|
|
|
1949
3283
|
this.rowsWaiting.clear();
|
|
1950
3284
|
this.staged?.clear();
|
|
1951
3285
|
this.fill?.clearMarks();
|
|
3286
|
+
this.paste?.clearMarks();
|
|
1952
3287
|
}
|
|
1953
3288
|
isEditing() {
|
|
1954
3289
|
return !!this.editor.session;
|
|
@@ -1967,16 +3302,16 @@ var CellGrid = class {
|
|
|
1967
3302
|
* (STORY-0088) its staged value and a refused batch's mark. */
|
|
1968
3303
|
_decorateCell(td, pos, rowIdx, col) {
|
|
1969
3304
|
this._paintCell(td, pos, col);
|
|
1970
|
-
const
|
|
1971
|
-
const err = this.errors.get(
|
|
1972
|
-
const waiting = this.pending.has(
|
|
1973
|
-
const staged = !!this.staged?.has(
|
|
3305
|
+
const key4 = `${rowIdx}:${col}`;
|
|
3306
|
+
const err = this.errors.get(key4);
|
|
3307
|
+
const waiting = this.pending.has(key4);
|
|
3308
|
+
const staged = !!this.staged?.has(key4);
|
|
1974
3309
|
if (err || waiting) {
|
|
1975
|
-
td.textContent = this.table._formatValue(err ? err.value : this.pending.get(
|
|
3310
|
+
td.textContent = this.table._formatValue(err ? err.value : this.pending.get(key4), col);
|
|
1976
3311
|
} else if (staged) {
|
|
1977
|
-
td.textContent = this.table._formatValue(this.staged.value(
|
|
3312
|
+
td.textContent = this.table._formatValue(this.staged.value(key4), col);
|
|
1978
3313
|
}
|
|
1979
|
-
const message = err ? err.message : this.staged?.errors.get(
|
|
3314
|
+
const message = err ? err.message : this.staged?.errors.get(key4);
|
|
1980
3315
|
td.classList.toggle("twm-dt-cell--pending", waiting);
|
|
1981
3316
|
td.classList.toggle("twm-dt-cell--staged", staged);
|
|
1982
3317
|
td.classList.toggle("twm-dt-cell--error", message != null);
|
|
@@ -1993,6 +3328,7 @@ var CellGrid = class {
|
|
|
1993
3328
|
if (cursor) td.setAttribute("aria-selected", "true");
|
|
1994
3329
|
else td.removeAttribute("aria-selected");
|
|
1995
3330
|
this.fill?.paintCell(td, pos, col);
|
|
3331
|
+
this.paste?.paintCell(td, col);
|
|
1996
3332
|
}
|
|
1997
3333
|
paint() {
|
|
1998
3334
|
const tbody = this.table._tbodyEl;
|
|
@@ -2010,6 +3346,7 @@ var CellGrid = class {
|
|
|
2010
3346
|
_changed({ reveal = false } = {}) {
|
|
2011
3347
|
const t = this.table;
|
|
2012
3348
|
this.fill?.clearMarks();
|
|
3349
|
+
this.paste?.clearMarks();
|
|
2013
3350
|
if (reveal && this.sel.focus) t.scrollToRow(this.rowIndexAt(this.sel.focus.row));
|
|
2014
3351
|
this.paint();
|
|
2015
3352
|
const before = t.getSelection().join();
|
|
@@ -2082,6 +3419,16 @@ var CellGrid = class {
|
|
|
2082
3419
|
ev.stopImmediatePropagation();
|
|
2083
3420
|
}
|
|
2084
3421
|
});
|
|
3422
|
+
if (this.paste) {
|
|
3423
|
+
on(table, "paste", (ev) => {
|
|
3424
|
+
if (inEditor(ev.target)) return;
|
|
3425
|
+
if (ev.target !== table && ev.target?.closest?.('input, select, textarea, [contenteditable="true"]')) return;
|
|
3426
|
+
const text = ev.clipboardData?.getData?.("text/plain");
|
|
3427
|
+
if (typeof text !== "string") return;
|
|
3428
|
+
ev.preventDefault();
|
|
3429
|
+
this.paste.paste(text);
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
2085
3432
|
this.paint();
|
|
2086
3433
|
this.staged?.paintFreeze();
|
|
2087
3434
|
}
|
|
@@ -2159,20 +3506,37 @@ var CellGrid = class {
|
|
|
2159
3506
|
}
|
|
2160
3507
|
return false;
|
|
2161
3508
|
}
|
|
2162
|
-
/** The column's
|
|
2163
|
-
|
|
2164
|
-
|
|
3509
|
+
/** STORY-0087. The column's `editTypes` entry, as `{type, ...}`, or null. */
|
|
3510
|
+
_editSpec(col) {
|
|
3511
|
+
const all = this._cfg().editTypes;
|
|
3512
|
+
let spec = all && typeof all === "object" ? all[col] : null;
|
|
3513
|
+
if (typeof spec === "string") spec = { type: spec };
|
|
3514
|
+
return spec && typeof spec === "object" ? spec : null;
|
|
3515
|
+
}
|
|
3516
|
+
/** The column's type as it is named: `editTypes`', else the declared one
|
|
3517
|
+
* (`columnTypes`), else the detected one. */
|
|
3518
|
+
_rawType(col) {
|
|
3519
|
+
const spec = this._editSpec(col);
|
|
3520
|
+
if (spec?.type) return String(spec.type);
|
|
2165
3521
|
const declared = this._cfg().columnTypes?.[col];
|
|
2166
3522
|
return declared || this.table._columnTypes?.[col] || "text";
|
|
2167
3523
|
}
|
|
3524
|
+
/** The column's type as `editors` and the built-in editors know it:
|
|
3525
|
+
* 'num' is 'number', 'date' is 'datetime'. */
|
|
3526
|
+
_typeOf(col) {
|
|
3527
|
+
return editorTypeOf(this._rawType(col));
|
|
3528
|
+
}
|
|
3529
|
+
/** By column index, then by type (as named, then as the built-ins name
|
|
3530
|
+
* it), then the built-in editor for the type, else the text box. */
|
|
2168
3531
|
_factoryFor(col) {
|
|
2169
3532
|
const editors = this._cfg().editors;
|
|
2170
3533
|
if (editors && typeof editors === "object") {
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
3534
|
+
for (const k of [col, this._rawType(col), this._typeOf(col)]) {
|
|
3535
|
+
if (typeof editors[k] === "function") return editors[k];
|
|
3536
|
+
}
|
|
2174
3537
|
}
|
|
2175
|
-
|
|
3538
|
+
const builtIn = Object.prototype.hasOwnProperty.call(CELL_EDITORS, this._typeOf(col)) ? CELL_EDITORS[this._typeOf(col)] : null;
|
|
3539
|
+
return builtIn ? builtIn.create : createTextEditor;
|
|
2176
3540
|
}
|
|
2177
3541
|
/**
|
|
2178
3542
|
* Open the editor on a cell (position, data column). Brings the row into
|
|
@@ -2195,7 +3559,12 @@ var CellGrid = class {
|
|
|
2195
3559
|
row,
|
|
2196
3560
|
generation: this.generation,
|
|
2197
3561
|
value: this.shownValue(rowIdx, col),
|
|
2198
|
-
column: { index: col, header: headers[col], type: this._typeOf(col) },
|
|
3562
|
+
column: { ...this._editSpec(col), index: col, header: headers[col], type: this._typeOf(col) },
|
|
3563
|
+
args: {
|
|
3564
|
+
locale: this._cfg().locale ?? void 0,
|
|
3565
|
+
utc: this.table._utc(),
|
|
3566
|
+
messages: this._cfg().editorMessages ?? void 0
|
|
3567
|
+
},
|
|
2199
3568
|
initialText
|
|
2200
3569
|
});
|
|
2201
3570
|
}
|
|
@@ -2208,6 +3577,7 @@ var CellGrid = class {
|
|
|
2208
3577
|
}
|
|
2209
3578
|
dispose() {
|
|
2210
3579
|
this.fill?.dispose();
|
|
3580
|
+
this.paste?.dispose();
|
|
2211
3581
|
this.editor.cancel("dispose");
|
|
2212
3582
|
this.renderWaiting = false;
|
|
2213
3583
|
this.rowsWaiting.clear();
|
|
@@ -2268,6 +3638,20 @@ var CellGrid = class {
|
|
|
2268
3638
|
this.rowsWaiting.delete(rowIdx);
|
|
2269
3639
|
t.updateRow(rowIdx, row);
|
|
2270
3640
|
}
|
|
3641
|
+
/** STORY-0087. The editor closed holding text that does not parse (the
|
|
3642
|
+
* focus left it, or its row left the window): nothing is committed, and
|
|
3643
|
+
* the cell keeps the text, marked with the message, as a refused commit's
|
|
3644
|
+
* would be. */
|
|
3645
|
+
_refused(s, error, text, reason) {
|
|
3646
|
+
if (s.generation === this.generation) {
|
|
3647
|
+
const key4 = `${s.rowIdx}:${s.colIdx}`;
|
|
3648
|
+
this.errors.set(key4, { value: text, message: error });
|
|
3649
|
+
this.latest.set(key4, {});
|
|
3650
|
+
this.pending.delete(key4);
|
|
3651
|
+
this._redraw(s.rowIdx, s.colIdx);
|
|
3652
|
+
}
|
|
3653
|
+
this._closed(s, reason);
|
|
3654
|
+
}
|
|
2271
3655
|
_committed(s, value, move, reason) {
|
|
2272
3656
|
if (move) {
|
|
2273
3657
|
this._sync();
|
|
@@ -2276,7 +3660,7 @@ var CellGrid = class {
|
|
|
2276
3660
|
this._changed({ reveal: true });
|
|
2277
3661
|
}
|
|
2278
3662
|
const live = s.generation === this.generation;
|
|
2279
|
-
const
|
|
3663
|
+
const key4 = `${s.rowIdx}:${s.colIdx}`;
|
|
2280
3664
|
if (Object.is(value, s.previous)) {
|
|
2281
3665
|
this._closed(s, reason);
|
|
2282
3666
|
return;
|
|
@@ -2288,29 +3672,29 @@ var CellGrid = class {
|
|
|
2288
3672
|
}
|
|
2289
3673
|
const held = live ? this.currentRow(s.rowIdx) : s.row;
|
|
2290
3674
|
const stored = held ? held[s.colIdx] : void 0;
|
|
2291
|
-
if (live && this.errors.has(
|
|
2292
|
-
this.errors.delete(
|
|
2293
|
-
this.latest.delete(
|
|
3675
|
+
if (live && this.errors.has(key4) && !this.pending.has(key4) && Object.is(value, stored)) {
|
|
3676
|
+
this.errors.delete(key4);
|
|
3677
|
+
this.latest.delete(key4);
|
|
2294
3678
|
this._redraw(s.rowIdx, s.colIdx);
|
|
2295
3679
|
this._closed(s, reason);
|
|
2296
3680
|
return;
|
|
2297
3681
|
}
|
|
2298
3682
|
const fn = this._cfg().onCellCommit;
|
|
2299
3683
|
const token = {};
|
|
2300
|
-
if (live) this.latest.set(
|
|
3684
|
+
if (live) this.latest.set(key4, token);
|
|
2301
3685
|
const finish = (result2) => {
|
|
2302
3686
|
if (!live || s.generation !== this.generation) return;
|
|
2303
|
-
const newest = this.latest.get(
|
|
3687
|
+
const newest = this.latest.get(key4) === token;
|
|
2304
3688
|
const good = !(result2 && result2.ok === false);
|
|
2305
3689
|
if (good) {
|
|
2306
3690
|
const row = this.currentRow(s.rowIdx);
|
|
2307
3691
|
if (Array.isArray(row)) row[s.colIdx] = value;
|
|
2308
3692
|
}
|
|
2309
3693
|
if (newest) {
|
|
2310
|
-
this.latest.delete(
|
|
2311
|
-
this.pending.delete(
|
|
2312
|
-
if (good) this.errors.delete(
|
|
2313
|
-
else this.errors.set(
|
|
3694
|
+
this.latest.delete(key4);
|
|
3695
|
+
this.pending.delete(key4);
|
|
3696
|
+
if (good) this.errors.delete(key4);
|
|
3697
|
+
else this.errors.set(key4, { value, message: String(result2.error ?? "Not saved") });
|
|
2314
3698
|
}
|
|
2315
3699
|
this._redraw(s.rowIdx, s.colIdx);
|
|
2316
3700
|
};
|
|
@@ -2325,8 +3709,8 @@ var CellGrid = class {
|
|
|
2325
3709
|
}
|
|
2326
3710
|
if (result && typeof result.then === "function") {
|
|
2327
3711
|
if (live) {
|
|
2328
|
-
this.pending.set(
|
|
2329
|
-
this.errors.delete(
|
|
3712
|
+
this.pending.set(key4, value);
|
|
3713
|
+
this.errors.delete(key4);
|
|
2330
3714
|
this._redraw(s.rowIdx, s.colIdx);
|
|
2331
3715
|
}
|
|
2332
3716
|
this._closed(s, reason);
|
|
@@ -2545,8 +3929,8 @@ var ColumnGroups = class {
|
|
|
2545
3929
|
const n = cfg.headers?.length || 0;
|
|
2546
3930
|
const shown = this.table._shownColumns();
|
|
2547
3931
|
const spec = Array.isArray(cfg.columnGroups) ? cfg.columnGroups : [];
|
|
2548
|
-
const
|
|
2549
|
-
if (
|
|
3932
|
+
const key4 = `${n}|${shown.join(",")}|${spec.map((g) => g ? `${g.key}${g.label}${(g.columns || []).join(",")}` : "").join("")}`;
|
|
3933
|
+
if (key4 === this._memoKey) return this._memo;
|
|
2550
3934
|
const groups = normalize(spec, n);
|
|
2551
3935
|
const groupOf = new Array(n).fill(null);
|
|
2552
3936
|
groups.forEach((g, gi) => {
|
|
@@ -2574,7 +3958,7 @@ var ColumnGroups = class {
|
|
|
2574
3958
|
if (!s.placeholder) slotOfCol.set(s.col, i);
|
|
2575
3959
|
});
|
|
2576
3960
|
const placeholders = slots.filter((s) => s.placeholder).length;
|
|
2577
|
-
this._memoKey =
|
|
3961
|
+
this._memoKey = key4;
|
|
2578
3962
|
this._memo = { groups, slots, slotOfCol, placeholders };
|
|
2579
3963
|
return this._memo;
|
|
2580
3964
|
}
|
|
@@ -3277,7 +4661,12 @@ function compareSortKeys(a, b, asc) {
|
|
|
3277
4661
|
}
|
|
3278
4662
|
var AUTOSIZE_MAX_PX = 520;
|
|
3279
4663
|
var VIRTUAL_FALLBACK_VIEWPORT_PX = 1e3;
|
|
3280
|
-
var
|
|
4664
|
+
var COPY_CELLS_MENU_HTML = `
|
|
4665
|
+
<div class="twm-context-menu-item" data-action="copy-tsv">
|
|
4666
|
+
<span class="material-symbols-outlined">content_copy</span>
|
|
4667
|
+
<span class="label">Copy Cells</span>
|
|
4668
|
+
</div>`;
|
|
4669
|
+
var copyMenuHtml = (cells) => cells ? COPY_CELLS_MENU_HTML : `
|
|
3281
4670
|
<div class="twm-context-menu-item" data-action="copy-tsv">
|
|
3282
4671
|
<span class="material-symbols-outlined">content_copy</span>
|
|
3283
4672
|
<span class="label">Copy Row(s)</span>
|
|
@@ -3424,6 +4813,9 @@ var DataTable = class {
|
|
|
3424
4813
|
selectionMode: "rows",
|
|
3425
4814
|
editable: false,
|
|
3426
4815
|
editors: null,
|
|
4816
|
+
editTypes: null,
|
|
4817
|
+
locale: null,
|
|
4818
|
+
editorMessages: null,
|
|
3427
4819
|
onCellCommit: null,
|
|
3428
4820
|
onCellSelectionChange: null,
|
|
3429
4821
|
headerMenu: null,
|
|
@@ -3448,6 +4840,10 @@ var DataTable = class {
|
|
|
3448
4840
|
onRowDelete: null,
|
|
3449
4841
|
// STORY-0089. Off — no fill handle — until set.
|
|
3450
4842
|
fillHandle: false,
|
|
4843
|
+
// STORY-0090. Off — Ctrl+V is not the table's — until set.
|
|
4844
|
+
pasteable: false,
|
|
4845
|
+
onPasteOverrun: null,
|
|
4846
|
+
onRowsAppend: null,
|
|
3451
4847
|
...config
|
|
3452
4848
|
};
|
|
3453
4849
|
this._cellGrid = null;
|
|
@@ -3470,6 +4866,9 @@ var DataTable = class {
|
|
|
3470
4866
|
if (this.config.fillHandle && !this._cellGrid) {
|
|
3471
4867
|
console.warn("[DataTable] `fillHandle` needs `staged`; ignored.");
|
|
3472
4868
|
}
|
|
4869
|
+
if (this.config.pasteable && !this._cellGrid) {
|
|
4870
|
+
console.warn("[DataTable] `pasteable` needs `staged`; ignored.");
|
|
4871
|
+
}
|
|
3473
4872
|
if (Array.isArray(this.config.columnGroups) && this.config.columnGroups.length && this._crosstab()) {
|
|
3474
4873
|
console.warn("[DataTable] `columnGroups` cannot be combined with `crosstab` (use crosstab.headerRows); ignored.");
|
|
3475
4874
|
}
|
|
@@ -4101,9 +5500,9 @@ var DataTable = class {
|
|
|
4101
5500
|
*
|
|
4102
5501
|
* @param {*} key
|
|
4103
5502
|
*/
|
|
4104
|
-
setActiveRow(
|
|
4105
|
-
this._activeKey =
|
|
4106
|
-
this.config.activeRow =
|
|
5503
|
+
setActiveRow(key4) {
|
|
5504
|
+
this._activeKey = key4 == null ? null : String(key4);
|
|
5505
|
+
this.config.activeRow = key4 ?? null;
|
|
4107
5506
|
const tbody = this._tbodyEl;
|
|
4108
5507
|
if (!tbody) return;
|
|
4109
5508
|
for (const tr of tbody.children) {
|
|
@@ -4242,6 +5641,24 @@ var DataTable = class {
|
|
|
4242
5641
|
fill(to, { forceCopy = false } = {}) {
|
|
4243
5642
|
return this._cellGrid?.fill ? this._cellGrid.fill.fill(to, { forceCopy }) : null;
|
|
4244
5643
|
}
|
|
5644
|
+
/**
|
|
5645
|
+
* STORY-0090. Paste tab-separated `text` at the cursor cell, as Ctrl+V
|
|
5646
|
+
* does: each value read by its column's parse, staged as ONE undo entry.
|
|
5647
|
+
* Resolves `{rows, cols, staged, skipped, refused, droppedColumns,
|
|
5648
|
+
* appended}` — `staged` the NUMBER of cells that changed, `skipped` the
|
|
5649
|
+
* cells `editable` refused (`{row, col, rowIdx}`), `refused` the values
|
|
5650
|
+
* that did not parse (`{row, col, rowIdx, text, error}`),
|
|
5651
|
+
* `droppedColumns` the columns past the last one, `appended` the rows
|
|
5652
|
+
* handed to `onRowsAppend` — or null when nothing was pasted (no
|
|
5653
|
+
* `pasteable`, empty text, no cursor, an editor open, a commit or another
|
|
5654
|
+
* paste out, an overrun `onPasteOverrun` did not accept).
|
|
5655
|
+
*
|
|
5656
|
+
* @param {string} text
|
|
5657
|
+
* @returns {Promise<object|null>}
|
|
5658
|
+
*/
|
|
5659
|
+
paste(text) {
|
|
5660
|
+
return this._cellGrid?.paste ? this._cellGrid.paste.paste(text) : Promise.resolve(null);
|
|
5661
|
+
}
|
|
4245
5662
|
/** Why sort, filters and pages are held — a staged edit — or null; with
|
|
4246
5663
|
* a reason, `what` is refused out loud. */
|
|
4247
5664
|
_stagedRefusal(what) {
|
|
@@ -4626,10 +6043,19 @@ var DataTable = class {
|
|
|
4626
6043
|
}
|
|
4627
6044
|
}
|
|
4628
6045
|
/**
|
|
4629
|
-
* Copy selected rows to clipboard
|
|
6046
|
+
* Copy selected rows to clipboard — in cell mode (STORY-0085) the cells
|
|
6047
|
+
* selected, or the cursor cell alone, as tab-separated raw values with no
|
|
6048
|
+
* header line; cell mode has no CSV copy
|
|
4630
6049
|
* @param {'tsv'|'csv'} [format='tsv'] - Output format
|
|
4631
6050
|
*/
|
|
4632
6051
|
async copyToClipboard(format = "tsv") {
|
|
6052
|
+
if (this._cellGrid) {
|
|
6053
|
+
if (format === "csv") {
|
|
6054
|
+
console.warn("[DataTable] copyToClipboard: cell mode has no CSV copy; nothing copied");
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
return this._copyCells();
|
|
6058
|
+
}
|
|
4633
6059
|
const indices = this.getSelection();
|
|
4634
6060
|
if (indices.length === 0) {
|
|
4635
6061
|
this._notify("Copy", "No rows selected.", "warn");
|
|
@@ -4645,14 +6071,14 @@ var DataTable = class {
|
|
|
4645
6071
|
}
|
|
4646
6072
|
const separator = format === "csv" ? "," : " ";
|
|
4647
6073
|
const text = format === "csv" ? matrix.map((row) => row.map((cell) => this._csvEscape(cell)).join(separator)).join("\n") : matrix.map((row) => row.join(separator)).join("\n");
|
|
4648
|
-
let
|
|
6074
|
+
let ok2 = false;
|
|
4649
6075
|
try {
|
|
4650
6076
|
await navigator.clipboard.writeText(text);
|
|
4651
|
-
|
|
6077
|
+
ok2 = true;
|
|
4652
6078
|
} catch (_) {
|
|
4653
|
-
|
|
6079
|
+
ok2 = this._fallbackCopy(text);
|
|
4654
6080
|
}
|
|
4655
|
-
if (
|
|
6081
|
+
if (ok2) {
|
|
4656
6082
|
const desc = indices.length === 1 ? "row" : "rows";
|
|
4657
6083
|
const suffix = format === "csv" ? " as CSV" : "";
|
|
4658
6084
|
this._notify("Copy", `Copied ${indices.length} ${desc}${suffix}.`, "info");
|
|
@@ -4661,6 +6087,35 @@ var DataTable = class {
|
|
|
4661
6087
|
}
|
|
4662
6088
|
this._hideContextMenu();
|
|
4663
6089
|
}
|
|
6090
|
+
/**
|
|
6091
|
+
* STORY-0085. Cell mode's copy: the range (or the cursor cell alone), its
|
|
6092
|
+
* raw values as TSV — Excel's quoting, `tsv.js` — with no header line.
|
|
6093
|
+
*/
|
|
6094
|
+
async _copyCells() {
|
|
6095
|
+
const matrix = this._cellGrid.copyMatrix();
|
|
6096
|
+
if (!matrix || !matrix.length) {
|
|
6097
|
+
this._notify("Copy", "No cells selected.", "warn");
|
|
6098
|
+
this._hideContextMenu();
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6101
|
+
const text = toTSV(matrix);
|
|
6102
|
+
let ok2 = false;
|
|
6103
|
+
try {
|
|
6104
|
+
await navigator.clipboard.writeText(text);
|
|
6105
|
+
ok2 = true;
|
|
6106
|
+
} catch (_) {
|
|
6107
|
+
ok2 = this._fallbackCopy(text);
|
|
6108
|
+
}
|
|
6109
|
+
if (ok2) {
|
|
6110
|
+
const n = matrix.length;
|
|
6111
|
+
const m = matrix[0].length;
|
|
6112
|
+
const what = n * m === 1 ? "1 cell" : `${n} \xD7 ${m} cells`;
|
|
6113
|
+
this._notify("Copy", `Copied ${what}.`, "info");
|
|
6114
|
+
} else {
|
|
6115
|
+
this._notify("Copy", "Clipboard unavailable.", "warn");
|
|
6116
|
+
}
|
|
6117
|
+
this._hideContextMenu();
|
|
6118
|
+
}
|
|
4664
6119
|
/**
|
|
4665
6120
|
* Download all data as CSV
|
|
4666
6121
|
* @param {string} [filename='data.csv'] - Filename for download
|
|
@@ -6591,8 +8046,8 @@ var DataTable = class {
|
|
|
6591
8046
|
td.insertBefore(gap, td.firstChild);
|
|
6592
8047
|
return;
|
|
6593
8048
|
}
|
|
6594
|
-
const
|
|
6595
|
-
const open =
|
|
8049
|
+
const key4 = this._rowKey(row, rowIdx);
|
|
8050
|
+
const open = key4 !== null && this._expanded.has(key4);
|
|
6596
8051
|
const btn = document.createElement("button");
|
|
6597
8052
|
btn.type = "button";
|
|
6598
8053
|
btn.className = "material-symbols-outlined twm-dt-detail-toggle";
|
|
@@ -6614,14 +8069,14 @@ var DataTable = class {
|
|
|
6614
8069
|
const row = tr.__row;
|
|
6615
8070
|
const idx = tr.__rowIndex;
|
|
6616
8071
|
if (!this._rowHasDetail(row, idx)) return;
|
|
6617
|
-
const
|
|
6618
|
-
if (
|
|
6619
|
-
const pieces = this._buildDetailPieces(tr,
|
|
8072
|
+
const key4 = this._rowKey(row, idx);
|
|
8073
|
+
if (key4 === null || !this._expanded.has(key4)) return;
|
|
8074
|
+
const pieces = this._buildDetailPieces(tr, key4);
|
|
6620
8075
|
const parent = tr.parentNode;
|
|
6621
8076
|
if (!parent) return;
|
|
6622
8077
|
for (const p of pieces) parent.insertBefore(p, ref);
|
|
6623
8078
|
}
|
|
6624
|
-
_buildDetailPieces(tr,
|
|
8079
|
+
_buildDetailPieces(tr, key4) {
|
|
6625
8080
|
const n = this._domColCount();
|
|
6626
8081
|
const detailTr = document.createElement("tr");
|
|
6627
8082
|
detailTr.className = "twm-dt-detail-row";
|
|
@@ -6640,16 +8095,16 @@ var DataTable = class {
|
|
|
6640
8095
|
filler.setAttribute("aria-hidden", "true");
|
|
6641
8096
|
detailTr.__detailOf = tr;
|
|
6642
8097
|
tr.__detail = [detailTr, filler];
|
|
6643
|
-
this._drawDetail(tr, host,
|
|
8098
|
+
this._drawDetail(tr, host, key4);
|
|
6644
8099
|
const btn = tr.querySelector(":scope > td > .twm-dt-detail-toggle");
|
|
6645
8100
|
if (btn) btn.setAttribute("aria-controls", host.id);
|
|
6646
8101
|
return tr.__detail;
|
|
6647
8102
|
}
|
|
6648
|
-
_drawDetail(tr, host,
|
|
8103
|
+
_drawDetail(tr, host, key4) {
|
|
6649
8104
|
host.replaceChildren();
|
|
6650
8105
|
let out;
|
|
6651
8106
|
try {
|
|
6652
|
-
out = this.config.rowDetail(tr.__row, tr.__rowIndex, { host, key:
|
|
8107
|
+
out = this.config.rowDetail(tr.__row, tr.__rowIndex, { host, key: key4, table: this });
|
|
6653
8108
|
} catch (err) {
|
|
6654
8109
|
console.error("[DataTable] rowDetail threw", err);
|
|
6655
8110
|
out = err?.message || String(err);
|
|
@@ -6668,8 +8123,8 @@ var DataTable = class {
|
|
|
6668
8123
|
}
|
|
6669
8124
|
/** `updateRow`'s half: the detail follows the row's new data. */
|
|
6670
8125
|
_redrawDetail(tr) {
|
|
6671
|
-
const
|
|
6672
|
-
const want = this._rowHasDetail(tr.__row, tr.__rowIndex) &&
|
|
8126
|
+
const key4 = this._rowKey(tr.__row, tr.__rowIndex);
|
|
8127
|
+
const want = this._rowHasDetail(tr.__row, tr.__rowIndex) && key4 !== null && this._expanded.has(key4);
|
|
6673
8128
|
const pieces = tr.__detail;
|
|
6674
8129
|
if (!want) {
|
|
6675
8130
|
if (pieces) this._removeDetailPieces(tr);
|
|
@@ -6678,7 +8133,7 @@ var DataTable = class {
|
|
|
6678
8133
|
if (pieces) {
|
|
6679
8134
|
const host = pieces[0].querySelector(".twm-dt-detail");
|
|
6680
8135
|
if (host) {
|
|
6681
|
-
this._drawDetail(tr, host,
|
|
8136
|
+
this._drawDetail(tr, host, key4);
|
|
6682
8137
|
const btn = tr.querySelector(":scope > td > .twm-dt-detail-toggle");
|
|
6683
8138
|
if (btn) btn.setAttribute("aria-controls", host.id);
|
|
6684
8139
|
}
|
|
@@ -6691,27 +8146,27 @@ var DataTable = class {
|
|
|
6691
8146
|
_setRowElExpanded(tr, open, ev = null) {
|
|
6692
8147
|
if (!this._detailOn() || tr.__rowIndex === void 0) return false;
|
|
6693
8148
|
if (!this._rowHasDetail(tr.__row, tr.__rowIndex)) return false;
|
|
6694
|
-
const
|
|
6695
|
-
if (
|
|
6696
|
-
if (open) this._expanded.add(
|
|
6697
|
-
else this._expanded.delete(
|
|
6698
|
-
this._syncDetailOnScreen(tr,
|
|
8149
|
+
const key4 = this._rowKey(tr.__row, tr.__rowIndex);
|
|
8150
|
+
if (key4 === null || this._expanded.has(key4) === !!open) return false;
|
|
8151
|
+
if (open) this._expanded.add(key4);
|
|
8152
|
+
else this._expanded.delete(key4);
|
|
8153
|
+
this._syncDetailOnScreen(tr, key4);
|
|
6699
8154
|
this._afterDetailChange();
|
|
6700
8155
|
try {
|
|
6701
|
-
this.config.onRowDetailToggle?.(tr.__rowIndex, tr.__row, !!open,
|
|
8156
|
+
this.config.onRowDetailToggle?.(tr.__rowIndex, tr.__row, !!open, key4, ev);
|
|
6702
8157
|
} catch (err) {
|
|
6703
8158
|
console.error("[DataTable] onRowDetailToggle threw", err);
|
|
6704
8159
|
}
|
|
6705
8160
|
return true;
|
|
6706
8161
|
}
|
|
6707
8162
|
/** Make the row on screen agree with `_expanded` for its key. */
|
|
6708
|
-
_syncDetailOnScreen(tr,
|
|
6709
|
-
const open = this._expanded.has(
|
|
8163
|
+
_syncDetailOnScreen(tr, key4) {
|
|
8164
|
+
const open = this._expanded.has(key4);
|
|
6710
8165
|
const btn = tr.querySelector(":scope > td > .twm-dt-detail-toggle");
|
|
6711
8166
|
if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false");
|
|
6712
8167
|
if (open && !tr.__detail && tr.parentNode) {
|
|
6713
8168
|
const ref = tr.nextSibling;
|
|
6714
|
-
for (const p of this._buildDetailPieces(tr,
|
|
8169
|
+
for (const p of this._buildDetailPieces(tr, key4)) tr.parentNode.insertBefore(p, ref);
|
|
6715
8170
|
} else if (!open && tr.__detail) {
|
|
6716
8171
|
this._removeDetailPieces(tr);
|
|
6717
8172
|
}
|
|
@@ -6725,9 +8180,9 @@ var DataTable = class {
|
|
|
6725
8180
|
* measured height open, three rows until it has been measured. */
|
|
6726
8181
|
_detailHeightOf(row, rowIdx, base) {
|
|
6727
8182
|
if (!this._rowHasDetail(row, rowIdx)) return 0;
|
|
6728
|
-
const
|
|
6729
|
-
if (
|
|
6730
|
-
const h = this._detailHeights.get(
|
|
8183
|
+
const key4 = this._rowKey(row, rowIdx);
|
|
8184
|
+
if (key4 === null || !this._expanded.has(key4)) return 0;
|
|
8185
|
+
const h = this._detailHeights.get(key4);
|
|
6731
8186
|
return h > 0 ? h : base * 3;
|
|
6732
8187
|
}
|
|
6733
8188
|
/** Measure the open details among `trs` (client pixels, the zoom divided
|
|
@@ -6750,9 +8205,9 @@ var DataTable = class {
|
|
|
6750
8205
|
if (!d || !d.isConnected) continue;
|
|
6751
8206
|
const h = d.getBoundingClientRect().height / zoom;
|
|
6752
8207
|
if (!(h > 0)) continue;
|
|
6753
|
-
const
|
|
6754
|
-
if (Math.abs((this._detailHeights.get(
|
|
6755
|
-
this._detailHeights.set(
|
|
8208
|
+
const key4 = this._rowKey(tr.__row, tr.__rowIndex);
|
|
8209
|
+
if (Math.abs((this._detailHeights.get(key4) || 0) - h) > 0.5) {
|
|
8210
|
+
this._detailHeights.set(key4, h);
|
|
6756
8211
|
changed = true;
|
|
6757
8212
|
}
|
|
6758
8213
|
}
|
|
@@ -6767,9 +8222,9 @@ var DataTable = class {
|
|
|
6767
8222
|
* @param {boolean} [open=true]
|
|
6768
8223
|
* @returns {boolean} whether anything changed
|
|
6769
8224
|
*/
|
|
6770
|
-
setRowExpanded(
|
|
6771
|
-
if (
|
|
6772
|
-
const k = String(
|
|
8225
|
+
setRowExpanded(key4, open = true) {
|
|
8226
|
+
if (key4 == null) return false;
|
|
8227
|
+
const k = String(key4);
|
|
6773
8228
|
if (this._expanded.has(k) === !!open) return false;
|
|
6774
8229
|
if (open) this._expanded.add(k);
|
|
6775
8230
|
else this._expanded.delete(k);
|
|
@@ -6790,8 +8245,8 @@ var DataTable = class {
|
|
|
6790
8245
|
return true;
|
|
6791
8246
|
}
|
|
6792
8247
|
/** Whether the row whose key is `key` has its detail open. */
|
|
6793
|
-
isRowExpanded(
|
|
6794
|
-
return
|
|
8248
|
+
isRowExpanded(key4) {
|
|
8249
|
+
return key4 != null && this._expanded.has(String(key4));
|
|
6795
8250
|
}
|
|
6796
8251
|
/** The keys of the rows whose detail is open, as text. */
|
|
6797
8252
|
getExpandedRows() {
|
|
@@ -6802,12 +8257,12 @@ var DataTable = class {
|
|
|
6802
8257
|
for (const k of [...this._expanded]) this.setRowExpanded(k, false);
|
|
6803
8258
|
}
|
|
6804
8259
|
/** The `<tr>` on screen for a key, or null. */
|
|
6805
|
-
_rowElByKey(
|
|
8260
|
+
_rowElByKey(key4) {
|
|
6806
8261
|
const tbody = this._tbodyEl;
|
|
6807
8262
|
if (!tbody) return null;
|
|
6808
8263
|
for (const tr of tbody.children) {
|
|
6809
8264
|
if (tr.__rowIndex === void 0) continue;
|
|
6810
|
-
if (this._rowKey(tr.__row, tr.__rowIndex) ===
|
|
8265
|
+
if (this._rowKey(tr.__row, tr.__rowIndex) === key4) return tr;
|
|
6811
8266
|
}
|
|
6812
8267
|
return null;
|
|
6813
8268
|
}
|
|
@@ -7736,18 +9191,18 @@ var DataTable = class {
|
|
|
7736
9191
|
/** A row's key, as text, or null: `getRowKey`, else its original index. */
|
|
7737
9192
|
_rowKey(row, rowIdx) {
|
|
7738
9193
|
const fn = this.config.getRowKey;
|
|
7739
|
-
let
|
|
9194
|
+
let key4;
|
|
7740
9195
|
if (typeof fn === "function") {
|
|
7741
9196
|
try {
|
|
7742
|
-
|
|
9197
|
+
key4 = fn(row, rowIdx);
|
|
7743
9198
|
} catch (err) {
|
|
7744
9199
|
console.error("[DataTable] getRowKey threw", err);
|
|
7745
|
-
|
|
9200
|
+
key4 = null;
|
|
7746
9201
|
}
|
|
7747
9202
|
} else {
|
|
7748
|
-
|
|
9203
|
+
key4 = rowIdx;
|
|
7749
9204
|
}
|
|
7750
|
-
return
|
|
9205
|
+
return key4 == null ? null : String(key4);
|
|
7751
9206
|
}
|
|
7752
9207
|
/** The gestures `activateOn` names, as a set. */
|
|
7753
9208
|
_activateOn() {
|
|
@@ -8058,8 +9513,8 @@ var DataTable = class {
|
|
|
8058
9513
|
const el = th.firstElementChild;
|
|
8059
9514
|
if (!el) continue;
|
|
8060
9515
|
const cs = getComputedStyle(th);
|
|
8061
|
-
const
|
|
8062
|
-
floors[i] = Math.ceil(el.getBoundingClientRect().width / zoom +
|
|
9516
|
+
const pad3 = (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0) + (parseFloat(cs.borderLeftWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0);
|
|
9517
|
+
floors[i] = Math.ceil(el.getBoundingClientRect().width / zoom + pad3);
|
|
8063
9518
|
natural[i] = Math.max(natural[i], floors[i]);
|
|
8064
9519
|
}
|
|
8065
9520
|
}
|
|
@@ -8812,13 +10267,13 @@ var DataTable = class {
|
|
|
8812
10267
|
const handleKeyDown = (event) => {
|
|
8813
10268
|
const ctrlLike = event.ctrlKey || event.metaKey;
|
|
8814
10269
|
if (ctrlLike && !event.altKey) {
|
|
8815
|
-
const
|
|
8816
|
-
if (
|
|
10270
|
+
const key4 = String(event.key || "").toLowerCase();
|
|
10271
|
+
if (key4 === "a" && selectable === "single") {
|
|
8817
10272
|
event.preventDefault();
|
|
8818
|
-
} else if (
|
|
10273
|
+
} else if (key4 === "a" && selectable) {
|
|
8819
10274
|
event.preventDefault();
|
|
8820
10275
|
this._selectAllShown();
|
|
8821
|
-
} else if (
|
|
10276
|
+
} else if (key4 === "c" && !(event.shiftKey && this._cellGrid)) {
|
|
8822
10277
|
event.preventDefault();
|
|
8823
10278
|
this.copyToClipboard(event.shiftKey ? "csv" : "tsv");
|
|
8824
10279
|
}
|
|
@@ -8956,7 +10411,7 @@ var DataTable = class {
|
|
|
8956
10411
|
menu.style.display = "none";
|
|
8957
10412
|
menu.style.position = "fixed";
|
|
8958
10413
|
menu.style.zIndex = "10001";
|
|
8959
|
-
menu.innerHTML =
|
|
10414
|
+
menu.innerHTML = copyMenuHtml(!!this._cellGrid);
|
|
8960
10415
|
document.body.appendChild(menu);
|
|
8961
10416
|
menu.addEventListener("click", (event) => {
|
|
8962
10417
|
const item = event.target.closest(".twm-context-menu-item");
|
|
@@ -9114,7 +10569,7 @@ var DataTable = class {
|
|
|
9114
10569
|
this._contextMenuCtx = custom ? custom.ctx : null;
|
|
9115
10570
|
if (!custom) {
|
|
9116
10571
|
if (this._contextMenuCustom) {
|
|
9117
|
-
menu.innerHTML =
|
|
10572
|
+
menu.innerHTML = copyMenuHtml(!!this._cellGrid);
|
|
9118
10573
|
this._contextMenuCustom = false;
|
|
9119
10574
|
}
|
|
9120
10575
|
return;
|
|
@@ -9158,7 +10613,7 @@ var DataTable = class {
|
|
|
9158
10613
|
sep.className = "twm-context-menu__separator";
|
|
9159
10614
|
menu.appendChild(sep);
|
|
9160
10615
|
}
|
|
9161
|
-
menu.insertAdjacentHTML("beforeend",
|
|
10616
|
+
menu.insertAdjacentHTML("beforeend", copyMenuHtml(!!this._cellGrid));
|
|
9162
10617
|
}
|
|
9163
10618
|
}
|
|
9164
10619
|
_teardownContextMenu() {
|
|
@@ -9207,9 +10662,9 @@ var DataTable = class {
|
|
|
9207
10662
|
textarea.style.left = "-9999px";
|
|
9208
10663
|
document.body.appendChild(textarea);
|
|
9209
10664
|
textarea.select();
|
|
9210
|
-
const
|
|
10665
|
+
const ok2 = document.execCommand("copy");
|
|
9211
10666
|
document.body.removeChild(textarea);
|
|
9212
|
-
return
|
|
10667
|
+
return ok2;
|
|
9213
10668
|
} catch (_) {
|
|
9214
10669
|
return false;
|
|
9215
10670
|
}
|
|
@@ -9220,8 +10675,26 @@ var DataTable = class {
|
|
|
9220
10675
|
};
|
|
9221
10676
|
|
|
9222
10677
|
export {
|
|
10678
|
+
AutocompleteField,
|
|
9223
10679
|
createTextEditor,
|
|
9224
10680
|
CellEditorHost,
|
|
10681
|
+
EDITOR_MESSAGES,
|
|
10682
|
+
editorTypeOf,
|
|
10683
|
+
numberSeparators,
|
|
10684
|
+
parseNumber,
|
|
10685
|
+
formatNumberForEdit,
|
|
10686
|
+
parseBool,
|
|
10687
|
+
dateOrder,
|
|
10688
|
+
parseDateTime,
|
|
10689
|
+
formatDateTimeForEdit,
|
|
10690
|
+
normalizeOptions,
|
|
10691
|
+
parseChoice,
|
|
10692
|
+
createNumberEditor,
|
|
10693
|
+
createDateTimeEditor,
|
|
10694
|
+
createMultiTextEditor,
|
|
10695
|
+
createBoolEditor,
|
|
10696
|
+
createChoiceEditor,
|
|
10697
|
+
CELL_EDITORS,
|
|
9225
10698
|
CellSelection,
|
|
9226
10699
|
createRafResizeObserver,
|
|
9227
10700
|
ISO_DATE,
|
|
@@ -9239,10 +10712,13 @@ export {
|
|
|
9239
10712
|
StagedHistory,
|
|
9240
10713
|
detectSeries,
|
|
9241
10714
|
projectSeries,
|
|
10715
|
+
tsvCell,
|
|
10716
|
+
toTSV,
|
|
10717
|
+
fromTSV,
|
|
9242
10718
|
attachInlineRenamer,
|
|
9243
10719
|
PAGING_HARD_LIMIT,
|
|
9244
10720
|
PAGING_SOFT_LIMIT,
|
|
9245
10721
|
resolvePaging,
|
|
9246
10722
|
DataTable
|
|
9247
10723
|
};
|
|
9248
|
-
//# sourceMappingURL=chunk-
|
|
10724
|
+
//# sourceMappingURL=chunk-LWV23YVE.js.map
|