flexdesk 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/css/flexdesk.css +33 -5
- package/css/tokens.css +7 -0
- package/dist/charts.js +78 -12
- package/dist/charts.js.map +2 -2
- package/dist/{chunk-5W4EEKSK.js → chunk-3BLIVOFD.js} +3 -3
- package/dist/{chunk-P74F6I6P.js → chunk-4X4SY3CP.js} +4 -4
- package/dist/{chunk-P74F6I6P.js.map → chunk-4X4SY3CP.js.map} +2 -2
- package/dist/{chunk-ZL3VKINP.js → chunk-HL3IIHU3.js} +494 -3
- package/dist/{chunk-ZL3VKINP.js.map → chunk-HL3IIHU3.js.map} +4 -4
- package/dist/{chunk-RKVYIXDC.js → chunk-NFZJ6P42.js} +19 -500
- package/dist/chunk-NFZJ6P42.js.map +7 -0
- package/dist/{chunk-U5GKJUOC.js → chunk-QRBOIUU3.js} +5 -6
- package/dist/chunk-QRBOIUU3.js.map +7 -0
- package/dist/flexdesk.css +33 -5
- package/dist/flow.js +1 -1
- package/dist/git.js +3 -3
- package/dist/tiles.js +4 -4
- package/dist/tokens.css +7 -0
- package/dist/widgets.js +226 -58
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +2 -2
- package/docs/components/charts.md +40 -0
- package/docs/components/chat-shell.md +8 -2
- package/docs/components/filter-builder.md +52 -6
- package/docs/components/forms-and-dialogs.md +8 -0
- package/docs/components/input-fields.md +24 -0
- package/docs/components/menus.md +4 -0
- package/docs/components/shell.md +4 -0
- package/docs/components/workspace-picker.md +46 -2
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +92 -6
- package/src/charts/plot_grid.js +6 -1
- package/src/ui/components/chat_shell.js +7 -1
- package/src/ui/components/context_menu.js +8 -2
- package/src/ui/components/filter_builder.js +53 -28
- package/src/ui/components/filter_model.js +86 -18
- package/src/ui/components/inline_renamer.js +10 -5
- package/src/ui/components/modal.js +25 -8
- package/src/ui/components/workspace_picker.js +125 -27
- package/src/ui/utils/tooltip_service.js +81 -14
- package/dist/chunk-RKVYIXDC.js.map +0 -7
- package/dist/chunk-U5GKJUOC.js.map +0 -7
- /package/dist/{chunk-5W4EEKSK.js.map → chunk-3BLIVOFD.js.map} +0 -0
package/dist/widgets.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
toastInfo,
|
|
24
24
|
toastSuccess,
|
|
25
25
|
toastWarn
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-3BLIVOFD.js";
|
|
27
27
|
import {
|
|
28
28
|
StateMachine
|
|
29
29
|
} from "./chunk-3PHCPZHT.js";
|
|
@@ -54,15 +54,12 @@ import {
|
|
|
54
54
|
import {
|
|
55
55
|
ColorListField,
|
|
56
56
|
SortableList,
|
|
57
|
-
attachInputOptions,
|
|
58
|
-
createCombobox,
|
|
59
|
-
createOptionList,
|
|
60
57
|
hideContextMenu,
|
|
61
58
|
openConfirm,
|
|
62
59
|
openForm,
|
|
63
60
|
openModal,
|
|
64
61
|
showContextMenu
|
|
65
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-NFZJ6P42.js";
|
|
66
63
|
import {
|
|
67
64
|
ACTIONS,
|
|
68
65
|
AutocompleteField,
|
|
@@ -109,7 +106,7 @@ import {
|
|
|
109
106
|
resolvePaging,
|
|
110
107
|
toTSV,
|
|
111
108
|
tsvCell
|
|
112
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-QRBOIUU3.js";
|
|
113
110
|
import {
|
|
114
111
|
ROW_CONTROL_SELECTOR,
|
|
115
112
|
endsTextSelection,
|
|
@@ -122,10 +119,13 @@ import {
|
|
|
122
119
|
DEFAULT_WINDOW_STORAGE_KEY,
|
|
123
120
|
ManagedWindow,
|
|
124
121
|
ToggleField,
|
|
122
|
+
attachInputOptions,
|
|
123
|
+
createCombobox,
|
|
124
|
+
createOptionList,
|
|
125
125
|
fireEvent,
|
|
126
126
|
isHexColor,
|
|
127
127
|
normalizeHex
|
|
128
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-HL3IIHU3.js";
|
|
129
129
|
import {
|
|
130
130
|
choiceSections,
|
|
131
131
|
filterSections,
|
|
@@ -3142,6 +3142,7 @@ function mountChatShell(host, {
|
|
|
3142
3142
|
closeReply("stopped");
|
|
3143
3143
|
machine.cancel();
|
|
3144
3144
|
if (sent) callCancel(request);
|
|
3145
|
+
else runEnding = null;
|
|
3145
3146
|
syncWelcome();
|
|
3146
3147
|
changed();
|
|
3147
3148
|
return true;
|
|
@@ -3213,6 +3214,7 @@ function mountChatShell(host, {
|
|
|
3213
3214
|
const modelBtn = el3("button", "twm-chat-shell__model-btn");
|
|
3214
3215
|
modelBtn.type = "button";
|
|
3215
3216
|
modelBtn.setAttribute("aria-haspopup", "menu");
|
|
3217
|
+
modelBtn.setAttribute("aria-expanded", "false");
|
|
3216
3218
|
const modelDot = el3("span", "twm-chat-shell__status");
|
|
3217
3219
|
const modelLabel = el3("span", "twm-chat-shell__model-label");
|
|
3218
3220
|
modelBtn.append(modelDot, modelLabel, icon2("expand_more", "twm-chat-shell__model-chevron"));
|
|
@@ -3887,6 +3889,25 @@ function createFilterModel({
|
|
|
3887
3889
|
if (value !== void 0) clause.value = value;
|
|
3888
3890
|
return clause;
|
|
3889
3891
|
}
|
|
3892
|
+
function valueFits(field, op, value) {
|
|
3893
|
+
const errors = [];
|
|
3894
|
+
validateClause({ field: field.key, op: op.id, value }, errors, "");
|
|
3895
|
+
if (errors.length) return false;
|
|
3896
|
+
const options = op.multi || field.type === "enum" ? filterOptions(field) : [];
|
|
3897
|
+
return !options.length || arr(value).every((v) => options.some((o) => o.value === v));
|
|
3898
|
+
}
|
|
3899
|
+
function moveClause(clause, key) {
|
|
3900
|
+
const next = newClause(key);
|
|
3901
|
+
if (!next) return null;
|
|
3902
|
+
const field = byKey.get(key);
|
|
3903
|
+
const op = operatorsFor(key).find((o) => o.id === clause.op);
|
|
3904
|
+
if (!op) return next;
|
|
3905
|
+
next.op = op.id;
|
|
3906
|
+
delete next.value;
|
|
3907
|
+
const value = op.arity && valueFits(field, op, clause.value) ? clause.value : defaultValue(field, op);
|
|
3908
|
+
if (value !== void 0) next.value = value;
|
|
3909
|
+
return next;
|
|
3910
|
+
}
|
|
3890
3911
|
function compileClause(clause) {
|
|
3891
3912
|
const field = byKey.get(clause?.field);
|
|
3892
3913
|
if (!field) {
|
|
@@ -4066,26 +4087,30 @@ function createFilterModel({
|
|
|
4066
4087
|
const [a, b] = arr(value);
|
|
4067
4088
|
return `${shown(a)} and ${shown(b)}`;
|
|
4068
4089
|
}
|
|
4069
|
-
return shown(
|
|
4090
|
+
return Array.isArray(value) ? value.map(shown).join(", ") : shown(value);
|
|
4070
4091
|
}
|
|
4071
|
-
function describeNode(node2, depth,
|
|
4092
|
+
function describeNode(node2, depth, opts) {
|
|
4072
4093
|
if (!node2) return "";
|
|
4073
4094
|
if (node2.kind === "clause") {
|
|
4074
4095
|
const field = byKey.get(node2.field);
|
|
4075
|
-
|
|
4076
|
-
const
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
return val ? `${field.label} ${op.label} ${val}` : `${field.label} ${op.label}`;
|
|
4096
|
+
const op = catalogue.get(node2.op) ?? { label: node2.op ?? "", arity: 1 };
|
|
4097
|
+
const subject = field ? field.label : `\u26A0 unknown field \u201C${unknownName(String(node2.field ?? ""))}\u201D`;
|
|
4098
|
+
const value = describeValue(field, op, node2.value, field ? opts.formatValue : null);
|
|
4099
|
+
return [subject, op.label, value].filter(Boolean).join(" ");
|
|
4080
4100
|
}
|
|
4081
4101
|
if (node2.kind !== "group") return `\u26A0 ${JSON.stringify(node2.node ?? node2)}`;
|
|
4082
|
-
const parts = (Array.isArray(node2.children) ? node2.children : []).map((c) => describeNode(c, depth + 1,
|
|
4102
|
+
const parts = (Array.isArray(node2.children) ? node2.children : []).map((c) => describeNode(c, depth + 1, opts)).filter(Boolean);
|
|
4083
4103
|
if (!parts.length) return emptyMatches(node2) !== !!node2.negate ? "everything" : "nothing";
|
|
4084
4104
|
const joined = parts.join(String(node2.op || "AND").toUpperCase() === "OR" ? " OR " : " AND ");
|
|
4105
|
+
const many = parts.length > 1;
|
|
4106
|
+
if (opts.groupBrackets) {
|
|
4107
|
+
const group = many ? `(${joined})` : joined;
|
|
4108
|
+
return node2.negate ? `NOT ${group}` : group;
|
|
4109
|
+
}
|
|
4085
4110
|
if (node2.negate) return `NOT (${joined})`;
|
|
4086
|
-
return depth > 0 &&
|
|
4111
|
+
return depth > 0 && many ? `(${joined})` : joined;
|
|
4087
4112
|
}
|
|
4088
|
-
const describeFilter = (expr, { formatValue = null } = {}) => describeNode(expr, 0, formatValue);
|
|
4113
|
+
const describeFilter = (expr, { formatValue = null, groupBrackets = false } = {}) => describeNode(expr, 0, { formatValue, groupBrackets });
|
|
4089
4114
|
function unknownFields(expr) {
|
|
4090
4115
|
const out = /* @__PURE__ */ new Set();
|
|
4091
4116
|
const walk = (n) => {
|
|
@@ -4112,13 +4137,13 @@ function createFilterModel({
|
|
|
4112
4137
|
}
|
|
4113
4138
|
if (op.arity === 0) return;
|
|
4114
4139
|
const say = (what) => errors.push(`${field.label} ${op.label}: ${what}`);
|
|
4115
|
-
if (op.multi) {
|
|
4116
|
-
|
|
4140
|
+
if (op.multi && (!Array.isArray(node2.value) || node2.value.length === 0)) {
|
|
4141
|
+
say("pick at least one value");
|
|
4117
4142
|
return;
|
|
4118
4143
|
}
|
|
4119
|
-
const values = op.arity === 2 ? arr(node2.value).slice(0, 2) : [first(node2.value)];
|
|
4144
|
+
const values = op.multi ? node2.value : op.arity === 2 ? arr(node2.value).slice(0, 2) : [first(node2.value)];
|
|
4120
4145
|
const needed = op.arity === 2 ? 2 : 1;
|
|
4121
|
-
if (values.length < needed || values.some(isBlank)) {
|
|
4146
|
+
if (!op.multi && (values.length < needed || values.some(isBlank))) {
|
|
4122
4147
|
say(needed === 2 ? "needs two values" : "needs a value");
|
|
4123
4148
|
return;
|
|
4124
4149
|
}
|
|
@@ -4154,6 +4179,24 @@ function createFilterModel({
|
|
|
4154
4179
|
validateNode(expr, errors, "expr");
|
|
4155
4180
|
return { ok: errors.length === 0, errors };
|
|
4156
4181
|
}
|
|
4182
|
+
function partialRanges(expr) {
|
|
4183
|
+
const said = [];
|
|
4184
|
+
const walk = (node2) => {
|
|
4185
|
+
if (node2?.kind === "group") {
|
|
4186
|
+
(Array.isArray(node2.children) ? node2.children : []).forEach(walk);
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
if (node2?.kind !== "clause") return;
|
|
4190
|
+
const field = byKey.get(node2.field);
|
|
4191
|
+
const op = catalogue.get(node2.op);
|
|
4192
|
+
if (!field || op?.arity !== 2 || op.multi) return;
|
|
4193
|
+
const ends = arr(node2.value).slice(0, 2);
|
|
4194
|
+
const filled = ends.filter((v) => !isBlank(v)).length;
|
|
4195
|
+
if (filled === 1) said.push(`${field.label} ${op.label}: needs two values`);
|
|
4196
|
+
};
|
|
4197
|
+
walk(expr);
|
|
4198
|
+
return said;
|
|
4199
|
+
}
|
|
4157
4200
|
function prune(node2) {
|
|
4158
4201
|
if (!node2 || typeof node2 !== "object") return null;
|
|
4159
4202
|
if (node2.kind === "clause") {
|
|
@@ -4177,6 +4220,7 @@ function createFilterModel({
|
|
|
4177
4220
|
operatorById,
|
|
4178
4221
|
defaultValue,
|
|
4179
4222
|
newClause,
|
|
4223
|
+
moveClause,
|
|
4180
4224
|
emptyMatches,
|
|
4181
4225
|
unknownName,
|
|
4182
4226
|
matcherFor,
|
|
@@ -4184,7 +4228,8 @@ function createFilterModel({
|
|
|
4184
4228
|
describeFilter,
|
|
4185
4229
|
validateFilter,
|
|
4186
4230
|
unknownFields,
|
|
4187
|
-
pruneFilter
|
|
4231
|
+
pruneFilter,
|
|
4232
|
+
partialRanges
|
|
4188
4233
|
});
|
|
4189
4234
|
}
|
|
4190
4235
|
|
|
@@ -4532,18 +4577,21 @@ function createFilterBuilder(host, { model, value = null, onChange = null } = {}
|
|
|
4532
4577
|
function operatorChoices(clause) {
|
|
4533
4578
|
const offered = model.operatorsFor(clause.field);
|
|
4534
4579
|
const held = model.operatorById(clause.op);
|
|
4535
|
-
const list = held &&
|
|
4580
|
+
const list = held && !offered.includes(held) ? [...offered, held] : offered;
|
|
4536
4581
|
return list.map((op) => ({ value: op.id, label: op.label }));
|
|
4537
4582
|
}
|
|
4538
4583
|
function clauseEl(clause) {
|
|
4539
4584
|
const row = el("div", "twm-filter-builder__row");
|
|
4585
|
+
const known = !!model.fieldByKey(clause.field);
|
|
4586
|
+
const unknownWords = `\u26A0 unknown field \u201C${model.unknownName(String(clause.field ?? ""))}\u201D`;
|
|
4540
4587
|
const fieldSelect = selectBox(fieldChoices(clause.field), clause.field, {
|
|
4541
4588
|
label: "Field",
|
|
4542
|
-
className: `${SELECT_CLASS} twm-filter-builder__select--field`,
|
|
4543
|
-
notFound: () =>
|
|
4589
|
+
className: `${SELECT_CLASS} twm-filter-builder__select--field${known ? "" : " twm-filter-builder__select--unknown"}`,
|
|
4590
|
+
notFound: () => unknownWords
|
|
4544
4591
|
});
|
|
4592
|
+
if (!known) fieldSelect.box.title = unknownWords;
|
|
4545
4593
|
fieldSelect.box.addEventListener("change", () => {
|
|
4546
|
-
const next = model.
|
|
4594
|
+
const next = model.moveClause(clause, fieldSelect.valueOf());
|
|
4547
4595
|
for (const key of ["op", "value"]) delete clause[key];
|
|
4548
4596
|
Object.assign(clause, next);
|
|
4549
4597
|
restructure(clause, "field");
|
|
@@ -4581,7 +4629,7 @@ function createFilterBuilder(host, { model, value = null, onChange = null } = {}
|
|
|
4581
4629
|
function drawValue(clause, cellEl, cell, drawCell) {
|
|
4582
4630
|
const field = model.fieldByKey(clause.field);
|
|
4583
4631
|
const op = model.operatorById(clause.op);
|
|
4584
|
-
if (!field || !op) {
|
|
4632
|
+
if (!field || !op || op.arity && !model.operatorsFor(clause.field).includes(op)) {
|
|
4585
4633
|
if (clause.value !== void 0) {
|
|
4586
4634
|
cellEl.appendChild(el("span", "twm-filter-builder__carried", JSON.stringify(clause.value)));
|
|
4587
4635
|
}
|
|
@@ -4635,6 +4683,7 @@ function createFilterBuilder(host, { model, value = null, onChange = null } = {}
|
|
|
4635
4683
|
if (numeric) {
|
|
4636
4684
|
input.type = "number";
|
|
4637
4685
|
input.step = "any";
|
|
4686
|
+
if (op.input === "days") input.min = "0";
|
|
4638
4687
|
} else if (field.type === "date") {
|
|
4639
4688
|
input.type = field.time ? "datetime-local" : "date";
|
|
4640
4689
|
} else {
|
|
@@ -4814,6 +4863,7 @@ async function openFilterDialog({
|
|
|
4814
4863
|
requireValid = true,
|
|
4815
4864
|
prune = false,
|
|
4816
4865
|
confirmDiscard = false,
|
|
4866
|
+
dismissOnBackdrop = false,
|
|
4817
4867
|
submitLabel = "Apply",
|
|
4818
4868
|
onSubmit = null,
|
|
4819
4869
|
clearLabel = null,
|
|
@@ -4822,10 +4872,11 @@ async function openFilterDialog({
|
|
|
4822
4872
|
height = 560
|
|
4823
4873
|
} = {}) {
|
|
4824
4874
|
if (!model) throw new Error("[FilterDialog] needs a filter model");
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
if (
|
|
4875
|
+
let start = asRoot(value);
|
|
4876
|
+
const seeded = seedField ? model.newClause(seedField) : null;
|
|
4877
|
+
if (seeded) {
|
|
4878
|
+
if (start.negate) start = { kind: "group", op: "AND", children: [start, seeded] };
|
|
4879
|
+
else start.children.push(seeded);
|
|
4829
4880
|
}
|
|
4830
4881
|
const content = el("div", "twm-filter-dialog");
|
|
4831
4882
|
if (head) content.appendChild(head);
|
|
@@ -4895,9 +4946,12 @@ async function openFilterDialog({
|
|
|
4895
4946
|
const expr = builder.getValue();
|
|
4896
4947
|
return prune ? model.pruneFilter(expr) : expr;
|
|
4897
4948
|
}
|
|
4898
|
-
const
|
|
4949
|
+
const refusals = (expr) => {
|
|
4950
|
+
if (!expr) return [];
|
|
4951
|
+
return requireValid ? model.validateFilter(expr).errors : model.partialRanges(expr);
|
|
4952
|
+
};
|
|
4899
4953
|
const nameMissing = () => !!nameInput && name.required !== false && !nameInput.value.trim();
|
|
4900
|
-
const canSubmit = () => !busy && !nameMissing() &&
|
|
4954
|
+
const canSubmit = () => !busy && !nameMissing() && !refusals(current()).length;
|
|
4901
4955
|
function refresh() {
|
|
4902
4956
|
if (!controls) return;
|
|
4903
4957
|
for (const { value: value2 } of modalActions) {
|
|
@@ -4914,11 +4968,11 @@ async function openFilterDialog({
|
|
|
4914
4968
|
}
|
|
4915
4969
|
function drawPreview() {
|
|
4916
4970
|
const expr = current();
|
|
4917
|
-
const
|
|
4971
|
+
const errors = refusals(expr);
|
|
4918
4972
|
previewRun?.abort();
|
|
4919
4973
|
previewRun = null;
|
|
4920
|
-
const blocked =
|
|
4921
|
-
showErrors(
|
|
4974
|
+
const blocked = errors.length > 0;
|
|
4975
|
+
showErrors(errors);
|
|
4922
4976
|
if (!preview) return;
|
|
4923
4977
|
slot.hidden = blocked;
|
|
4924
4978
|
if (blocked) {
|
|
@@ -4954,11 +5008,15 @@ async function openFilterDialog({
|
|
|
4954
5008
|
showFailure("");
|
|
4955
5009
|
busy = true;
|
|
4956
5010
|
try {
|
|
4957
|
-
if (action.confirm && !await openConfirm(action.confirm)) return;
|
|
4958
|
-
refresh();
|
|
4959
5011
|
const result = { action: action.value, expr: action.value === CLEAR ? null : current() };
|
|
4960
5012
|
if (nameInput) result.name = nameInput.value.trim();
|
|
4961
5013
|
if (picker) result.icon = picker.value();
|
|
5014
|
+
if (action.confirm) {
|
|
5015
|
+
const { message } = action.confirm;
|
|
5016
|
+
const asked = typeof message === "function" ? { ...action.confirm, message: message(result) } : action.confirm;
|
|
5017
|
+
if (!await openConfirm(asked)) return;
|
|
5018
|
+
}
|
|
5019
|
+
refresh();
|
|
4962
5020
|
await action.run?.(result);
|
|
4963
5021
|
close(result);
|
|
4964
5022
|
} catch (err) {
|
|
@@ -4999,9 +5057,12 @@ async function openFilterDialog({
|
|
|
4999
5057
|
height,
|
|
5000
5058
|
maximizable: true,
|
|
5001
5059
|
beforeClose: mayDiscard,
|
|
5060
|
+
dismissOnBackdrop,
|
|
5002
5061
|
onMount: (_body, modalControls) => {
|
|
5003
5062
|
controls = modalControls;
|
|
5004
5063
|
refresh();
|
|
5064
|
+
nameInput?.focus();
|
|
5065
|
+
nameInput?.select();
|
|
5005
5066
|
}
|
|
5006
5067
|
});
|
|
5007
5068
|
drawPreview();
|
|
@@ -9198,8 +9259,11 @@ function keyedChildren(container, build, update) {
|
|
|
9198
9259
|
if (!next.has(key)) entry.node.remove();
|
|
9199
9260
|
}
|
|
9200
9261
|
drawn = next;
|
|
9262
|
+
const focused = document.activeElement;
|
|
9201
9263
|
nodes.forEach((node2, index) => {
|
|
9202
|
-
if (
|
|
9264
|
+
if (node2.contains(focused)) {
|
|
9265
|
+
while (container.children[index] !== node2) container.append(container.children[index]);
|
|
9266
|
+
} else if (container.children[index] !== node2) {
|
|
9203
9267
|
container.insertBefore(node2, container.children[index] || null);
|
|
9204
9268
|
}
|
|
9205
9269
|
});
|
|
@@ -9375,6 +9439,7 @@ function createPickerSwitcher({
|
|
|
9375
9439
|
let activeKey = null;
|
|
9376
9440
|
let activeNode = null;
|
|
9377
9441
|
let moved = false;
|
|
9442
|
+
const rovingKeys = {};
|
|
9378
9443
|
let nextEntryId = 0;
|
|
9379
9444
|
const kept = /* @__PURE__ */ new Map();
|
|
9380
9445
|
const idOf = (item) => String(describe(item, { current: false }).id);
|
|
@@ -9444,6 +9509,7 @@ function createPickerSwitcher({
|
|
|
9444
9509
|
const node2 = part("div", "twm-wspicker__row");
|
|
9445
9510
|
node2.id = `${uid}-row-${++nextEntryId}`;
|
|
9446
9511
|
node2.setAttribute("role", "option");
|
|
9512
|
+
node2.tabIndex = -1;
|
|
9447
9513
|
const icon2 = glyph("", "twm-wspicker__row-icon");
|
|
9448
9514
|
const text = part("span", "twm-wspicker__row-text");
|
|
9449
9515
|
const name = part("span", "twm-wspicker__row-name");
|
|
@@ -9483,6 +9549,7 @@ function createPickerSwitcher({
|
|
|
9483
9549
|
const disabled = !!model.disabled;
|
|
9484
9550
|
paintRow(entry, { ...model, disabled, isCurrentRow: isCurrent(item) });
|
|
9485
9551
|
entry.run = disabled ? null : () => pick(item);
|
|
9552
|
+
if (disabled) entry.node.tabIndex = -1;
|
|
9486
9553
|
const removable = !!onRemove && model.removable !== false;
|
|
9487
9554
|
entry.remove = removable ? () => removeItem(item) : null;
|
|
9488
9555
|
show(entry.bin, removable);
|
|
@@ -9594,6 +9661,15 @@ function createPickerSwitcher({
|
|
|
9594
9661
|
}
|
|
9595
9662
|
for (const [key, entry] of drawnFoot) next.push({ key: `foot:${key}`, node: entry.node, run: entry.run });
|
|
9596
9663
|
stops = next;
|
|
9664
|
+
syncTabStops();
|
|
9665
|
+
}
|
|
9666
|
+
function syncTabStops() {
|
|
9667
|
+
const currentKey = current == null ? null : `row:${idOf(current)}`;
|
|
9668
|
+
for (const prefix of ["row:", "action:"]) {
|
|
9669
|
+
const rows = stops.filter((s) => s.key.startsWith(prefix));
|
|
9670
|
+
const chosen = rows.find((s) => s.key === rovingKeys[prefix]) ?? rows.find((s) => s.key === currentKey) ?? rows[0];
|
|
9671
|
+
for (const stop of rows) stop.node.tabIndex = stop === chosen ? 0 : -1;
|
|
9672
|
+
}
|
|
9597
9673
|
}
|
|
9598
9674
|
function paintActive() {
|
|
9599
9675
|
const stop = stops.find((s) => s.key === activeKey) ?? null;
|
|
@@ -9616,19 +9692,29 @@ function createPickerSwitcher({
|
|
|
9616
9692
|
}
|
|
9617
9693
|
if (!moved || !stops.some((s) => s.key === activeKey)) {
|
|
9618
9694
|
reveal = moved;
|
|
9695
|
+
const at = document.activeElement;
|
|
9696
|
+
if (moved && isOpen && (at === document.body || panel.contains(at) && at !== input)) input.focus();
|
|
9619
9697
|
moved = false;
|
|
9620
9698
|
activeKey = stops.find((s) => s.primary)?.key ?? null;
|
|
9621
9699
|
}
|
|
9622
9700
|
paintActive();
|
|
9623
9701
|
if (reveal) revealActive();
|
|
9624
9702
|
}
|
|
9625
|
-
function
|
|
9626
|
-
|
|
9627
|
-
const at = stops.findIndex((s) => s.key === activeKey);
|
|
9628
|
-
const next = at === -1 ? step > 0 ? 0 : stops.length - 1 : (at + step + stops.length) % stops.length;
|
|
9629
|
-
activeKey = stops[next].key;
|
|
9703
|
+
function focusStop(stop) {
|
|
9704
|
+
activeKey = stop.key;
|
|
9630
9705
|
moved = true;
|
|
9706
|
+
stop.node.focus({ preventScroll: true });
|
|
9631
9707
|
paintActive();
|
|
9708
|
+
}
|
|
9709
|
+
function move(step) {
|
|
9710
|
+
const walk = [input, ...stops.map((s) => s.node)];
|
|
9711
|
+
const from = document.activeElement === input && moved ? activeNode : document.activeElement;
|
|
9712
|
+
const next = walk[(walk.indexOf(from) + step + walk.length) % walk.length];
|
|
9713
|
+
if (next === input) {
|
|
9714
|
+
input.focus();
|
|
9715
|
+
return;
|
|
9716
|
+
}
|
|
9717
|
+
focusStop(stops.find((s) => s.node === next));
|
|
9632
9718
|
revealActive();
|
|
9633
9719
|
}
|
|
9634
9720
|
function render() {
|
|
@@ -9652,7 +9738,7 @@ function createPickerSwitcher({
|
|
|
9652
9738
|
if (remaining > 0) {
|
|
9653
9739
|
setText2(moreLabel, labels.loadMore({ next: Math.min(pageSize, remaining), shown: items.length, total }));
|
|
9654
9740
|
}
|
|
9655
|
-
more.disabled
|
|
9741
|
+
more.setAttribute("aria-disabled", String(loading || loadingMore));
|
|
9656
9742
|
collectStops(rowEntries, actionEntries, shown);
|
|
9657
9743
|
settleActive();
|
|
9658
9744
|
}
|
|
@@ -9745,7 +9831,11 @@ function createPickerSwitcher({
|
|
|
9745
9831
|
}
|
|
9746
9832
|
}
|
|
9747
9833
|
renderEverything();
|
|
9748
|
-
if (
|
|
9834
|
+
if (stayed) {
|
|
9835
|
+
const stop = stops.find((s) => s.key === activeKey);
|
|
9836
|
+
if (stop) focusStop(stop);
|
|
9837
|
+
if (byKey) revealActive();
|
|
9838
|
+
}
|
|
9749
9839
|
}
|
|
9750
9840
|
more.addEventListener("click", () => loadMore());
|
|
9751
9841
|
function setBusy(waiting) {
|
|
@@ -9758,6 +9848,7 @@ function createPickerSwitcher({
|
|
|
9758
9848
|
const removedKey = `row:${idOf(item)}`;
|
|
9759
9849
|
const at = stops.findIndex((s) => s.key === removedKey);
|
|
9760
9850
|
const following = stops.slice(at + 1).find((s) => s.key.startsWith("row:")) ?? stops.slice(0, Math.max(at, 0)).reverse().find((s) => s.key.startsWith("row:"));
|
|
9851
|
+
const fromRow = stops[at]?.node === document.activeElement;
|
|
9761
9852
|
setBusy(true);
|
|
9762
9853
|
let outcome;
|
|
9763
9854
|
try {
|
|
@@ -9781,6 +9872,11 @@ function createPickerSwitcher({
|
|
|
9781
9872
|
moved = true;
|
|
9782
9873
|
}
|
|
9783
9874
|
ask(remote ? answered : "");
|
|
9875
|
+
if (fromRow && following) {
|
|
9876
|
+
focusStop(following);
|
|
9877
|
+
revealActive();
|
|
9878
|
+
return;
|
|
9879
|
+
}
|
|
9784
9880
|
}
|
|
9785
9881
|
input.focus();
|
|
9786
9882
|
}
|
|
@@ -9847,6 +9943,8 @@ function createPickerSwitcher({
|
|
|
9847
9943
|
pickFirst = false;
|
|
9848
9944
|
moved = false;
|
|
9849
9945
|
activeKey = null;
|
|
9946
|
+
delete rovingKeys["row:"];
|
|
9947
|
+
delete rovingKeys["action:"];
|
|
9850
9948
|
const last = kept.get(cacheKey());
|
|
9851
9949
|
items = last ? [...last.items] : [];
|
|
9852
9950
|
total = last ? last.total : null;
|
|
@@ -9902,6 +10000,24 @@ function createPickerSwitcher({
|
|
|
9902
10000
|
panel.addEventListener("mousedown", (event) => {
|
|
9903
10001
|
if (event.target !== input) event.preventDefault();
|
|
9904
10002
|
});
|
|
10003
|
+
panel.addEventListener("focusin", ({ target, relatedTarget }) => {
|
|
10004
|
+
if (target === input) {
|
|
10005
|
+
if (panel.contains(relatedTarget)) {
|
|
10006
|
+
moved = false;
|
|
10007
|
+
settleActive();
|
|
10008
|
+
}
|
|
10009
|
+
return;
|
|
10010
|
+
}
|
|
10011
|
+
const stop = stops.find((s) => s.node === target);
|
|
10012
|
+
if (!stop) return;
|
|
10013
|
+
activeKey = stop.key;
|
|
10014
|
+
moved = true;
|
|
10015
|
+
selectId = null;
|
|
10016
|
+
const prefix = ["row:", "action:"].find((p) => stop.key.startsWith(p));
|
|
10017
|
+
if (prefix) rovingKeys[prefix] = stop.key;
|
|
10018
|
+
syncTabStops();
|
|
10019
|
+
paintActive();
|
|
10020
|
+
});
|
|
9905
10021
|
input.addEventListener("input", () => {
|
|
9906
10022
|
moved = false;
|
|
9907
10023
|
scroller.scrollTop = 0;
|
|
@@ -9922,21 +10038,27 @@ function createPickerSwitcher({
|
|
|
9922
10038
|
event.preventDefault();
|
|
9923
10039
|
event.stopPropagation();
|
|
9924
10040
|
};
|
|
10041
|
+
const stop = stops.find((s) => s.node === target);
|
|
9925
10042
|
if (key === "Escape") {
|
|
9926
10043
|
handled();
|
|
9927
10044
|
close({ restoreFocus: true });
|
|
9928
10045
|
} else if (key === "ArrowDown" || key === "ArrowUp") {
|
|
9929
10046
|
handled();
|
|
9930
|
-
if (document.activeElement !== input) input.focus();
|
|
9931
10047
|
move(key === "ArrowDown" ? 1 : -1);
|
|
9932
10048
|
} else if (key === "Enter" && target === input) {
|
|
9933
10049
|
handled();
|
|
9934
10050
|
enter();
|
|
9935
|
-
} else if (key === "
|
|
10051
|
+
} else if ((key === "Enter" || key === " ") && stop) {
|
|
10052
|
+
handled();
|
|
10053
|
+
stop.run();
|
|
10054
|
+
} else if (key === "Delete" && shiftKey) {
|
|
9936
10055
|
handled();
|
|
9937
10056
|
stops.find((s) => s.key === activeKey)?.remove?.();
|
|
9938
10057
|
} else if (key === "Backspace" && target !== input) {
|
|
9939
10058
|
handled();
|
|
10059
|
+
} else if (target !== input && key.length === 1 && key !== " " && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
10060
|
+
event.stopPropagation();
|
|
10061
|
+
input.focus();
|
|
9940
10062
|
}
|
|
9941
10063
|
});
|
|
9942
10064
|
panel.addEventListener("focusout", (event) => {
|
|
@@ -11460,6 +11582,8 @@ var currentTarget = null;
|
|
|
11460
11582
|
var listenersAttached = false;
|
|
11461
11583
|
var mouseCheckInterval = null;
|
|
11462
11584
|
var lastMousePosition = { x: 0, y: 0 };
|
|
11585
|
+
var focusTarget = null;
|
|
11586
|
+
var byKeyboard = false;
|
|
11463
11587
|
function createTooltipElement() {
|
|
11464
11588
|
if (tooltipElement) return tooltipElement;
|
|
11465
11589
|
tooltipElement = document.createElement("div");
|
|
@@ -11538,11 +11662,12 @@ function showTooltip(target) {
|
|
|
11538
11662
|
const hasLatex = target.classList && target.classList.contains("has-latex-tooltip");
|
|
11539
11663
|
const hasPlain = target.classList && target.classList.contains("twm-has-tooltip");
|
|
11540
11664
|
if (!hasLatex && !hasPlain) return;
|
|
11665
|
+
if (!target.getAttribute(hasLatex ? "data-latex-tooltip" : "data-tooltip")) return;
|
|
11666
|
+
stopMouseCheck();
|
|
11541
11667
|
currentTarget = target;
|
|
11542
11668
|
const tooltip = createTooltipElement();
|
|
11543
11669
|
if (hasLatex) {
|
|
11544
11670
|
const latex = target.getAttribute("data-latex-tooltip");
|
|
11545
|
-
if (!latex) return;
|
|
11546
11671
|
try {
|
|
11547
11672
|
if (typeof katex !== "undefined") {
|
|
11548
11673
|
katex.render(latex, tooltip, {
|
|
@@ -11559,7 +11684,6 @@ function showTooltip(target) {
|
|
|
11559
11684
|
}
|
|
11560
11685
|
} else if (hasPlain) {
|
|
11561
11686
|
const text = target.getAttribute("data-tooltip");
|
|
11562
|
-
if (!text) return;
|
|
11563
11687
|
const formattedText = text.replace(/ /g, "\n");
|
|
11564
11688
|
const lines = formattedText.split("\n");
|
|
11565
11689
|
tooltip.innerHTML = "";
|
|
@@ -11578,9 +11702,10 @@ function showTooltip(target) {
|
|
|
11578
11702
|
const widthOverride = target.getAttribute("data-tooltip-max-width");
|
|
11579
11703
|
tooltip.style.maxWidth = widthOverride || "";
|
|
11580
11704
|
requestAnimationFrame(() => {
|
|
11705
|
+
if (currentTarget !== target) return;
|
|
11581
11706
|
positionTooltip(target, tooltip);
|
|
11582
11707
|
tooltip.classList.add("visible");
|
|
11583
|
-
startMouseCheck();
|
|
11708
|
+
if (focusTarget !== target) startMouseCheck();
|
|
11584
11709
|
});
|
|
11585
11710
|
}
|
|
11586
11711
|
function hideTooltip() {
|
|
@@ -11588,15 +11713,34 @@ function hideTooltip() {
|
|
|
11588
11713
|
tooltipElement.classList.remove("visible");
|
|
11589
11714
|
}
|
|
11590
11715
|
currentTarget = null;
|
|
11716
|
+
focusTarget = null;
|
|
11717
|
+
stopMouseCheck();
|
|
11718
|
+
}
|
|
11719
|
+
function stopMouseCheck() {
|
|
11591
11720
|
if (mouseCheckInterval) {
|
|
11592
11721
|
clearInterval(mouseCheckInterval);
|
|
11593
11722
|
mouseCheckInterval = null;
|
|
11594
11723
|
}
|
|
11595
11724
|
}
|
|
11725
|
+
function pointerGone() {
|
|
11726
|
+
const held = focusTarget;
|
|
11727
|
+
if (held && held === currentTarget) return;
|
|
11728
|
+
hideTooltip();
|
|
11729
|
+
if (!held) return;
|
|
11730
|
+
focusTarget = held;
|
|
11731
|
+
showTooltip(held);
|
|
11732
|
+
requestAnimationFrame(() => {
|
|
11733
|
+
if (held === focusTarget && held === currentTarget) followFocus();
|
|
11734
|
+
});
|
|
11735
|
+
}
|
|
11736
|
+
function followFocus() {
|
|
11737
|
+
const { left, top, width, height } = focusTarget.getBoundingClientRect();
|
|
11738
|
+
const inSight = focusTarget.contains(document.elementFromPoint(left + width / 2, top + height / 2));
|
|
11739
|
+
tooltipElement.classList.toggle("visible", inSight);
|
|
11740
|
+
if (inSight) positionTooltip(focusTarget, tooltipElement);
|
|
11741
|
+
}
|
|
11596
11742
|
function startMouseCheck() {
|
|
11597
|
-
|
|
11598
|
-
clearInterval(mouseCheckInterval);
|
|
11599
|
-
}
|
|
11743
|
+
stopMouseCheck();
|
|
11600
11744
|
mouseCheckInterval = setInterval(() => {
|
|
11601
11745
|
if (!currentTarget) {
|
|
11602
11746
|
clearInterval(mouseCheckInterval);
|
|
@@ -11607,10 +11751,10 @@ function startMouseCheck() {
|
|
|
11607
11751
|
if (elementUnderMouse) {
|
|
11608
11752
|
const isStillOnTarget = elementUnderMouse === currentTarget || currentTarget.contains(elementUnderMouse) || elementUnderMouse.closest(".twm-has-tooltip, .has-latex-tooltip") === currentTarget;
|
|
11609
11753
|
if (!isStillOnTarget) {
|
|
11610
|
-
|
|
11754
|
+
pointerGone();
|
|
11611
11755
|
}
|
|
11612
11756
|
} else {
|
|
11613
|
-
|
|
11757
|
+
pointerGone();
|
|
11614
11758
|
}
|
|
11615
11759
|
}, 3e3);
|
|
11616
11760
|
}
|
|
@@ -11636,9 +11780,33 @@ function attachListeners() {
|
|
|
11636
11780
|
if (!(target instanceof Element)) return;
|
|
11637
11781
|
const tooltipTarget = target.closest(".has-latex-tooltip, .twm-has-tooltip");
|
|
11638
11782
|
if (!tooltipTarget) return;
|
|
11639
|
-
|
|
11783
|
+
pointerGone();
|
|
11784
|
+
}, true);
|
|
11785
|
+
window.addEventListener("keydown", (event) => {
|
|
11786
|
+
if (!event.ctrlKey && !event.metaKey && !event.altKey) byKeyboard = true;
|
|
11787
|
+
}, true);
|
|
11788
|
+
window.addEventListener("pointerdown", () => {
|
|
11789
|
+
byKeyboard = false;
|
|
11790
|
+
}, true);
|
|
11791
|
+
document.addEventListener("focusin", (event) => {
|
|
11792
|
+
const target = event.target;
|
|
11793
|
+
if (!(target instanceof Element) || !byKeyboard) return;
|
|
11794
|
+
const tooltipTarget = target.closest(".has-latex-tooltip, .twm-has-tooltip");
|
|
11795
|
+
if (!tooltipTarget) return;
|
|
11796
|
+
focusTarget = tooltipTarget;
|
|
11797
|
+
showTooltip(tooltipTarget);
|
|
11798
|
+
}, true);
|
|
11799
|
+
document.addEventListener("focusout", (event) => {
|
|
11800
|
+
if (!focusTarget) return;
|
|
11801
|
+
const next = event.relatedTarget;
|
|
11802
|
+
if (next instanceof Node && focusTarget.contains(next)) return;
|
|
11803
|
+
if (focusTarget === currentTarget) hideTooltip();
|
|
11804
|
+
else focusTarget = null;
|
|
11805
|
+
}, true);
|
|
11806
|
+
document.addEventListener("scroll", () => {
|
|
11807
|
+
if (focusTarget && focusTarget === currentTarget) followFocus();
|
|
11808
|
+
else pointerGone();
|
|
11640
11809
|
}, true);
|
|
11641
|
-
document.addEventListener("scroll", hideTooltip, true);
|
|
11642
11810
|
document.addEventListener("keydown", (event) => {
|
|
11643
11811
|
if (event.key === "Escape") {
|
|
11644
11812
|
hideTooltip();
|