@syware/jlib 0.6.0 → 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 +9 -4
- package/dist/atoms/jl-app-icon.d.ts +5 -0
- package/dist/atoms/jl-app-icon.js +26 -4
- package/dist/atoms/jl-badge.js +1 -1
- package/dist/atoms/jl-button.d.ts +5 -0
- package/dist/atoms/jl-button.js +4 -1
- package/dist/atoms/jl-chip.js +3 -2
- package/dist/atoms/jl-input.js +4 -3
- package/dist/atoms/jl-link.js +1 -1
- 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 +5 -4
- package/dist/atoms/jl-spinner.js +2 -2
- package/dist/base/jl-form-control.js +2 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -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 +2 -0
- package/dist/lib/text.js +4 -1
- package/dist/lib/texts.d.ts +97 -0
- package/dist/lib/texts.js +102 -0
- package/dist/lib/tree.d.ts +10 -1
- package/dist/lib/tree.js +37 -3
- package/dist/molecules/jl-account-button.js +1 -1
- package/dist/molecules/jl-alert.js +2 -1
- package/dist/molecules/jl-breadcrumb.js +3 -3
- package/dist/molecules/jl-calendar.js +4 -4
- package/dist/molecules/jl-chip-group.js +2 -2
- 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 +5 -6
- package/dist/molecules/jl-copy-button.js +5 -4
- package/dist/molecules/jl-date-picker.js +6 -7
- package/dist/molecules/jl-empty.js +1 -1
- 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 +17 -14
- 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-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.js +6 -7
- package/dist/molecules/jl-number-input.js +4 -4
- package/dist/molecules/jl-pagination.js +5 -5
- package/dist/molecules/jl-radio-group.js +2 -2
- package/dist/molecules/jl-search-button.js +4 -4
- package/dist/molecules/jl-search.js +4 -4
- package/dist/molecules/jl-segmented.js +2 -1
- 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 +2 -1
- package/dist/molecules/jl-tag-input.js +2 -2
- 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/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 +39 -9
- package/dist/organisms/jl-confirm.js +5 -7
- package/dist/organisms/jl-dialog.js +2 -1
- package/dist/organisms/jl-drawer.js +2 -1
- package/dist/organisms/jl-flow.js +12 -12
- 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 +70 -13
- package/dist/organisms/jl-tab-bar.js +2 -2
- 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 +35 -5
- 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/templates/jl-app-shell.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.d.ts +10 -0
- package/dist/templates/jl-app-shell.js +16 -2
- package/dist/templates/jl-auth-layout.js +1 -1
- package/dist/templates/jl-page.js +3 -3
- package/dist/templates/jl-split-view.js +5 -4
- 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-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
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { JlElement } from '../base/jl-element';
|
|
2
|
-
import type { TreeItem } from '../types/jl-tree.types';
|
|
2
|
+
import type { TreeItem, TreeLoader } from '../types/jl-tree.types';
|
|
3
3
|
/**
|
|
4
4
|
* Nested items that open and close, as files in folders: `items` holds them, `expanded` lists the open folders and
|
|
5
|
-
* `value` is the selected item.
|
|
6
|
-
*
|
|
5
|
+
* `value` is the selected item. With a `loader`, the tree loads itself a level at a time and a page at a time, so a
|
|
6
|
+
* large archive opens without loading everything: the roots first, a `lazy` folder's children when it opens, and
|
|
7
|
+
* the next page of a level from the button that ends it. The keyboard follows the tree pattern: Up and Down move,
|
|
8
|
+
* Right opens or enters a folder, Left closes it or goes to the parent, Home and End jump, Enter selects. Emits
|
|
9
|
+
* `select` and `toggle`; an item with an `href` is a link instead.
|
|
7
10
|
*/
|
|
8
11
|
export declare class JlTree extends JlElement {
|
|
9
12
|
static readonly selector = "jl-tree";
|
|
@@ -33,24 +36,88 @@ export declare class JlTree extends JlElement {
|
|
|
33
36
|
get(): string;
|
|
34
37
|
set(setter: string | ((prevState: string) => string)): void;
|
|
35
38
|
}>>;
|
|
39
|
+
/** What an empty tree says, such as "No object yet"; none by default. */
|
|
40
|
+
readonly emptyText: Readonly<Readonly<{
|
|
41
|
+
get(): string;
|
|
42
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
43
|
+
}>>;
|
|
44
|
+
/** Said while a page loads. */
|
|
45
|
+
readonly loadingText: Readonly<Readonly<{
|
|
46
|
+
get(): string;
|
|
47
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
48
|
+
}>>;
|
|
49
|
+
/** The button that loads a level's next page; `{count}` is how many items are left. */
|
|
50
|
+
readonly moreText: Readonly<Readonly<{
|
|
51
|
+
get(): string;
|
|
52
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
53
|
+
}>>;
|
|
54
|
+
/** Said where a page could not be loaded. */
|
|
55
|
+
readonly errorText: Readonly<Readonly<{
|
|
56
|
+
get(): string;
|
|
57
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
58
|
+
}>>;
|
|
59
|
+
/** The action that loads it again. */
|
|
60
|
+
readonly retryLabel: Readonly<Readonly<{
|
|
61
|
+
get(): string;
|
|
62
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
63
|
+
}>>;
|
|
64
|
+
private readonly _source;
|
|
36
65
|
private readonly _rows;
|
|
37
66
|
private readonly _focus;
|
|
67
|
+
private readonly _loading;
|
|
38
68
|
private readonly _refs;
|
|
69
|
+
/** Loads the items a page at a time; setting it forgets `items` and loads the roots. */
|
|
70
|
+
get loader(): TreeLoader | null;
|
|
71
|
+
set loader(loader: TreeLoader | null);
|
|
39
72
|
/** Opens or closes a folder, as a press on its chevron would. */
|
|
40
73
|
toggle(id: string, open: boolean): void;
|
|
74
|
+
/** Forgets what the loader gave and loads the roots again. */
|
|
75
|
+
reload(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Shows an item given the ids from its root down to it: opens the folders on the way, loading their levels and
|
|
78
|
+
* pages as needed, then selects it, without firing `select`. False when an id is not found where the path says.
|
|
79
|
+
*/
|
|
80
|
+
reveal(path: readonly string[]): Promise<boolean>;
|
|
41
81
|
templateHtml(): Node;
|
|
42
82
|
templateCss(): string;
|
|
43
83
|
onConnect(): void;
|
|
44
84
|
private idOf;
|
|
45
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* A press selects the item; on a folder it also opens or closes it, and the chevron only does that. A link's row
|
|
87
|
+
* leaves a modified click (a new tab) to the browser.
|
|
88
|
+
*/
|
|
46
89
|
private press;
|
|
47
90
|
private handleKey;
|
|
91
|
+
/** Enter on an item: the row that ends a level loads its page, a link is followed, an item is selected. */
|
|
92
|
+
private activate;
|
|
93
|
+
/** Marks the item; one that is not a link says so with `select`. */
|
|
48
94
|
private select;
|
|
49
95
|
private findItem;
|
|
50
96
|
/** One item takes the Tab stop: the focused one, else the selected one, else the first. */
|
|
51
97
|
private moveFocus;
|
|
98
|
+
private hasFocusInside;
|
|
99
|
+
private scrollToRow;
|
|
100
|
+
/** Loads a page of a level, once at a time: a second call while it loads waits for the same page. */
|
|
101
|
+
private loadLevel;
|
|
102
|
+
private loadNextPage;
|
|
103
|
+
private addPage;
|
|
104
|
+
private failLevel;
|
|
105
|
+
/** The open folders whose children the loader has not given yet: their first page. */
|
|
106
|
+
private loadOpenFolders;
|
|
107
|
+
/** The item of an id in a level, loading the level's next pages until it shows or none is left. */
|
|
108
|
+
private findInLevel;
|
|
109
|
+
private openFolder;
|
|
110
|
+
private levelEnd;
|
|
111
|
+
private hasLevelEndRow;
|
|
52
112
|
private renderItems;
|
|
53
113
|
private itemNode;
|
|
114
|
+
/** The row that ends a level, if it has one. */
|
|
115
|
+
private levelEndNodes;
|
|
116
|
+
private levelEndRow;
|
|
117
|
+
private loadingNode;
|
|
118
|
+
private moreNode;
|
|
119
|
+
private errorNode;
|
|
120
|
+
private noteNode;
|
|
54
121
|
private renderExpanded;
|
|
55
122
|
private isVisible;
|
|
56
123
|
/** The item itself when it shows, else the nearest folder around it that does: the focus goes there. */
|
|
@@ -2,16 +2,20 @@ import { BASE_STYLES } from "../styles/shared.js";
|
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { jsonOf } from "../lib/attributes.js";
|
|
4
4
|
import { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { jlText } from "../lib/texts.js";
|
|
5
6
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
6
|
-
import { setOrRemove, uniqueId } from "../lib/dom.js";
|
|
7
|
-
import {
|
|
7
|
+
import { isModifiedClick, setOrRemove, uniqueId } from "../lib/dom.js";
|
|
8
|
+
import { JlSpinner } from "../atoms/jl-spinner.js";
|
|
9
|
+
import { pageLocale } from "../lib/locale.js";
|
|
10
|
+
import { allItems, childrenOf, isFolder, treeKeyAction, withExpanded, withExtraRows, withPage } from "../lib/tree.js";
|
|
8
11
|
import jl_tree_default from "./jl-tree.css_raw.js";
|
|
9
12
|
import { css } from "@jadis/core";
|
|
10
|
-
import { ChevronRight, File, Folder, FolderOpen } from "lucide";
|
|
13
|
+
import { ChevronRight, CircleAlert, File, Folder, FolderOpen } from "lucide";
|
|
11
14
|
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
12
15
|
// src/organisms/jl-tree.tsx
|
|
13
16
|
registerIcons({
|
|
14
17
|
"chevron-right": ChevronRight,
|
|
18
|
+
"circle-alert": CircleAlert,
|
|
15
19
|
file: File,
|
|
16
20
|
folder: Folder,
|
|
17
21
|
"folder-open": FolderOpen
|
|
@@ -21,10 +25,20 @@ var CHEVRON_SIZE = 14;
|
|
|
21
25
|
var FOLDER_ICON = "folder";
|
|
22
26
|
var OPEN_FOLDER_ICON = "folder-open";
|
|
23
27
|
var FILE_ICON = "file";
|
|
28
|
+
var ERROR_ICON = "circle-alert";
|
|
29
|
+
var LEVEL_END_PREFIX = "jl-tree-level-end:";
|
|
30
|
+
var COUNT_PLACEHOLDER = "{count}";
|
|
31
|
+
var levelEndId = (parent) => `${LEVEL_END_PREFIX}${parent ?? ""}`;
|
|
32
|
+
var isLevelEnd = (id) => id.startsWith(LEVEL_END_PREFIX);
|
|
33
|
+
var parentOfLevelEnd = (id) => id.slice(18) || null;
|
|
34
|
+
var messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
24
35
|
/**
|
|
25
36
|
* Nested items that open and close, as files in folders: `items` holds them, `expanded` lists the open folders and
|
|
26
|
-
* `value` is the selected item.
|
|
27
|
-
*
|
|
37
|
+
* `value` is the selected item. With a `loader`, the tree loads itself a level at a time and a page at a time, so a
|
|
38
|
+
* large archive opens without loading everything: the roots first, a `lazy` folder's children when it opens, and
|
|
39
|
+
* the next page of a level from the button that ends it. The keyboard follows the tree pattern: Up and Down move,
|
|
40
|
+
* Right opens or enters a folder, Left closes it or goes to the parent, Home and End jump, Enter selects. Emits
|
|
41
|
+
* `select` and `toggle`; an item with an `href` is a link instead.
|
|
28
42
|
*/
|
|
29
43
|
var JlTree = class extends JlElement {
|
|
30
44
|
static selector = "jl-tree";
|
|
@@ -34,7 +48,10 @@ var JlTree = class extends JlElement {
|
|
|
34
48
|
parse: jsonOf([])
|
|
35
49
|
});
|
|
36
50
|
/** The ids of the open folders. */
|
|
37
|
-
expanded = this.useChange([], () =>
|
|
51
|
+
expanded = this.useChange([], () => {
|
|
52
|
+
this.renderExpanded();
|
|
53
|
+
this.loadOpenFolders();
|
|
54
|
+
}, {
|
|
38
55
|
attribute: "expanded",
|
|
39
56
|
parse: jsonOf([])
|
|
40
57
|
});
|
|
@@ -42,9 +59,32 @@ var JlTree = class extends JlElement {
|
|
|
42
59
|
value = this.useChange("", () => this.renderSelection(), { attribute: "value" });
|
|
43
60
|
/** The tree's name for screen readers. */
|
|
44
61
|
label = this.useChange("", (value) => setOrRemove(this._refs.tree, "aria-label", value), { attribute: "label" });
|
|
62
|
+
/** What an empty tree says, such as "No object yet"; none by default. */
|
|
63
|
+
emptyText = this.useChange("", () => this.renderItems(), { attribute: "empty-text" });
|
|
64
|
+
/** Said while a page loads. */
|
|
65
|
+
loadingText = this.useChange(jlText("loadingMore"), () => this.renderItems(), { attribute: "loading-text" });
|
|
66
|
+
/** The button that loads a level's next page; `{count}` is how many items are left. */
|
|
67
|
+
moreText = this.useChange(jlText("showMoreCount"), () => this.renderItems(), { attribute: "more-text" });
|
|
68
|
+
/** Said where a page could not be loaded. */
|
|
69
|
+
errorText = this.useChange(jlText("loadError"), () => this.renderItems(), { attribute: "error-text" });
|
|
70
|
+
/** The action that loads it again. */
|
|
71
|
+
retryLabel = this.useChange(jlText("retry"), () => this.renderItems(), { attribute: "retry-label" });
|
|
72
|
+
_source = { loader: null };
|
|
45
73
|
_rows = /* @__PURE__ */ new Map();
|
|
46
74
|
_focus = { id: "" };
|
|
75
|
+
_loading = {
|
|
76
|
+
generation: 0,
|
|
77
|
+
levels: /* @__PURE__ */ new Map()
|
|
78
|
+
};
|
|
47
79
|
_refs = this.useRefs((ref) => ({ tree: ref(".tree") }));
|
|
80
|
+
/** Loads the items a page at a time; setting it forgets `items` and loads the roots. */
|
|
81
|
+
get loader() {
|
|
82
|
+
return this._source.loader;
|
|
83
|
+
}
|
|
84
|
+
set loader(loader) {
|
|
85
|
+
this._source.loader = loader;
|
|
86
|
+
this.whenRendered(() => void this.reload());
|
|
87
|
+
}
|
|
48
88
|
/** Opens or closes a folder, as a press on its chevron would. */
|
|
49
89
|
toggle(id, open) {
|
|
50
90
|
this.expanded.set(withExpanded(this.expanded.get(), id, open));
|
|
@@ -53,6 +93,30 @@ var JlTree = class extends JlElement {
|
|
|
53
93
|
open
|
|
54
94
|
});
|
|
55
95
|
}
|
|
96
|
+
/** Forgets what the loader gave and loads the roots again. */
|
|
97
|
+
async reload() {
|
|
98
|
+
if (this.loader === null) return;
|
|
99
|
+
this._loading.generation += 1;
|
|
100
|
+
this._loading.levels.clear();
|
|
101
|
+
this.items.set([]);
|
|
102
|
+
await this.loadLevel(null, 0);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Shows an item given the ids from its root down to it: opens the folders on the way, loading their levels and
|
|
106
|
+
* pages as needed, then selects it, without firing `select`. False when an id is not found where the path says.
|
|
107
|
+
*/
|
|
108
|
+
async reveal(path) {
|
|
109
|
+
const target = path.at(-1);
|
|
110
|
+
if (target === void 0) return false;
|
|
111
|
+
for (const [index, id] of path.entries()) {
|
|
112
|
+
if (!await this.findInLevel(index === 0 ? null : path[index - 1] ?? null, id)) return false;
|
|
113
|
+
if (index < path.length - 1) await this.openFolder(id);
|
|
114
|
+
}
|
|
115
|
+
this.value.set(target);
|
|
116
|
+
this.moveFocus(target, false);
|
|
117
|
+
this.scrollToRow(target);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
56
120
|
templateHtml() {
|
|
57
121
|
return /* @__PURE__ */ jsx("div", {
|
|
58
122
|
class: "tree",
|
|
@@ -75,30 +139,53 @@ var JlTree = class extends JlElement {
|
|
|
75
139
|
idOf(target) {
|
|
76
140
|
return (target instanceof Element ? target.closest("[role=\"treeitem\"]") : null)?.dataset.id ?? null;
|
|
77
141
|
}
|
|
78
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* A press selects the item; on a folder it also opens or closes it, and the chevron only does that. A link's row
|
|
144
|
+
* leaves a modified click (a new tab) to the browser.
|
|
145
|
+
*/
|
|
79
146
|
press(event) {
|
|
80
147
|
const id = this.idOf(event.target);
|
|
148
|
+
if (id !== null && isLevelEnd(id)) {
|
|
149
|
+
this.loadNextPage(parentOfLevelEnd(id));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
81
152
|
const item = id === null ? void 0 : this.findItem(id);
|
|
82
153
|
if (id === null || item === void 0) return;
|
|
83
154
|
const onChevron = event.target instanceof Element && event.target.closest(".chevron") !== null;
|
|
155
|
+
if (onChevron) event.preventDefault();
|
|
156
|
+
if (item.href !== void 0 && !onChevron && isModifiedClick(event)) return;
|
|
84
157
|
if (isFolder(item)) this.toggle(id, !this.expanded.get().includes(id));
|
|
85
|
-
if (!onChevron) this.select(
|
|
158
|
+
if (!onChevron) this.select(item);
|
|
86
159
|
this.moveFocus(id, true);
|
|
87
160
|
}
|
|
88
161
|
handleKey(event) {
|
|
89
162
|
const focused = this._focus.id;
|
|
90
|
-
const
|
|
163
|
+
const keyboardItems = withExtraRows(this.items.get(), (parent) => this.hasLevelEndRow(parent) ? levelEndId(parent) : null);
|
|
164
|
+
const action = treeKeyAction(keyboardItems, new Set(this.expanded.get()), focused, event.key);
|
|
91
165
|
if (action === null) return;
|
|
92
166
|
event.preventDefault();
|
|
93
167
|
({
|
|
94
168
|
focus: () => this.moveFocus(action.id, true),
|
|
95
|
-
select: () => this.
|
|
169
|
+
select: () => this.activate(action.id),
|
|
96
170
|
toggle: () => this.toggle(action.id, action.kind === "toggle" && action.open)
|
|
97
171
|
})[action.kind]();
|
|
98
172
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
173
|
+
/** Enter on an item: the row that ends a level loads its page, a link is followed, an item is selected. */
|
|
174
|
+
activate(id) {
|
|
175
|
+
if (isLevelEnd(id)) {
|
|
176
|
+
this.loadNextPage(parentOfLevelEnd(id));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const item = this.findItem(id);
|
|
180
|
+
if (item === void 0) return;
|
|
181
|
+
this.select(item);
|
|
182
|
+
const link = this._rows.get(id)?.querySelector(":scope > a.row");
|
|
183
|
+
if (link) location.assign(link.href);
|
|
184
|
+
}
|
|
185
|
+
/** Marks the item; one that is not a link says so with `select`. */
|
|
186
|
+
select(item) {
|
|
187
|
+
this.value.set(item.id);
|
|
188
|
+
if (item.href === void 0) this.events.emit("select", item.id);
|
|
102
189
|
}
|
|
103
190
|
findItem(id) {
|
|
104
191
|
return allItems(this.items.get()).find((entry) => entry.item.id === id)?.item;
|
|
@@ -112,18 +199,133 @@ var JlTree = class extends JlElement {
|
|
|
112
199
|
row.setAttribute("tabindex", "0");
|
|
113
200
|
if (focus) row.focus();
|
|
114
201
|
}
|
|
202
|
+
hasFocusInside() {
|
|
203
|
+
const active = this.shadowRoot?.activeElement;
|
|
204
|
+
return active instanceof Element && this._refs.tree.contains(active);
|
|
205
|
+
}
|
|
206
|
+
scrollToRow(id) {
|
|
207
|
+
const row = this._rows.get(id);
|
|
208
|
+
if (row !== void 0 && typeof row.scrollIntoView === "function") row.scrollIntoView({ block: "nearest" });
|
|
209
|
+
}
|
|
210
|
+
/** Loads a page of a level, once at a time: a second call while it loads waits for the same page. */
|
|
211
|
+
loadLevel(parent, offset) {
|
|
212
|
+
const loader = this.loader;
|
|
213
|
+
const level = this._loading.levels.get(parent);
|
|
214
|
+
if (loader === null) return Promise.resolve();
|
|
215
|
+
if (level !== void 0 && level.pending !== null) return level.pending;
|
|
216
|
+
const generation = this._loading.generation;
|
|
217
|
+
const pending = Promise.resolve().then(() => loader(parent, offset)).then((page) => this.addPage(parent, page, generation), (error) => this.failLevel(parent, error, generation));
|
|
218
|
+
this._loading.levels.set(parent, {
|
|
219
|
+
error: "",
|
|
220
|
+
pending,
|
|
221
|
+
total: level?.total ?? 0
|
|
222
|
+
});
|
|
223
|
+
this.whenRendered(() => this.renderItems());
|
|
224
|
+
return pending;
|
|
225
|
+
}
|
|
226
|
+
loadNextPage(parent) {
|
|
227
|
+
this.loadLevel(parent, childrenOf(this.items.get(), parent).length);
|
|
228
|
+
}
|
|
229
|
+
addPage(parent, page, generation) {
|
|
230
|
+
if (generation !== this._loading.generation) return;
|
|
231
|
+
const focusFirst = this._focus.id === levelEndId(parent) && this.hasFocusInside();
|
|
232
|
+
const loaded = childrenOf(this.items.get(), parent).length + page.items.length;
|
|
233
|
+
const total = page.items.length === 0 ? loaded : page.total;
|
|
234
|
+
this._loading.levels.set(parent, {
|
|
235
|
+
error: "",
|
|
236
|
+
pending: null,
|
|
237
|
+
total
|
|
238
|
+
});
|
|
239
|
+
this.items.set(withPage(this.items.get(), parent, page.items));
|
|
240
|
+
this.loadOpenFolders();
|
|
241
|
+
const first = page.items[0];
|
|
242
|
+
if (focusFirst && first !== void 0) this.moveFocus(first.id, true);
|
|
243
|
+
}
|
|
244
|
+
failLevel(parent, error, generation) {
|
|
245
|
+
if (generation !== this._loading.generation) return;
|
|
246
|
+
const total = this._loading.levels.get(parent)?.total ?? 0;
|
|
247
|
+
this._loading.levels.set(parent, {
|
|
248
|
+
error: messageOf(error) || this.errorText.get(),
|
|
249
|
+
pending: null,
|
|
250
|
+
total
|
|
251
|
+
});
|
|
252
|
+
this.whenRendered(() => this.renderItems());
|
|
253
|
+
}
|
|
254
|
+
/** The open folders whose children the loader has not given yet: their first page. */
|
|
255
|
+
loadOpenFolders() {
|
|
256
|
+
for (const id of this.expanded.get()) if (this.findItem(id)?.lazy === true && !this._loading.levels.has(id)) this.loadLevel(id, 0);
|
|
257
|
+
}
|
|
258
|
+
/** The item of an id in a level, loading the level's next pages until it shows or none is left. */
|
|
259
|
+
async findInLevel(parent, id) {
|
|
260
|
+
const isThere = () => childrenOf(this.items.get(), parent).some((item) => item.id === id);
|
|
261
|
+
await this._loading.levels.get(parent)?.pending;
|
|
262
|
+
while (!isThere() && this.levelEnd(parent) === "more") await this.loadLevel(parent, childrenOf(this.items.get(), parent).length);
|
|
263
|
+
return isThere();
|
|
264
|
+
}
|
|
265
|
+
async openFolder(id) {
|
|
266
|
+
if (!this.expanded.get().includes(id)) this.expanded.set(withExpanded(this.expanded.get(), id, true));
|
|
267
|
+
await this._loading.levels.get(id)?.pending;
|
|
268
|
+
}
|
|
269
|
+
levelEnd(parent) {
|
|
270
|
+
const level = this._loading.levels.get(parent);
|
|
271
|
+
if (level !== void 0 && level.pending !== null) return "loading";
|
|
272
|
+
if (level !== void 0 && level.error !== "") return "error";
|
|
273
|
+
if (level !== void 0 && level.total > childrenOf(this.items.get(), parent).length) return "more";
|
|
274
|
+
return parent === null && this.items.get().length === 0 && this.emptyText.get() !== "" ? "empty" : "none";
|
|
275
|
+
}
|
|
276
|
+
hasLevelEndRow(parent) {
|
|
277
|
+
const end = this.levelEnd(parent);
|
|
278
|
+
return end !== "none" && end !== "empty";
|
|
279
|
+
}
|
|
115
280
|
renderItems() {
|
|
281
|
+
const hadFocus = this.hasFocusInside();
|
|
116
282
|
this._rows.clear();
|
|
117
|
-
this._refs.tree.replaceChildren(...this.items.get().map((item) => this.itemNode(item, 1)));
|
|
283
|
+
this._refs.tree.replaceChildren(...this.items.get().map((item) => this.itemNode(item, 1)), ...this.levelEndNodes(null, 1));
|
|
118
284
|
this.renderExpanded();
|
|
119
285
|
this.renderSelection();
|
|
120
|
-
const
|
|
121
|
-
this.
|
|
286
|
+
const first = this._rows.keys().next().value ?? "";
|
|
287
|
+
const start = this._rows.has(this.value.get()) ? this.value.get() : first;
|
|
288
|
+
this.moveFocus(this._rows.has(this._focus.id) ? this._focus.id : start, hadFocus);
|
|
122
289
|
}
|
|
123
290
|
itemNode(item, level) {
|
|
124
291
|
const folder = isFolder(item);
|
|
125
292
|
const labelId = uniqueId("tree-label");
|
|
126
293
|
const iconClass = item.icon === void 0 && folder ? "icon auto" : "icon";
|
|
294
|
+
const content = [
|
|
295
|
+
/* @__PURE__ */ jsx("span", {
|
|
296
|
+
class: "chevron",
|
|
297
|
+
"aria-hidden": "true",
|
|
298
|
+
children: folder ? /* @__PURE__ */ jsx(JlIcon, {
|
|
299
|
+
name: "chevron-right",
|
|
300
|
+
size: CHEVRON_SIZE
|
|
301
|
+
}) : null
|
|
302
|
+
}),
|
|
303
|
+
/* @__PURE__ */ jsx(JlIcon, {
|
|
304
|
+
class: iconClass,
|
|
305
|
+
size: ICON_SIZE,
|
|
306
|
+
name: item.icon ?? (folder ? FOLDER_ICON : FILE_ICON)
|
|
307
|
+
}),
|
|
308
|
+
/* @__PURE__ */ jsx("span", {
|
|
309
|
+
class: "label",
|
|
310
|
+
id: labelId,
|
|
311
|
+
children: item.label
|
|
312
|
+
}),
|
|
313
|
+
item.badge === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
314
|
+
class: "badge",
|
|
315
|
+
children: String(item.badge)
|
|
316
|
+
})
|
|
317
|
+
];
|
|
318
|
+
const row = item.href === void 0 ? /* @__PURE__ */ jsx("div", {
|
|
319
|
+
class: "row",
|
|
320
|
+
title: item.hint,
|
|
321
|
+
children: content
|
|
322
|
+
}) : /* @__PURE__ */ jsx("a", {
|
|
323
|
+
class: "row",
|
|
324
|
+
href: item.href,
|
|
325
|
+
title: item.hint,
|
|
326
|
+
tabIndex: -1,
|
|
327
|
+
children: content
|
|
328
|
+
});
|
|
127
329
|
const node = /* @__PURE__ */ jsx("div", {
|
|
128
330
|
class: "item",
|
|
129
331
|
role: "treeitem",
|
|
@@ -131,29 +333,7 @@ var JlTree = class extends JlElement {
|
|
|
131
333
|
tabIndex: -1,
|
|
132
334
|
"aria-selected": "false",
|
|
133
335
|
"aria-labelledby": labelId,
|
|
134
|
-
children:
|
|
135
|
-
class: "row",
|
|
136
|
-
children: [
|
|
137
|
-
/* @__PURE__ */ jsx("span", {
|
|
138
|
-
class: "chevron",
|
|
139
|
-
"aria-hidden": "true",
|
|
140
|
-
children: folder ? /* @__PURE__ */ jsx(JlIcon, {
|
|
141
|
-
name: "chevron-right",
|
|
142
|
-
size: CHEVRON_SIZE
|
|
143
|
-
}) : null
|
|
144
|
-
}),
|
|
145
|
-
/* @__PURE__ */ jsx(JlIcon, {
|
|
146
|
-
class: iconClass,
|
|
147
|
-
size: ICON_SIZE,
|
|
148
|
-
name: item.icon ?? (folder ? FOLDER_ICON : FILE_ICON)
|
|
149
|
-
}),
|
|
150
|
-
/* @__PURE__ */ jsx("span", {
|
|
151
|
-
class: "label",
|
|
152
|
-
id: labelId,
|
|
153
|
-
children: item.label
|
|
154
|
-
})
|
|
155
|
-
]
|
|
156
|
-
})
|
|
336
|
+
children: row
|
|
157
337
|
});
|
|
158
338
|
node.style.setProperty("--level", String(level - 1));
|
|
159
339
|
this._rows.set(item.id, node);
|
|
@@ -163,11 +343,94 @@ var JlTree = class extends JlElement {
|
|
|
163
343
|
role: "group"
|
|
164
344
|
});
|
|
165
345
|
group.style.setProperty("--level", String(level - 1));
|
|
166
|
-
group.replaceChildren(...(item.children ?? []).map((child) => this.itemNode(child, level + 1)));
|
|
346
|
+
group.replaceChildren(...(item.children ?? []).map((child) => this.itemNode(child, level + 1)), ...this.levelEndNodes(item.id, level + 1));
|
|
167
347
|
node.append(group);
|
|
168
348
|
}
|
|
169
349
|
return node;
|
|
170
350
|
}
|
|
351
|
+
/** The row that ends a level, if it has one. */
|
|
352
|
+
levelEndNodes(parent, level) {
|
|
353
|
+
return {
|
|
354
|
+
empty: () => [this.noteNode(this.emptyText.get(), level)],
|
|
355
|
+
error: () => [this.errorNode(parent, level)],
|
|
356
|
+
loading: () => [this.loadingNode(parent, level)],
|
|
357
|
+
more: () => [this.moreNode(parent, level)],
|
|
358
|
+
none: () => []
|
|
359
|
+
}[this.levelEnd(parent)]();
|
|
360
|
+
}
|
|
361
|
+
levelEndRow(parent, level, content, disabled) {
|
|
362
|
+
const id = levelEndId(parent);
|
|
363
|
+
const node = /* @__PURE__ */ jsx("div", {
|
|
364
|
+
class: "item level-end",
|
|
365
|
+
role: "treeitem",
|
|
366
|
+
"data-id": id,
|
|
367
|
+
tabIndex: -1,
|
|
368
|
+
"aria-disabled": disabled ? "true" : void 0,
|
|
369
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
370
|
+
class: "row",
|
|
371
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
372
|
+
class: "chevron",
|
|
373
|
+
"aria-hidden": "true"
|
|
374
|
+
}), content]
|
|
375
|
+
})
|
|
376
|
+
});
|
|
377
|
+
node.style.setProperty("--level", String(level - 1));
|
|
378
|
+
this._rows.set(id, node);
|
|
379
|
+
return node;
|
|
380
|
+
}
|
|
381
|
+
loadingNode(parent, level) {
|
|
382
|
+
const text = this.loadingText.get();
|
|
383
|
+
return this.levelEndRow(parent, level, [/* @__PURE__ */ jsx(JlSpinner, {
|
|
384
|
+
class: "spinner",
|
|
385
|
+
size: ICON_SIZE,
|
|
386
|
+
label: text
|
|
387
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
388
|
+
class: "label",
|
|
389
|
+
"aria-hidden": "true",
|
|
390
|
+
children: text
|
|
391
|
+
})], true);
|
|
392
|
+
}
|
|
393
|
+
moreNode(parent, level) {
|
|
394
|
+
const left = (this._loading.levels.get(parent)?.total ?? 0) - childrenOf(this.items.get(), parent).length;
|
|
395
|
+
const text = this.moreText.get().replace(COUNT_PLACEHOLDER, left.toLocaleString(pageLocale()));
|
|
396
|
+
return this.levelEndRow(parent, level, [/* @__PURE__ */ jsx("span", {
|
|
397
|
+
class: "label more",
|
|
398
|
+
children: text
|
|
399
|
+
})], false);
|
|
400
|
+
}
|
|
401
|
+
errorNode(parent, level) {
|
|
402
|
+
const node = this.levelEndRow(parent, level, [
|
|
403
|
+
/* @__PURE__ */ jsx(JlIcon, {
|
|
404
|
+
class: "icon error",
|
|
405
|
+
size: ICON_SIZE,
|
|
406
|
+
name: ERROR_ICON
|
|
407
|
+
}),
|
|
408
|
+
/* @__PURE__ */ jsx("span", {
|
|
409
|
+
class: "label",
|
|
410
|
+
children: this.errorText.get()
|
|
411
|
+
}),
|
|
412
|
+
/* @__PURE__ */ jsx("span", {
|
|
413
|
+
class: "retry",
|
|
414
|
+
children: this.retryLabel.get()
|
|
415
|
+
})
|
|
416
|
+
], false);
|
|
417
|
+
setOrRemove(node, "title", this._loading.levels.get(parent)?.error ?? "");
|
|
418
|
+
return node;
|
|
419
|
+
}
|
|
420
|
+
noteNode(text, level) {
|
|
421
|
+
const node = /* @__PURE__ */ jsxs("div", {
|
|
422
|
+
class: "note",
|
|
423
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
424
|
+
class: "chevron",
|
|
425
|
+
"aria-hidden": "true"
|
|
426
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
427
|
+
class: "label",
|
|
428
|
+
children: text
|
|
429
|
+
})]
|
|
430
|
+
});
|
|
431
|
+
node.style.setProperty("--level", String(level - 1));
|
|
432
|
+
return node;
|
|
433
|
+
}
|
|
171
434
|
renderExpanded() {
|
|
172
435
|
const expanded = new Set(this.expanded.get());
|
|
173
436
|
for (const [id, row] of this._rows) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/templates/jl-app-shell.css?raw
|
|
2
|
-
var jl_app_shell_default = ":host {\n display: block;\n height: 100%;\n min-height: 0;\n container: jl-app-shell / inline-size;\n --header-height: 56px;\n --drawer-width: min(300px, 86%);\n}\n\n.shell {\n position: relative;\n display: grid;\n grid-template:\n \"top top top\" auto\n \"nav main aside\" minmax(0, 1fr)\n / auto minmax(0, 1fr) auto;\n height: 100%;\n overflow: hidden;\n background: var(--jl-bg);\n color: var(--jl-text);\n}\n\n.top {\n grid-area: top;\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: var(--header-height);\n padding: 0 16px;\n border-bottom: 1px solid var(--jl-line);\n background: var(--jl-surface);\n}\n\n:host(:not([has-header])) .top {\n display: none;\n}\n\n::slotted([slot=\"header\"]) {\n flex: 1;\n min-width: 0;\n}\n\n.menu {\n display: none;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--jl-control-md);\n height: var(--jl-control-md);\n margin-left: -8px;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-sm);\n background: none;\n color: var(--jl-text-2);\n cursor: pointer;\n}\n\n.menu:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n.nav {\n grid-area: nav;\n min-height: 0;\n overflow-y: auto;\n border-right: 1px solid var(--jl-line);\n background: var(--jl-surface);\n}\n\n.nav:focus-visible {\n box-shadow: none;\n}\n\n:host(:not([has-nav])) .nav {\n display: none;\n}\n\n/* Its own stacking context: what the page lifts with z-index (a sticky bar) stays under the drawer and its scrim. */\n.main {\n grid-area: main;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n isolation: isolate;\n}\n\n.aside {\n grid-area: aside;\n min-height: 0;\n overflow-y: auto;\n border-left: 1px solid var(--jl-line);\n background: var(--jl-surface);\n}\n\n:host(:not([has-aside])) .aside {\n display: none;\n}\n\n/* The bottom bar is for narrow screens: wide ones have the sidebar. */\n.tabs {\n display: none;\n}\n\n.scrim {\n display: none;\n}\n\n/* Narrow: the sidebar slides over the content as a drawer, the menu button opens it, the panel steps aside. */\n@container jl-app-shell (width < 960px) {\n .shell {\n grid-template:\n \"top\" auto\n \"main\" minmax(0, 1fr)\n \"tabs\" auto\n / minmax(0, 1fr);\n }\n\n :host([has-tabs]) .tabs {\n display: block;\n grid-area: tabs;\n }\n\n :host([has-nav]) .top {\n display: flex;\n }\n\n :host([has-nav]) .menu {\n display: inline-flex;\n }\n\n .nav {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n z-index: 2;\n max-width: var(--drawer-width);\n box-shadow: var(--jl-shadow-lg);\n transform: translateX(-100%);\n visibility: hidden;\n transition:\n transform var(--jl-slow) var(--jl-ease),\n visibility 0s linear var(--jl-slow);\n }\n\n :host([nav-open]) .nav {\n transform: none;\n visibility: visible;\n transition: transform var(--jl-slow) var(--jl-ease);\n }\n\n .scrim {\n position: absolute;\n inset: 0;\n z-index: 1;\n display: block;\n background: var(--jl-scrim);\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--jl-slow) var(--jl-ease);\n }\n\n :host([nav-open]) .scrim {\n opacity: 1;\n pointer-events: auto;\n }\n\n .aside {\n display: none;\n }\n}\n";
|
|
2
|
+
var jl_app_shell_default = ":host {\n display: block;\n height: 100%;\n min-height: 0;\n container: jl-app-shell / inline-size;\n --header-height: 56px;\n --drawer-width: min(300px, 86%);\n}\n\n.shell {\n position: relative;\n display: grid;\n grid-template:\n \"top top top\" auto\n \"nav main aside\" minmax(0, 1fr)\n / auto minmax(0, 1fr) auto;\n height: 100%;\n overflow: hidden;\n background: var(--jl-bg);\n color: var(--jl-text);\n}\n\n.top {\n grid-area: top;\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: var(--header-height);\n padding: 0 16px;\n border-bottom: 1px solid var(--jl-line);\n background: var(--jl-surface);\n}\n\n:host(:not([has-header])) .top,\n:host([narrow-header]) .top {\n display: none;\n}\n\n::slotted([slot=\"header\"]) {\n flex: 1;\n min-width: 0;\n}\n\n.menu {\n display: none;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--jl-control-md);\n height: var(--jl-control-md);\n margin-left: -8px;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-sm);\n background: none;\n color: var(--jl-text-2);\n cursor: pointer;\n}\n\n.menu:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n\n.nav {\n grid-area: nav;\n min-height: 0;\n overflow-y: auto;\n border-right: 1px solid var(--jl-line);\n background: var(--jl-surface);\n}\n\n.nav:focus-visible {\n box-shadow: none;\n}\n\n:host(:not([has-nav])) .nav {\n display: none;\n}\n\n/* Its own stacking context: what the page lifts with z-index (a sticky bar) stays under the drawer and its scrim. */\n.main {\n grid-area: main;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n isolation: isolate;\n}\n\n.aside {\n grid-area: aside;\n min-height: 0;\n overflow-y: auto;\n border-left: 1px solid var(--jl-line);\n background: var(--jl-surface);\n}\n\n:host(:not([has-aside])) .aside {\n display: none;\n}\n\n/* The bottom bar is for narrow screens: wide ones have the sidebar. */\n.tabs {\n display: none;\n}\n\n.scrim {\n display: none;\n}\n\n/* Narrow: the sidebar slides over the content as a drawer, the menu button opens it, the panel steps aside. */\n@container jl-app-shell (width < 960px) {\n .shell {\n grid-template:\n \"top\" auto\n \"main\" minmax(0, 1fr)\n \"tabs\" auto\n / minmax(0, 1fr);\n }\n\n :host([has-tabs]) .tabs {\n display: block;\n grid-area: tabs;\n }\n\n :host([has-nav]) .top,\n :host([narrow-header][has-header]) .top {\n display: flex;\n }\n\n :host([has-nav]) .menu {\n display: inline-flex;\n }\n\n .nav {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n z-index: 2;\n max-width: var(--drawer-width);\n box-shadow: var(--jl-shadow-lg);\n transform: translateX(-100%);\n visibility: hidden;\n transition:\n transform var(--jl-slow) var(--jl-ease),\n visibility 0s linear var(--jl-slow);\n }\n\n :host([nav-open]) .nav {\n transform: none;\n visibility: visible;\n transition: transform var(--jl-slow) var(--jl-ease);\n }\n\n .scrim {\n position: absolute;\n inset: 0;\n z-index: 1;\n display: block;\n background: var(--jl-scrim);\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--jl-slow) var(--jl-ease);\n }\n\n :host([nav-open]) .scrim {\n opacity: 1;\n pointer-events: auto;\n }\n\n .aside {\n display: none;\n }\n}\n";
|
|
3
3
|
export { jl_app_shell_default as default };
|
|
@@ -18,6 +18,14 @@ export declare class JlAppShell extends JlElement {
|
|
|
18
18
|
get(): boolean;
|
|
19
19
|
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
20
20
|
}>>;
|
|
21
|
+
/**
|
|
22
|
+
* The top bar only on narrow screens, as the navigation pattern lays it out: there it holds the menu button and
|
|
23
|
+
* the app's name; wide, the side nav's own header names the app.
|
|
24
|
+
*/
|
|
25
|
+
readonly narrowHeader: Readonly<Readonly<{
|
|
26
|
+
get(): boolean;
|
|
27
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
28
|
+
}>>;
|
|
21
29
|
private readonly _state;
|
|
22
30
|
private readonly _resize;
|
|
23
31
|
private readonly _refs;
|
|
@@ -45,4 +53,6 @@ export declare class JlAppShell extends JlElement {
|
|
|
45
53
|
* `more` from the tabs (a tab bar's) opens it.
|
|
46
54
|
*/
|
|
47
55
|
private renderSlots;
|
|
56
|
+
/** Narrow, the nav is a drawer: a side nav shows whole there, whether it is folded on a wide screen or not. */
|
|
57
|
+
private renderDrawer;
|
|
48
58
|
}
|
|
@@ -1,7 +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 { jlText } from "../lib/texts.js";
|
|
4
5
|
import { JlIcon } from "../atoms/jl-icon.js";
|
|
6
|
+
import { watchSizes } from "../lib/resize.js";
|
|
5
7
|
import jl_app_shell_default from "./jl-app-shell.css_raw.js";
|
|
6
8
|
import { css } from "@jadis/core";
|
|
7
9
|
import { Menu } from "lucide";
|
|
@@ -27,14 +29,20 @@ var JlAppShell = class extends JlElement {
|
|
|
27
29
|
events = this.useEvents();
|
|
28
30
|
/** Whether the drawer is open, on narrow screens; wide ones always show the sidebar. */
|
|
29
31
|
navOpen = this.useChange(false, (value) => this.renderNavOpen(value), { attribute: "nav-open" });
|
|
32
|
+
/**
|
|
33
|
+
* The top bar only on narrow screens, as the navigation pattern lays it out: there it holds the menu button and
|
|
34
|
+
* the app's name; wide, the side nav's own header names the app.
|
|
35
|
+
*/
|
|
36
|
+
narrowHeader = this.useChange(false, (value) => this.reflect("narrow-header", value), { attribute: "narrow-header" });
|
|
30
37
|
_state = {
|
|
31
38
|
navListeners: new AbortController(),
|
|
32
39
|
publishedBottom: false,
|
|
33
40
|
tabsListeners: new AbortController()
|
|
34
41
|
};
|
|
35
|
-
_resize =
|
|
42
|
+
_resize = watchSizes(() => {
|
|
36
43
|
this.renderInert();
|
|
37
44
|
this.renderShellBottom();
|
|
45
|
+
this.renderDrawer();
|
|
38
46
|
});
|
|
39
47
|
_refs = this.useRefs((ref) => ({
|
|
40
48
|
aside: ref(".aside"),
|
|
@@ -63,7 +71,7 @@ var JlAppShell = class extends JlElement {
|
|
|
63
71
|
children: [/* @__PURE__ */ jsx("button", {
|
|
64
72
|
class: "menu",
|
|
65
73
|
type: "button",
|
|
66
|
-
"aria-label": "
|
|
74
|
+
"aria-label": jlText("navigation"),
|
|
67
75
|
"aria-expanded": "false",
|
|
68
76
|
"aria-controls": "nav",
|
|
69
77
|
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
@@ -196,6 +204,12 @@ var JlAppShell = class extends JlElement {
|
|
|
196
204
|
const signal = this._state.navListeners.signal;
|
|
197
205
|
for (const element of navElements) element.addEventListener("select", () => this.setNavOpen(false), { signal });
|
|
198
206
|
this.renderInert();
|
|
207
|
+
this.renderDrawer();
|
|
208
|
+
}
|
|
209
|
+
/** Narrow, the nav is a drawer: a side nav shows whole there, whether it is folded on a wide screen or not. */
|
|
210
|
+
renderDrawer() {
|
|
211
|
+
const narrow = this.isNarrow();
|
|
212
|
+
for (const element of this._refs.navSlot.assignedElements()) element.toggleAttribute("in-drawer", narrow);
|
|
199
213
|
}
|
|
200
214
|
};
|
|
201
215
|
JlAppShell.register();
|
|
@@ -2,8 +2,8 @@ import { BASE_STYLES, TOKEN_STYLES } from "../styles/shared.js";
|
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { jsonOf } from "../lib/attributes.js";
|
|
4
4
|
import { registerIcons } from "../lib/icons.js";
|
|
5
|
-
import { JlIcon } from "../atoms/jl-icon.js";
|
|
6
5
|
import { starredParts } from "../lib/text.js";
|
|
6
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
7
7
|
import jl_auth_layout_default from "./jl-auth-layout.css_raw.js";
|
|
8
8
|
import { css } from "@jadis/core";
|
|
9
9
|
import { Check } from "lucide";
|