@syware/jlib 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +11 -4
- package/dist/atoms/jl-app-icon.css_raw.js +3 -0
- package/dist/atoms/jl-app-icon.d.ts +34 -0
- package/dist/atoms/jl-app-icon.js +86 -0
- package/dist/atoms/jl-avatar.js +1 -1
- package/dist/atoms/jl-badge.js +9 -2
- package/dist/atoms/jl-button.d.ts +5 -0
- package/dist/atoms/jl-button.js +5 -2
- package/dist/atoms/jl-chip.js +3 -2
- package/dist/atoms/jl-input.js +5 -4
- package/dist/atoms/jl-link.js +2 -2
- package/dist/atoms/jl-rating.js +4 -5
- package/dist/atoms/jl-select.css_raw.js +1 -1
- package/dist/atoms/jl-select.d.ts +3 -2
- package/dist/atoms/jl-select.js +6 -5
- package/dist/atoms/jl-spinner.js +2 -2
- package/dist/base/jl-form-control.js +2 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -3
- package/dist/lib/app-icon.d.ts +8 -0
- package/dist/lib/app-icon.js +37 -0
- package/dist/lib/dom.d.ts +2 -0
- package/dist/lib/dom.js +3 -1
- package/dist/lib/download.d.ts +10 -0
- package/dist/lib/download.js +62 -0
- package/dist/lib/icons.d.ts +4 -0
- package/dist/lib/icons.js +7 -4
- package/dist/lib/input-list.d.ts +12 -0
- package/dist/lib/input-list.js +14 -0
- package/dist/lib/internals.d.ts +5 -0
- package/dist/lib/internals.js +18 -0
- package/dist/lib/palette.js +6 -0
- package/dist/lib/resize.d.ts +2 -0
- package/dist/lib/resize.js +10 -0
- package/dist/lib/table.d.ts +6 -1
- package/dist/lib/table.js +16 -1
- package/dist/lib/text.d.ts +12 -0
- package/dist/lib/text.js +16 -1
- package/dist/lib/texts.d.ts +97 -0
- package/dist/lib/texts.js +102 -0
- package/dist/lib/time.d.ts +5 -3
- package/dist/lib/time.js +23 -15
- package/dist/lib/tree.d.ts +10 -1
- package/dist/lib/tree.js +37 -3
- package/dist/lib/trigger.js +1 -1
- package/dist/molecules/jl-account-button.css_raw.js +1 -1
- package/dist/molecules/jl-account-button.d.ts +8 -1
- package/dist/molecules/jl-account-button.js +9 -2
- package/dist/molecules/jl-alert.js +3 -2
- package/dist/molecules/jl-breadcrumb.js +3 -3
- package/dist/molecules/jl-calendar.js +5 -5
- package/dist/molecules/jl-chip-group.js +3 -3
- package/dist/molecules/jl-code-block.css_raw.js +1 -1
- package/dist/molecules/jl-code-block.d.ts +5 -0
- package/dist/molecules/jl-code-block.js +5 -2
- package/dist/molecules/jl-combobox.js +6 -7
- package/dist/molecules/jl-copy-button.js +5 -4
- package/dist/molecules/jl-date-picker.js +7 -8
- package/dist/molecules/jl-empty.js +2 -2
- package/dist/molecules/jl-facts.css_raw.js +3 -0
- package/dist/molecules/jl-facts.d.ts +35 -0
- package/dist/molecules/jl-facts.js +64 -0
- package/dist/molecules/jl-file-drop.d.ts +5 -0
- package/dist/molecules/jl-file-drop.js +18 -15
- package/dist/molecules/jl-input-list.css_raw.js +3 -0
- package/dist/molecules/jl-input-list.d.ts +79 -0
- package/dist/molecules/jl-input-list.js +177 -0
- package/dist/molecules/jl-list-item.js +1 -1
- package/dist/molecules/jl-load-more.css_raw.js +3 -0
- package/dist/molecules/jl-load-more.d.ts +41 -0
- package/dist/molecules/jl-load-more.js +62 -0
- package/dist/molecules/jl-load-state.d.ts +4 -1
- package/dist/molecules/jl-load-state.js +20 -12
- package/dist/molecules/jl-menu.js +1 -1
- package/dist/molecules/jl-number-input.js +5 -5
- package/dist/molecules/jl-pagination.js +5 -5
- package/dist/molecules/jl-radio-group.js +2 -2
- package/dist/molecules/jl-search-button.js +5 -5
- package/dist/molecules/jl-search.js +5 -5
- package/dist/molecules/jl-segmented.js +3 -2
- package/dist/molecules/jl-steps.css_raw.js +1 -1
- package/dist/molecules/jl-steps.d.ts +2 -0
- package/dist/molecules/jl-steps.js +30 -14
- package/dist/molecules/jl-tabs.js +3 -2
- package/dist/molecules/jl-tag-input.js +3 -3
- package/dist/molecules/jl-timeline.js +1 -1
- package/dist/molecules/jl-toast.css_raw.js +1 -1
- package/dist/molecules/jl-toast.d.ts +13 -1
- package/dist/molecules/jl-toast.js +23 -3
- package/dist/molecules/jl-tooltip.d.ts +4 -1
- package/dist/molecules/jl-tooltip.js +27 -15
- package/dist/organisms/jl-command-palette.css_raw.js +1 -1
- package/dist/organisms/jl-command-palette.d.ts +14 -0
- package/dist/organisms/jl-command-palette.js +40 -10
- package/dist/organisms/jl-confirm.d.ts +2 -1
- package/dist/organisms/jl-confirm.js +18 -12
- package/dist/organisms/jl-dialog.js +2 -1
- package/dist/organisms/jl-drawer.js +2 -1
- package/dist/organisms/jl-flow.js +13 -13
- package/dist/organisms/jl-navbar.js +6 -6
- package/dist/organisms/jl-side-nav.css_raw.js +1 -1
- package/dist/organisms/jl-side-nav.d.ts +33 -1
- package/dist/organisms/jl-side-nav.js +71 -14
- package/dist/organisms/jl-tab-bar.js +3 -3
- package/dist/organisms/jl-table.css_raw.js +1 -1
- package/dist/organisms/jl-table.d.ts +9 -0
- package/dist/organisms/jl-table.js +36 -6
- package/dist/organisms/jl-toaster.css_raw.js +1 -1
- package/dist/organisms/jl-toaster.d.ts +9 -1
- package/dist/organisms/jl-toaster.js +148 -7
- package/dist/organisms/jl-tree.css_raw.js +1 -1
- package/dist/organisms/jl-tree.d.ts +71 -4
- package/dist/organisms/jl-tree.js +303 -40
- package/dist/styles/tokens.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.d.ts +16 -1
- package/dist/templates/jl-app-shell.js +42 -3
- package/dist/templates/jl-auth-layout.css_raw.js +3 -0
- package/dist/templates/jl-auth-layout.d.ts +35 -0
- package/dist/templates/jl-auth-layout.js +139 -0
- package/dist/templates/jl-page.css_raw.js +1 -1
- package/dist/templates/jl-page.js +3 -3
- package/dist/templates/jl-split-view.css_raw.js +1 -1
- package/dist/templates/jl-split-view.d.ts +21 -1
- package/dist/templates/jl-split-view.js +37 -4
- package/dist/tokens.css +2 -0
- package/dist/types/jl-app-icon.types.d.ts +5 -0
- package/dist/types/jl-command-palette.types.d.ts +7 -0
- package/dist/types/jl-facts.types.d.ts +17 -0
- package/dist/types/jl-side-nav.types.d.ts +2 -0
- package/dist/types/jl-split-view.types.d.ts +2 -0
- package/dist/types/jl-steps.types.d.ts +2 -0
- package/dist/types/jl-table.types.d.ts +9 -2
- package/dist/types/jl-toaster.types.d.ts +42 -1
- package/dist/types/jl-tree.types.d.ts +26 -0
- package/dist/types/texts.types.d.ts +5 -0
- package/package.json +3 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { jsonOf, oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
5
|
+
import jl_facts_default from "./jl-facts.css_raw.js";
|
|
6
|
+
import { css } from "@jadis/core";
|
|
7
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
8
|
+
// src/molecules/jl-facts.tsx
|
|
9
|
+
var FACTS_ORIENTATIONS = ["horizontal", "vertical"];
|
|
10
|
+
var hasValue = (fact) => fact.value !== null && fact.value !== void 0 && fact.value !== "";
|
|
11
|
+
/**
|
|
12
|
+
* The facts about one thing, label and value: an object's size and digest, an order's customer and date. Facts
|
|
13
|
+
* without a value are left out, and an element with none takes no room. Values can be code (monospace, broken
|
|
14
|
+
* anywhere), links, or drawn by code (`renderers`, by a fact's `key`). Narrower than 420 pixels, labels go over
|
|
15
|
+
* their values.
|
|
16
|
+
*/
|
|
17
|
+
var JlFacts = class extends JlElement {
|
|
18
|
+
static selector = "jl-facts";
|
|
19
|
+
items = this.useChange([], () => this.render(), {
|
|
20
|
+
attribute: "items",
|
|
21
|
+
parse: jsonOf([])
|
|
22
|
+
});
|
|
23
|
+
orientation = this.useChange("horizontal", (value) => this.reflect("orientation", value), {
|
|
24
|
+
attribute: "orientation",
|
|
25
|
+
parse: oneOf(FACTS_ORIENTATIONS, "horizontal")
|
|
26
|
+
});
|
|
27
|
+
/** Draws the values of some facts by code, by their `key`: `{ type: (fact) => <JlBadge label={…} /> }`. */
|
|
28
|
+
renderers = this.useChange({}, () => this.render());
|
|
29
|
+
/** The list's name for screen readers. */
|
|
30
|
+
label = this.useChange("", (value) => setOrRemove(this._refs.list, "aria-label", value), { attribute: "label" });
|
|
31
|
+
_refs = this.useRefs((ref) => ({ list: ref("dl") }));
|
|
32
|
+
templateHtml() {
|
|
33
|
+
return /* @__PURE__ */ jsx("dl", {
|
|
34
|
+
class: "facts",
|
|
35
|
+
attrs: { part: "base" }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
templateCss() {
|
|
39
|
+
return css`${BASE_STYLES}${jl_facts_default}`;
|
|
40
|
+
}
|
|
41
|
+
render() {
|
|
42
|
+
const shown = this.items.get().filter(hasValue);
|
|
43
|
+
this._refs.list.replaceChildren(...shown.map((fact) => /* @__PURE__ */ jsxs("div", {
|
|
44
|
+
class: "fact",
|
|
45
|
+
children: [/* @__PURE__ */ jsx("dt", { children: fact.label }), /* @__PURE__ */ jsx("dd", {
|
|
46
|
+
title: fact.hint,
|
|
47
|
+
children: this.valueNode(fact)
|
|
48
|
+
})]
|
|
49
|
+
})));
|
|
50
|
+
this.reflect("empty", shown.length === 0);
|
|
51
|
+
}
|
|
52
|
+
valueNode(fact) {
|
|
53
|
+
const renderer = fact.key === void 0 ? void 0 : this.renderers.get()[fact.key];
|
|
54
|
+
if (renderer !== void 0) return renderer(fact);
|
|
55
|
+
const text = String(fact.value);
|
|
56
|
+
const content = fact.code ? /* @__PURE__ */ jsx("code", { children: text }) : document.createTextNode(text);
|
|
57
|
+
return fact.href === void 0 ? content : /* @__PURE__ */ jsx("a", {
|
|
58
|
+
href: fact.href,
|
|
59
|
+
children: content
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
JlFacts.register();
|
|
64
|
+
export { FACTS_ORIENTATIONS, JlFacts };
|
|
@@ -61,6 +61,11 @@ export declare class JlFileDrop extends JlFormControl {
|
|
|
61
61
|
focus(options?: FocusOptions): void;
|
|
62
62
|
/** Opens the file chooser. */
|
|
63
63
|
browse(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Takes files as a drop would, such as files dropped elsewhere on the page: the ones it accepts join the list (or
|
|
66
|
+
* replace the file there) and fire `change`, the others say why not.
|
|
67
|
+
*/
|
|
68
|
+
add(files: readonly File[]): void;
|
|
64
69
|
/** Takes every file back, and empties the list of refused ones. */
|
|
65
70
|
clear(): void;
|
|
66
71
|
templateHtml(): Node;
|
|
@@ -3,8 +3,9 @@ import { JlFormControl } from "../base/jl-form-control.js";
|
|
|
3
3
|
import { clampedNumber, oneOf } from "../lib/attributes.js";
|
|
4
4
|
import { registerIcons } from "../lib/icons.js";
|
|
5
5
|
import { CONTROL_SIZES } from "../lib/options.js";
|
|
6
|
-
import {
|
|
6
|
+
import { jlText } from "../lib/texts.js";
|
|
7
7
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
8
|
+
import { setOrRemove, uniqueId } from "../lib/dom.js";
|
|
8
9
|
import { pageLocale } from "../lib/locale.js";
|
|
9
10
|
import { checkFiles, describeAccept, formatFileSize, isSameFile } from "../lib/files.js";
|
|
10
11
|
import jl_file_drop_default from "./jl-file-drop.css_raw.js";
|
|
@@ -30,15 +31,10 @@ var ALERT_ICON_SIZE = 14;
|
|
|
30
31
|
var NO_LIMIT = 0;
|
|
31
32
|
var FILES_TYPE = "Files";
|
|
32
33
|
var IMAGE_FAMILY = "image/";
|
|
33
|
-
var VALUE_MISSING = "Choose a file.";
|
|
34
|
-
var DEFAULT_TEXT = {
|
|
35
|
-
multiple: "Drop files here",
|
|
36
|
-
single: "Drop a file here"
|
|
37
|
-
};
|
|
38
34
|
var REASONS = {
|
|
39
|
-
count: () => "
|
|
40
|
-
size: (rules) =>
|
|
41
|
-
type: (rules) =>
|
|
35
|
+
count: () => jlText("fileTooMany"),
|
|
36
|
+
size: (rules) => jlText("fileTooLarge", { size: formatFileSize(rules.maxSize, pageLocale()) }),
|
|
37
|
+
type: (rules) => jlText("fileWrongType", { types: describeAccept(rules.accept) })
|
|
42
38
|
};
|
|
43
39
|
var hasFiles = (event) => event.dataTransfer?.types.includes(FILES_TYPE) ?? false;
|
|
44
40
|
/**
|
|
@@ -114,6 +110,13 @@ var JlFileDrop = class extends JlFormControl {
|
|
|
114
110
|
if (!this.disabled.get()) this._refs.picker.click();
|
|
115
111
|
});
|
|
116
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Takes files as a drop would, such as files dropped elsewhere on the page: the ones it accepts join the list (or
|
|
115
|
+
* replace the file there) and fire `change`, the others say why not.
|
|
116
|
+
*/
|
|
117
|
+
add(files) {
|
|
118
|
+
this.whenRendered(() => this.take([...files]));
|
|
119
|
+
}
|
|
117
120
|
/** Takes every file back, and empties the list of refused ones. */
|
|
118
121
|
clear() {
|
|
119
122
|
this.whenRendered(() => {
|
|
@@ -148,7 +151,7 @@ var JlFileDrop = class extends JlFormControl {
|
|
|
148
151
|
class: "browse",
|
|
149
152
|
type: "button",
|
|
150
153
|
"aria-describedby": this._ids.hint,
|
|
151
|
-
children: "
|
|
154
|
+
children: jlText("browse")
|
|
152
155
|
})
|
|
153
156
|
]
|
|
154
157
|
}),
|
|
@@ -165,7 +168,7 @@ var JlFileDrop = class extends JlFormControl {
|
|
|
165
168
|
}),
|
|
166
169
|
/* @__PURE__ */ jsx("ul", {
|
|
167
170
|
class: "files",
|
|
168
|
-
"aria-label": "
|
|
171
|
+
"aria-label": jlText("chosenFiles"),
|
|
169
172
|
hidden: true
|
|
170
173
|
})
|
|
171
174
|
] });
|
|
@@ -297,7 +300,7 @@ var JlFileDrop = class extends JlFormControl {
|
|
|
297
300
|
editable ? /* @__PURE__ */ jsx("button", {
|
|
298
301
|
class: "file-remove",
|
|
299
302
|
type: "button",
|
|
300
|
-
"aria-label":
|
|
303
|
+
"aria-label": jlText("removeNamed", { name: file.name }),
|
|
301
304
|
"data-index": String(index),
|
|
302
305
|
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
303
306
|
name: "x",
|
|
@@ -327,14 +330,14 @@ var JlFileDrop = class extends JlFormControl {
|
|
|
327
330
|
}
|
|
328
331
|
renderText() {
|
|
329
332
|
const text = this.text.get();
|
|
330
|
-
const fallback = this.multiple.get() ?
|
|
333
|
+
const fallback = jlText(this.multiple.get() ? "dropFiles" : "dropFile");
|
|
331
334
|
this._refs.text.textContent = text === "" ? fallback : text;
|
|
332
335
|
}
|
|
333
336
|
/** What it takes, under the text: `PNG, JPG · up to 5 MB`. */
|
|
334
337
|
renderHint() {
|
|
335
338
|
const types = describeAccept(this.accept.get());
|
|
336
339
|
const maxSize = this.maxSize.get();
|
|
337
|
-
const hint = [types, maxSize === NO_LIMIT ? "" :
|
|
340
|
+
const hint = [types, maxSize === NO_LIMIT ? "" : jlText("fileUpTo", { size: formatFileSize(maxSize, pageLocale()) })].filter((part) => part !== "").join(" · ");
|
|
338
341
|
this._refs.hint.textContent = hint;
|
|
339
342
|
this._refs.hint.hidden = hint === "";
|
|
340
343
|
}
|
|
@@ -351,7 +354,7 @@ var JlFileDrop = class extends JlFormControl {
|
|
|
351
354
|
}
|
|
352
355
|
renderValidity() {
|
|
353
356
|
if (this.required.get() && this._state.files.length === 0) {
|
|
354
|
-
this.internals.setValidity({ valueMissing: true },
|
|
357
|
+
this.internals.setValidity({ valueMissing: true }, jlText("chooseFile"), this._refs.browse);
|
|
355
358
|
return;
|
|
356
359
|
}
|
|
357
360
|
this.internals.setValidity({});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-input-list.css?raw
|
|
2
|
+
var jl_input_list_default = ":host {\n display: block;\n}\n\n.list,\n.rows {\n display: grid;\n gap: var(--jl-space-2);\n}\n\n.row {\n display: flex;\n align-items: start;\n gap: var(--jl-space-1);\n}\n\n.row > :first-child {\n flex: 1;\n min-width: 0;\n}\n\n[hidden] {\n display: none;\n}\n";
|
|
3
|
+
export { jl_input_list_default as default };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { InputType } from '../types/jl-input.types';
|
|
3
|
+
import type { SelectOption } from '../types/jl-select.types';
|
|
4
|
+
/**
|
|
5
|
+
* A field with several values, each in its own row: typed in an input (or a text area when `multiline`), or picked
|
|
6
|
+
* from `options`. People add a row and remove one; `max` caps how many. `values` holds those filled in, in their
|
|
7
|
+
* order, and `change` fires with them. Put it in a `jl-field` for its label, hint and error.
|
|
8
|
+
*/
|
|
9
|
+
export declare class JlInputList extends JlElement {
|
|
10
|
+
static readonly selector = "jl-input-list";
|
|
11
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
12
|
+
change: string[];
|
|
13
|
+
}>>;
|
|
14
|
+
/** The values filled in, in their order; rows left empty are not among them. */
|
|
15
|
+
readonly values: Readonly<Readonly<{
|
|
16
|
+
get(): string[];
|
|
17
|
+
set(setter: string[] | ((prevState: string[]) => string[])): void;
|
|
18
|
+
}>>;
|
|
19
|
+
/** The input of each row: text, a number, a date, a link… */
|
|
20
|
+
readonly type: Readonly<Readonly<{
|
|
21
|
+
get(): InputType;
|
|
22
|
+
set(setter: ((prevState: InputType) => InputType) | InputType): void;
|
|
23
|
+
}>>;
|
|
24
|
+
/** Each value on several lines, in a text area. */
|
|
25
|
+
readonly multiline: Readonly<Readonly<{
|
|
26
|
+
get(): boolean;
|
|
27
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
28
|
+
}>>;
|
|
29
|
+
/** The values to pick from, each row a select; none lets people type. */
|
|
30
|
+
readonly options: Readonly<Readonly<{
|
|
31
|
+
get(): SelectOption[];
|
|
32
|
+
set(setter: SelectOption[] | ((prevState: SelectOption[]) => SelectOption[])): void;
|
|
33
|
+
}>>;
|
|
34
|
+
/** How many values it takes at most; 0 takes any number. */
|
|
35
|
+
readonly max: Readonly<Readonly<{
|
|
36
|
+
get(): number;
|
|
37
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
38
|
+
}>>;
|
|
39
|
+
readonly placeholder: Readonly<Readonly<{
|
|
40
|
+
get(): string;
|
|
41
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
42
|
+
}>>;
|
|
43
|
+
/** The name of each row for screen readers: the field's label. */
|
|
44
|
+
readonly label: Readonly<Readonly<{
|
|
45
|
+
get(): string;
|
|
46
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
47
|
+
}>>;
|
|
48
|
+
readonly disabled: Readonly<Readonly<{
|
|
49
|
+
get(): boolean;
|
|
50
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
51
|
+
}>>;
|
|
52
|
+
/** The add button's text. */
|
|
53
|
+
readonly addLabel: Readonly<Readonly<{
|
|
54
|
+
get(): string;
|
|
55
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
56
|
+
}>>;
|
|
57
|
+
/** The text a screen reader says for each row's remove button. */
|
|
58
|
+
readonly removeLabel: Readonly<Readonly<{
|
|
59
|
+
get(): string;
|
|
60
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
61
|
+
}>>;
|
|
62
|
+
private readonly _state;
|
|
63
|
+
private readonly _refs;
|
|
64
|
+
templateHtml(): Node;
|
|
65
|
+
templateCss(): string;
|
|
66
|
+
onConnect(): void;
|
|
67
|
+
/** Values set from outside start the rows again; those the rows themselves gave change nothing. */
|
|
68
|
+
private follow;
|
|
69
|
+
private renderRows;
|
|
70
|
+
private renderAdd;
|
|
71
|
+
private renderDisabled;
|
|
72
|
+
private row;
|
|
73
|
+
/** The control of a row: a select among the options, a text area, or an input of the type; `onInput` hears it. */
|
|
74
|
+
private control;
|
|
75
|
+
private change;
|
|
76
|
+
private addRow;
|
|
77
|
+
private removeRow;
|
|
78
|
+
private publish;
|
|
79
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { clampedNumber, jsonOf, oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { jlText } from "../lib/texts.js";
|
|
6
|
+
import { JlButton } from "../atoms/jl-button.js";
|
|
7
|
+
import { INPUT_TYPES, JlInput } from "../atoms/jl-input.js";
|
|
8
|
+
import { JlSelect } from "../atoms/jl-select.js";
|
|
9
|
+
import { JlTextarea } from "../atoms/jl-textarea.js";
|
|
10
|
+
import { canAddRow, filledValues, rowsOf, sameValues, withRowAt, withoutRowAt } from "../lib/input-list.js";
|
|
11
|
+
import jl_input_list_default from "./jl-input-list.css_raw.js";
|
|
12
|
+
import { css } from "@jadis/core";
|
|
13
|
+
import { Plus, X } from "lucide";
|
|
14
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
15
|
+
// src/molecules/jl-input-list.tsx
|
|
16
|
+
registerIcons({
|
|
17
|
+
plus: Plus,
|
|
18
|
+
x: X
|
|
19
|
+
});
|
|
20
|
+
var NO_LIMIT = 0;
|
|
21
|
+
var MAX_LIMIT = 1e3;
|
|
22
|
+
/**
|
|
23
|
+
* A field with several values, each in its own row: typed in an input (or a text area when `multiline`), or picked
|
|
24
|
+
* from `options`. People add a row and remove one; `max` caps how many. `values` holds those filled in, in their
|
|
25
|
+
* order, and `change` fires with them. Put it in a `jl-field` for its label, hint and error.
|
|
26
|
+
*/
|
|
27
|
+
var JlInputList = class extends JlElement {
|
|
28
|
+
static selector = "jl-input-list";
|
|
29
|
+
events = this.useEvents();
|
|
30
|
+
/** The values filled in, in their order; rows left empty are not among them. */
|
|
31
|
+
values = this.useChange([], (values) => this.follow(values), {
|
|
32
|
+
attribute: "values",
|
|
33
|
+
parse: jsonOf([])
|
|
34
|
+
});
|
|
35
|
+
/** The input of each row: text, a number, a date, a link… */
|
|
36
|
+
type = this.useChange("text", () => this.renderRows(), {
|
|
37
|
+
attribute: "type",
|
|
38
|
+
parse: oneOf(INPUT_TYPES, "text")
|
|
39
|
+
});
|
|
40
|
+
/** Each value on several lines, in a text area. */
|
|
41
|
+
multiline = this.useChange(false, () => this.renderRows(), { attribute: "multiline" });
|
|
42
|
+
/** The values to pick from, each row a select; none lets people type. */
|
|
43
|
+
options = this.useChange([], () => this.renderRows(), {
|
|
44
|
+
attribute: "options",
|
|
45
|
+
parse: jsonOf([])
|
|
46
|
+
});
|
|
47
|
+
/** How many values it takes at most; 0 takes any number. */
|
|
48
|
+
max = this.useChange(NO_LIMIT, () => this.renderAdd(), {
|
|
49
|
+
attribute: "max",
|
|
50
|
+
parse: clampedNumber(NO_LIMIT, NO_LIMIT, MAX_LIMIT)
|
|
51
|
+
});
|
|
52
|
+
placeholder = this.useChange("", () => this.renderRows(), { attribute: "placeholder" });
|
|
53
|
+
/** The name of each row for screen readers: the field's label. */
|
|
54
|
+
label = this.useChange("", () => this.renderRows(), { attribute: "label" });
|
|
55
|
+
disabled = this.useChange(false, (value) => this.renderDisabled(value), { attribute: "disabled" });
|
|
56
|
+
/** The add button's text. */
|
|
57
|
+
addLabel = this.useChange(jlText("addValue"), () => this.renderAdd(), { attribute: "add-label" });
|
|
58
|
+
/** The text a screen reader says for each row's remove button. */
|
|
59
|
+
removeLabel = this.useChange(jlText("removeValue"), () => this.renderRows(), { attribute: "remove-label" });
|
|
60
|
+
_state = { rows: [""] };
|
|
61
|
+
_refs = this.useRefs((ref) => ({
|
|
62
|
+
add: ref(".add"),
|
|
63
|
+
rows: ref(".rows")
|
|
64
|
+
}));
|
|
65
|
+
templateHtml() {
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
67
|
+
class: "list",
|
|
68
|
+
attrs: { part: "base" },
|
|
69
|
+
children: [/* @__PURE__ */ jsx("div", { class: "rows" }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(JlButton, {
|
|
70
|
+
class: "add",
|
|
71
|
+
variant: "ghost",
|
|
72
|
+
size: "sm",
|
|
73
|
+
icon: "plus"
|
|
74
|
+
}) })]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
templateCss() {
|
|
78
|
+
return css`${BASE_STYLES}${jl_input_list_default}`;
|
|
79
|
+
}
|
|
80
|
+
onConnect() {
|
|
81
|
+
this._refs.add.events.register("press", () => this.addRow());
|
|
82
|
+
}
|
|
83
|
+
/** Values set from outside start the rows again; those the rows themselves gave change nothing. */
|
|
84
|
+
follow(values) {
|
|
85
|
+
if (sameValues(values, filledValues(this._state.rows))) return;
|
|
86
|
+
this._state.rows = rowsOf(values);
|
|
87
|
+
this.renderRows();
|
|
88
|
+
}
|
|
89
|
+
renderRows() {
|
|
90
|
+
this._refs.rows.replaceChildren(...this._state.rows.map((value, index) => this.row(value, index)));
|
|
91
|
+
this.renderAdd();
|
|
92
|
+
}
|
|
93
|
+
renderAdd() {
|
|
94
|
+
const add = this._refs.add;
|
|
95
|
+
add.label.set(this.addLabel.get());
|
|
96
|
+
add.hidden = this.disabled.get() || !canAddRow(this._state.rows.length, this.max.get());
|
|
97
|
+
}
|
|
98
|
+
renderDisabled(disabled) {
|
|
99
|
+
this.reflect("disabled", disabled);
|
|
100
|
+
this.renderRows();
|
|
101
|
+
}
|
|
102
|
+
row(value, index) {
|
|
103
|
+
const control = this.control(value, (text) => this.change(index, text));
|
|
104
|
+
const remove = /* @__PURE__ */ jsx(JlButton, {
|
|
105
|
+
class: "remove",
|
|
106
|
+
variant: "ghost",
|
|
107
|
+
size: "sm",
|
|
108
|
+
icon: "x",
|
|
109
|
+
hint: this.removeLabel.get()
|
|
110
|
+
});
|
|
111
|
+
remove.events.register("press", () => this.removeRow(index));
|
|
112
|
+
remove.hidden = this.disabled.get() || this._state.rows.length === 1;
|
|
113
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
114
|
+
class: "row",
|
|
115
|
+
children: [control, remove]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** The control of a row: a select among the options, a text area, or an input of the type; `onInput` hears it. */
|
|
119
|
+
control(value, onInput) {
|
|
120
|
+
const label = this.label.get();
|
|
121
|
+
const disabled = this.disabled.get();
|
|
122
|
+
const options = this.options.get();
|
|
123
|
+
if (options.length > 0) {
|
|
124
|
+
const select = /* @__PURE__ */ jsx(JlSelect, {
|
|
125
|
+
label,
|
|
126
|
+
options,
|
|
127
|
+
disabled
|
|
128
|
+
});
|
|
129
|
+
select.value.set(value);
|
|
130
|
+
select.events.register("change", onInput);
|
|
131
|
+
return select;
|
|
132
|
+
}
|
|
133
|
+
if (this.multiline.get()) {
|
|
134
|
+
const area = /* @__PURE__ */ jsx(JlTextarea, {
|
|
135
|
+
label,
|
|
136
|
+
value,
|
|
137
|
+
placeholder: this.placeholder.get(),
|
|
138
|
+
disabled,
|
|
139
|
+
autosize: true
|
|
140
|
+
});
|
|
141
|
+
area.events.register("input", onInput);
|
|
142
|
+
return area;
|
|
143
|
+
}
|
|
144
|
+
const input = /* @__PURE__ */ jsx(JlInput, {
|
|
145
|
+
label,
|
|
146
|
+
value,
|
|
147
|
+
type: this.type.get(),
|
|
148
|
+
placeholder: this.placeholder.get(),
|
|
149
|
+
disabled
|
|
150
|
+
});
|
|
151
|
+
input.events.register("input", onInput);
|
|
152
|
+
return input;
|
|
153
|
+
}
|
|
154
|
+
change(index, text) {
|
|
155
|
+
this._state.rows = withRowAt(this._state.rows, index, text);
|
|
156
|
+
this.publish();
|
|
157
|
+
}
|
|
158
|
+
addRow() {
|
|
159
|
+
if (!canAddRow(this._state.rows.length, this.max.get())) return;
|
|
160
|
+
this._state.rows = [...this._state.rows, ""];
|
|
161
|
+
this.renderRows();
|
|
162
|
+
const added = this._refs.rows.lastElementChild?.firstElementChild;
|
|
163
|
+
if (added instanceof HTMLElement) added.focus();
|
|
164
|
+
}
|
|
165
|
+
removeRow(index) {
|
|
166
|
+
this._state.rows = withoutRowAt(this._state.rows, index);
|
|
167
|
+
this.renderRows();
|
|
168
|
+
this.publish();
|
|
169
|
+
}
|
|
170
|
+
publish() {
|
|
171
|
+
const values = filledValues(this._state.rows);
|
|
172
|
+
this.values.set(values);
|
|
173
|
+
this.events.emit("change", values);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
JlInputList.register();
|
|
177
|
+
export { JlInputList };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { registerIcons } from "../lib/icons.js";
|
|
4
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
4
5
|
import { setOrRemove } from "../lib/dom.js";
|
|
5
6
|
import { hasSlotted } from "../lib/slots.js";
|
|
6
|
-
import { JlIcon } from "../atoms/jl-icon.js";
|
|
7
7
|
import { isActivationKey, startedOnControl } from "../lib/surface.js";
|
|
8
8
|
import jl_list_item_default from "./jl-list-item.css_raw.js";
|
|
9
9
|
import { css } from "@jadis/core";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-load-more.css?raw
|
|
2
|
+
var jl_load_more_default = ":host {\n display: block;\n}\n\n:host([complete]) {\n display: none;\n}\n\n.more {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n align-items: center;\n justify-content: center;\n padding: 12px 0;\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n}\n";
|
|
3
|
+
export { jl_load_more_default as default };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
/**
|
|
3
|
+
* The end of a long list read a page at a time: how much of it shows ("30 of 120") and a button that asks for the
|
|
4
|
+
* next page (`more`). It shows the button's spinner while `loading`, and takes no room once the whole list shows.
|
|
5
|
+
*/
|
|
6
|
+
export declare class JlLoadMore extends JlElement {
|
|
7
|
+
static readonly selector = "jl-load-more";
|
|
8
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
9
|
+
more: undefined;
|
|
10
|
+
}>>;
|
|
11
|
+
/** How many items the list shows. */
|
|
12
|
+
readonly shown: Readonly<Readonly<{
|
|
13
|
+
get(): number;
|
|
14
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
15
|
+
}>>;
|
|
16
|
+
/** How many it has in all. */
|
|
17
|
+
readonly total: Readonly<Readonly<{
|
|
18
|
+
get(): number;
|
|
19
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
20
|
+
}>>;
|
|
21
|
+
/** The next page is on its way: the button shows a spinner and ignores presses. */
|
|
22
|
+
readonly loading: Readonly<Readonly<{
|
|
23
|
+
get(): boolean;
|
|
24
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
25
|
+
}>>;
|
|
26
|
+
/** The button's text. */
|
|
27
|
+
readonly label: Readonly<Readonly<{
|
|
28
|
+
get(): string;
|
|
29
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
30
|
+
}>>;
|
|
31
|
+
/** What shows beside it; `{shown}` and `{total}` are filled. */
|
|
32
|
+
readonly countText: Readonly<Readonly<{
|
|
33
|
+
get(): string;
|
|
34
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
35
|
+
}>>;
|
|
36
|
+
private readonly _refs;
|
|
37
|
+
templateHtml(): Node;
|
|
38
|
+
templateCss(): string;
|
|
39
|
+
onConnect(): void;
|
|
40
|
+
private render;
|
|
41
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { fillText } from "../lib/text.js";
|
|
4
|
+
import { jlText } from "../lib/texts.js";
|
|
5
|
+
import { JlButton } from "../atoms/jl-button.js";
|
|
6
|
+
import { pageLocale } from "../lib/locale.js";
|
|
7
|
+
import jl_load_more_default from "./jl-load-more.css_raw.js";
|
|
8
|
+
import { css } from "@jadis/core";
|
|
9
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
10
|
+
// src/molecules/jl-load-more.tsx
|
|
11
|
+
/**
|
|
12
|
+
* The end of a long list read a page at a time: how much of it shows ("30 of 120") and a button that asks for the
|
|
13
|
+
* next page (`more`). It shows the button's spinner while `loading`, and takes no room once the whole list shows.
|
|
14
|
+
*/
|
|
15
|
+
var JlLoadMore = class extends JlElement {
|
|
16
|
+
static selector = "jl-load-more";
|
|
17
|
+
events = this.useEvents();
|
|
18
|
+
/** How many items the list shows. */
|
|
19
|
+
shown = this.useChange(0, () => this.render(), { attribute: "shown" });
|
|
20
|
+
/** How many it has in all. */
|
|
21
|
+
total = this.useChange(0, () => this.render(), { attribute: "total" });
|
|
22
|
+
/** The next page is on its way: the button shows a spinner and ignores presses. */
|
|
23
|
+
loading = this.useChange(false, (value) => this._refs.button.loading.set(value), { attribute: "loading" });
|
|
24
|
+
/** The button's text. */
|
|
25
|
+
label = this.useChange(jlText("showMore"), (value) => this._refs.button.label.set(value), { attribute: "label" });
|
|
26
|
+
/** What shows beside it; `{shown}` and `{total}` are filled. */
|
|
27
|
+
countText = this.useChange(jlText("shownOfTotal"), () => this.render(), { attribute: "count-text" });
|
|
28
|
+
_refs = this.useRefs((ref) => ({
|
|
29
|
+
button: ref(JlButton),
|
|
30
|
+
count: ref(".count")
|
|
31
|
+
}));
|
|
32
|
+
templateHtml() {
|
|
33
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
34
|
+
class: "more",
|
|
35
|
+
attrs: { part: "base" },
|
|
36
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
37
|
+
class: "count",
|
|
38
|
+
"aria-live": "polite"
|
|
39
|
+
}), /* @__PURE__ */ jsx(JlButton, { size: "sm" })]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
templateCss() {
|
|
43
|
+
return css`${BASE_STYLES}${jl_load_more_default}`;
|
|
44
|
+
}
|
|
45
|
+
onConnect() {
|
|
46
|
+
this._refs.button.events.register("press", () => {
|
|
47
|
+
if (!this.loading.get()) this.events.emit("more");
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
render() {
|
|
51
|
+
const locale = pageLocale();
|
|
52
|
+
const shown = this.shown.get();
|
|
53
|
+
const total = this.total.get();
|
|
54
|
+
this._refs.count.textContent = fillText(this.countText.get(), {
|
|
55
|
+
shown: shown.toLocaleString(locale),
|
|
56
|
+
total: total.toLocaleString(locale)
|
|
57
|
+
});
|
|
58
|
+
this.reflect("complete", shown >= total);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
JlLoadMore.register();
|
|
62
|
+
export { JlLoadMore };
|
|
@@ -49,9 +49,12 @@ export declare class JlLoadState extends JlElement {
|
|
|
49
49
|
/**
|
|
50
50
|
* Fetches the data and draws it. The first time, and after a failure, the skeleton shows while it comes; later
|
|
51
51
|
* (a refresh), what is shown stays until the new data is there, and stays too if it fails. Resolves to whether it
|
|
52
|
-
* worked: a refresh that fails says so only here, for the page to tell people.
|
|
52
|
+
* worked: a refresh that fails says so only here, for the page to tell people. A load overtaken by a later one
|
|
53
|
+
* (a search typed on, a poll during a reload) draws nothing and resolves true: the later one decides.
|
|
53
54
|
*/
|
|
54
55
|
load<Value>(task: LoadTask<Value>): Promise<boolean>;
|
|
56
|
+
/** Whether data is shown (the content or the empty state), which a refresh keeps. */
|
|
57
|
+
private isShowing;
|
|
55
58
|
templateHtml(): Node;
|
|
56
59
|
templateCss(): string;
|
|
57
60
|
onConnect(): void;
|
|
@@ -3,6 +3,7 @@ import { JlElement } from "../base/jl-element.js";
|
|
|
3
3
|
import { clampedNumber, oneOf } from "../lib/attributes.js";
|
|
4
4
|
import { define } from "../lib/define.js";
|
|
5
5
|
import { registerIcons } from "../lib/icons.js";
|
|
6
|
+
import { jlText } from "../lib/texts.js";
|
|
6
7
|
import { JlButton } from "../atoms/jl-button.js";
|
|
7
8
|
import { JlSkeleton } from "../atoms/jl-skeleton.js";
|
|
8
9
|
import { JlEmpty } from "./jl-empty.js";
|
|
@@ -28,8 +29,6 @@ var LOAD_SKELETONS = [
|
|
|
28
29
|
"lines"
|
|
29
30
|
];
|
|
30
31
|
var DEFAULT_ERROR_HEADING = "This could not be loaded";
|
|
31
|
-
var DEFAULT_ERROR_TEXT = "Check the connection, then try again. Nothing was changed.";
|
|
32
|
-
var DEFAULT_RETRY_LABEL = "Try again";
|
|
33
32
|
var DEFAULT_DELAY_MS = 300;
|
|
34
33
|
var MAX_DELAY_MS = 5e3;
|
|
35
34
|
var DEFAULT_COUNT = 3;
|
|
@@ -95,36 +94,45 @@ var JlLoadState = class extends JlElement {
|
|
|
95
94
|
parse: clampedNumber(DEFAULT_DELAY_MS, 0, MAX_DELAY_MS)
|
|
96
95
|
});
|
|
97
96
|
errorHeading = this.useChange(DEFAULT_ERROR_HEADING, (value) => this._refs.error.heading.set(value), { attribute: "error-heading" });
|
|
98
|
-
errorText = this.useChange(
|
|
99
|
-
retryLabel = this.useChange(
|
|
100
|
-
_work = {};
|
|
97
|
+
errorText = this.useChange(jlText("loadStateError"), (value) => this._refs.error.description.set(value), { attribute: "error-text" });
|
|
98
|
+
retryLabel = this.useChange(jlText("retry"), (value) => this._refs.retry.label.set(value), { attribute: "retry-label" });
|
|
99
|
+
_work = { latest: 0 };
|
|
101
100
|
_refs = this.useRefs((ref) => ({
|
|
102
101
|
bones: ref(".bones"),
|
|
103
102
|
content: ref(".content"),
|
|
104
103
|
empty: ref(".empty"),
|
|
105
|
-
error: ref(
|
|
104
|
+
error: ref(JlEmpty),
|
|
106
105
|
loading: ref(".loading"),
|
|
107
106
|
retry: ref(".retry")
|
|
108
107
|
}));
|
|
109
108
|
/**
|
|
110
109
|
* Fetches the data and draws it. The first time, and after a failure, the skeleton shows while it comes; later
|
|
111
110
|
* (a refresh), what is shown stays until the new data is there, and stays too if it fails. Resolves to whether it
|
|
112
|
-
* worked: a refresh that fails says so only here, for the page to tell people.
|
|
111
|
+
* worked: a refresh that fails says so only here, for the page to tell people. A load overtaken by a later one
|
|
112
|
+
* (a search typed on, a poll during a reload) draws nothing and resolves true: the later one decides.
|
|
113
113
|
*/
|
|
114
114
|
async load(task) {
|
|
115
|
+
this._work.latest += 1;
|
|
116
|
+
const ticket = this._work.latest;
|
|
115
117
|
this._work.retry = () => this.load(task);
|
|
116
|
-
|
|
117
|
-
if (!showing) this.state.set("loading");
|
|
118
|
+
if (!this.isShowing()) this.state.set("loading");
|
|
118
119
|
try {
|
|
119
120
|
const value = await task.fetch();
|
|
121
|
+
if (ticket !== this._work.latest) return true;
|
|
120
122
|
task.draw(value);
|
|
121
123
|
this.state.set(task.isEmpty?.(value) === true ? "empty" : "ready");
|
|
122
124
|
return true;
|
|
123
125
|
} catch {
|
|
124
|
-
if (
|
|
126
|
+
if (ticket !== this._work.latest) return true;
|
|
127
|
+
if (!this.isShowing()) this.state.set("error");
|
|
125
128
|
return false;
|
|
126
129
|
}
|
|
127
130
|
}
|
|
131
|
+
/** Whether data is shown (the content or the empty state), which a refresh keeps. */
|
|
132
|
+
isShowing() {
|
|
133
|
+
const state = this.state.get();
|
|
134
|
+
return state === "ready" || state === "empty";
|
|
135
|
+
}
|
|
128
136
|
templateHtml() {
|
|
129
137
|
return /* @__PURE__ */ jsxs("div", {
|
|
130
138
|
class: "load-state",
|
|
@@ -143,12 +151,12 @@ var JlLoadState = class extends JlElement {
|
|
|
143
151
|
class: "error",
|
|
144
152
|
icon: "circle-alert",
|
|
145
153
|
heading: DEFAULT_ERROR_HEADING,
|
|
146
|
-
description:
|
|
154
|
+
description: jlText("loadStateError"),
|
|
147
155
|
hidden: true,
|
|
148
156
|
children: /* @__PURE__ */ jsx(JlButton, {
|
|
149
157
|
class: "retry",
|
|
150
158
|
icon: "refresh",
|
|
151
|
-
label:
|
|
159
|
+
label: jlText("retry")
|
|
152
160
|
})
|
|
153
161
|
}),
|
|
154
162
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -2,8 +2,8 @@ import { BASE_STYLES, FLOATING_STYLES } from "../styles/shared.js";
|
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { jsonOf, oneOf } from "../lib/attributes.js";
|
|
4
4
|
import { PLACEMENTS, followAnchor } from "../lib/floating.js";
|
|
5
|
-
import { uniqueId } from "../lib/dom.js";
|
|
6
5
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
6
|
+
import { uniqueId } from "../lib/dom.js";
|
|
7
7
|
import { firstIndex, lastIndex, letterIndex, stepIndex } from "../lib/roving.js";
|
|
8
8
|
import { setTriggerAria } from "../lib/trigger.js";
|
|
9
9
|
import jl_menu_default from "./jl-menu.css_raw.js";
|