@syware/jlib 0.0.0-stage → 0.1.3
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/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/all.d.ts +1 -0
- package/dist/all.js +75 -0
- package/dist/atoms/jl-avatar.css_raw.js +3 -0
- package/dist/atoms/jl-avatar.d.ts +52 -0
- package/dist/atoms/jl-avatar.js +167 -0
- package/dist/atoms/jl-badge.css_raw.js +3 -0
- package/dist/atoms/jl-badge.d.ts +32 -0
- package/dist/atoms/jl-badge.js +60 -0
- package/dist/atoms/jl-button.css_raw.js +3 -0
- package/dist/atoms/jl-button.d.ts +70 -0
- package/dist/atoms/jl-button.js +146 -0
- package/dist/atoms/jl-checkbox.css_raw.js +3 -0
- package/dist/atoms/jl-checkbox.d.ts +61 -0
- package/dist/atoms/jl-checkbox.js +149 -0
- package/dist/atoms/jl-chip.css_raw.js +3 -0
- package/dist/atoms/jl-chip.d.ts +59 -0
- package/dist/atoms/jl-chip.js +164 -0
- package/dist/atoms/jl-divider.css_raw.js +3 -0
- package/dist/atoms/jl-divider.d.ts +26 -0
- package/dist/atoms/jl-divider.js +59 -0
- package/dist/atoms/jl-icon.css_raw.js +3 -0
- package/dist/atoms/jl-icon.d.ts +25 -0
- package/dist/atoms/jl-icon.js +41 -0
- package/dist/atoms/jl-input.css_raw.js +3 -0
- package/dist/atoms/jl-input.d.ts +112 -0
- package/dist/atoms/jl-input.js +202 -0
- package/dist/atoms/jl-kbd.css_raw.js +3 -0
- package/dist/atoms/jl-kbd.d.ts +17 -0
- package/dist/atoms/jl-kbd.js +62 -0
- package/dist/atoms/jl-link.css_raw.js +3 -0
- package/dist/atoms/jl-link.d.ts +37 -0
- package/dist/atoms/jl-link.js +86 -0
- package/dist/atoms/jl-progress-ring.css_raw.js +3 -0
- package/dist/atoms/jl-progress-ring.d.ts +49 -0
- package/dist/atoms/jl-progress-ring.js +132 -0
- package/dist/atoms/jl-progress.css_raw.js +3 -0
- package/dist/atoms/jl-progress.d.ts +43 -0
- package/dist/atoms/jl-progress.js +109 -0
- package/dist/atoms/jl-rating.css_raw.js +3 -0
- package/dist/atoms/jl-rating.d.ts +73 -0
- package/dist/atoms/jl-rating.js +184 -0
- package/dist/atoms/jl-select.css_raw.js +3 -0
- package/dist/atoms/jl-select.d.ts +70 -0
- package/dist/atoms/jl-select.js +146 -0
- package/dist/atoms/jl-skeleton.css_raw.js +3 -0
- package/dist/atoms/jl-skeleton.d.ts +34 -0
- package/dist/atoms/jl-skeleton.js +63 -0
- package/dist/atoms/jl-slider.css_raw.js +3 -0
- package/dist/atoms/jl-slider.d.ts +65 -0
- package/dist/atoms/jl-slider.js +143 -0
- package/dist/atoms/jl-spinner.css_raw.js +3 -0
- package/dist/atoms/jl-spinner.d.ts +16 -0
- package/dist/atoms/jl-spinner.js +26 -0
- package/dist/atoms/jl-switch.css_raw.js +3 -0
- package/dist/atoms/jl-switch.d.ts +57 -0
- package/dist/atoms/jl-switch.js +125 -0
- package/dist/atoms/jl-textarea.css_raw.js +3 -0
- package/dist/atoms/jl-textarea.d.ts +81 -0
- package/dist/atoms/jl-textarea.js +148 -0
- package/dist/base/jl-element.d.ts +16 -0
- package/dist/base/jl-element.js +35 -0
- package/dist/base/jl-form-control.d.ts +29 -0
- package/dist/base/jl-form-control.js +44 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +72 -0
- package/dist/lib/arrow.d.ts +6 -0
- package/dist/lib/arrow.js +15 -0
- package/dist/lib/attributes.d.ts +9 -0
- package/dist/lib/attributes.js +22 -0
- package/dist/lib/avatar.d.ts +11 -0
- package/dist/lib/avatar.js +37 -0
- package/dist/lib/calendar.d.ts +48 -0
- package/dist/lib/calendar.js +166 -0
- package/dist/lib/css-length.d.ts +2 -0
- package/dist/lib/css-length.js +8 -0
- package/dist/lib/define.d.ts +9 -0
- package/dist/lib/define.js +9 -0
- package/dist/lib/dom.d.ts +4 -0
- package/dist/lib/dom.js +16 -0
- package/dist/lib/files.d.ts +18 -0
- package/dist/lib/files.js +89 -0
- package/dist/lib/floating.d.ts +13 -0
- package/dist/lib/floating.js +127 -0
- package/dist/lib/fuzzy.d.ts +26 -0
- package/dist/lib/fuzzy.js +149 -0
- package/dist/lib/highlight.d.ts +5 -0
- package/dist/lib/highlight.js +267 -0
- package/dist/lib/hotkey.d.ts +9 -0
- package/dist/lib/hotkey.js +101 -0
- package/dist/lib/icon-set.d.ts +95 -0
- package/dist/lib/icon-set.js +97 -0
- package/dist/lib/icons.d.ts +12 -0
- package/dist/lib/icons.js +39 -0
- package/dist/lib/indicator.d.ts +6 -0
- package/dist/lib/indicator.js +18 -0
- package/dist/lib/keep-in-view.d.ts +5 -0
- package/dist/lib/keep-in-view.js +15 -0
- package/dist/lib/key-shortcut.d.ts +20 -0
- package/dist/lib/key-shortcut.js +100 -0
- package/dist/lib/keycaps.d.ts +8 -0
- package/dist/lib/keycaps.js +162 -0
- package/dist/lib/locale.d.ts +4 -0
- package/dist/lib/locale.js +13 -0
- package/dist/lib/nav-tree.d.ts +9 -0
- package/dist/lib/nav-tree.js +30 -0
- package/dist/lib/numeric.d.ts +20 -0
- package/dist/lib/numeric.js +50 -0
- package/dist/lib/option-filter.d.ts +29 -0
- package/dist/lib/option-filter.js +108 -0
- package/dist/lib/option-groups.d.ts +6 -0
- package/dist/lib/option-groups.js +26 -0
- package/dist/lib/options.d.ts +5 -0
- package/dist/lib/options.js +24 -0
- package/dist/lib/pagination.d.ts +9 -0
- package/dist/lib/pagination.js +52 -0
- package/dist/lib/palette.d.ts +8 -0
- package/dist/lib/palette.js +46 -0
- package/dist/lib/progress.d.ts +20 -0
- package/dist/lib/progress.js +33 -0
- package/dist/lib/rating.d.ts +7 -0
- package/dist/lib/rating.js +26 -0
- package/dist/lib/roving-index.d.ts +10 -0
- package/dist/lib/roving-index.js +32 -0
- package/dist/lib/roving.d.ts +11 -0
- package/dist/lib/roving.js +21 -0
- package/dist/lib/slots.d.ts +2 -0
- package/dist/lib/slots.js +6 -0
- package/dist/lib/sparkline.d.ts +11 -0
- package/dist/lib/sparkline.js +42 -0
- package/dist/lib/split.d.ts +12 -0
- package/dist/lib/split.js +37 -0
- package/dist/lib/surface.d.ts +7 -0
- package/dist/lib/surface.js +28 -0
- package/dist/lib/table.d.ts +30 -0
- package/dist/lib/table.js +110 -0
- package/dist/lib/tags.d.ts +14 -0
- package/dist/lib/tags.js +29 -0
- package/dist/lib/text.d.ts +5 -0
- package/dist/lib/text.js +13 -0
- package/dist/lib/tokens.d.ts +5 -0
- package/dist/lib/tokens.js +15 -0
- package/dist/lib/tree.d.ts +16 -0
- package/dist/lib/tree.js +89 -0
- package/dist/lib/trend.d.ts +3 -0
- package/dist/lib/trend.js +15 -0
- package/dist/lib/trigger.d.ts +11 -0
- package/dist/lib/trigger.js +34 -0
- package/dist/molecules/jl-accordion-item.css_raw.js +3 -0
- package/dist/molecules/jl-accordion-item.d.ts +34 -0
- package/dist/molecules/jl-accordion-item.js +82 -0
- package/dist/molecules/jl-accordion.css_raw.js +3 -0
- package/dist/molecules/jl-accordion.d.ts +27 -0
- package/dist/molecules/jl-accordion.js +61 -0
- package/dist/molecules/jl-alert.css_raw.js +3 -0
- package/dist/molecules/jl-alert.d.ts +38 -0
- package/dist/molecules/jl-alert.js +131 -0
- package/dist/molecules/jl-avatar-group.css_raw.js +3 -0
- package/dist/molecules/jl-avatar-group.d.ts +28 -0
- package/dist/molecules/jl-avatar-group.js +69 -0
- package/dist/molecules/jl-breadcrumb.css_raw.js +3 -0
- package/dist/molecules/jl-breadcrumb.d.ts +35 -0
- package/dist/molecules/jl-breadcrumb.js +126 -0
- package/dist/molecules/jl-calendar.css_raw.js +3 -0
- package/dist/molecules/jl-calendar.d.ts +101 -0
- package/dist/molecules/jl-calendar.js +314 -0
- package/dist/molecules/jl-card.css_raw.js +3 -0
- package/dist/molecules/jl-card.d.ts +46 -0
- package/dist/molecules/jl-card.js +172 -0
- package/dist/molecules/jl-code-block.css_raw.js +3 -0
- package/dist/molecules/jl-code-block.d.ts +44 -0
- package/dist/molecules/jl-code-block.js +106 -0
- package/dist/molecules/jl-combobox.css_raw.js +3 -0
- package/dist/molecules/jl-combobox.d.ts +120 -0
- package/dist/molecules/jl-combobox.js +450 -0
- package/dist/molecules/jl-copy-button.css_raw.js +3 -0
- package/dist/molecules/jl-copy-button.d.ts +31 -0
- package/dist/molecules/jl-copy-button.js +69 -0
- package/dist/molecules/jl-date-picker.css_raw.js +3 -0
- package/dist/molecules/jl-date-picker.d.ts +109 -0
- package/dist/molecules/jl-date-picker.js +322 -0
- package/dist/molecules/jl-empty.css_raw.js +3 -0
- package/dist/molecules/jl-empty.d.ts +32 -0
- package/dist/molecules/jl-empty.js +94 -0
- package/dist/molecules/jl-field.css_raw.js +3 -0
- package/dist/molecules/jl-field.d.ts +49 -0
- package/dist/molecules/jl-field.js +147 -0
- package/dist/molecules/jl-file-drop.css_raw.js +3 -0
- package/dist/molecules/jl-file-drop.d.ts +93 -0
- package/dist/molecules/jl-file-drop.js +366 -0
- package/dist/molecules/jl-list-item.css_raw.js +3 -0
- package/dist/molecules/jl-list-item.d.ts +56 -0
- package/dist/molecules/jl-list-item.js +176 -0
- package/dist/molecules/jl-menu.css_raw.js +3 -0
- package/dist/molecules/jl-menu.d.ts +62 -0
- package/dist/molecules/jl-menu.js +288 -0
- package/dist/molecules/jl-number-input.css_raw.js +3 -0
- package/dist/molecules/jl-number-input.d.ts +81 -0
- package/dist/molecules/jl-number-input.js +220 -0
- package/dist/molecules/jl-pagination.css_raw.js +3 -0
- package/dist/molecules/jl-pagination.d.ts +59 -0
- package/dist/molecules/jl-pagination.js +172 -0
- package/dist/molecules/jl-popover.css_raw.js +3 -0
- package/dist/molecules/jl-popover.d.ts +49 -0
- package/dist/molecules/jl-popover.js +152 -0
- package/dist/molecules/jl-radio-group.css_raw.js +3 -0
- package/dist/molecules/jl-radio-group.d.ts +71 -0
- package/dist/molecules/jl-radio-group.js +202 -0
- package/dist/molecules/jl-search.css_raw.js +3 -0
- package/dist/molecules/jl-search.d.ts +71 -0
- package/dist/molecules/jl-search.js +210 -0
- package/dist/molecules/jl-segmented.css_raw.js +3 -0
- package/dist/molecules/jl-segmented.d.ts +65 -0
- package/dist/molecules/jl-segmented.js +187 -0
- package/dist/molecules/jl-stat.css_raw.js +3 -0
- package/dist/molecules/jl-stat.d.ts +55 -0
- package/dist/molecules/jl-stat.js +169 -0
- package/dist/molecules/jl-steps.css_raw.js +3 -0
- package/dist/molecules/jl-steps.d.ts +44 -0
- package/dist/molecules/jl-steps.js +118 -0
- package/dist/molecules/jl-tab-panel.css_raw.js +3 -0
- package/dist/molecules/jl-tab-panel.d.ts +35 -0
- package/dist/molecules/jl-tab-panel.js +35 -0
- package/dist/molecules/jl-tabs.css_raw.js +3 -0
- package/dist/molecules/jl-tabs.d.ts +51 -0
- package/dist/molecules/jl-tabs.js +198 -0
- package/dist/molecules/jl-tag-input.css_raw.js +3 -0
- package/dist/molecules/jl-tag-input.d.ts +103 -0
- package/dist/molecules/jl-tag-input.js +429 -0
- package/dist/molecules/jl-timeline.css_raw.js +3 -0
- package/dist/molecules/jl-timeline.d.ts +28 -0
- package/dist/molecules/jl-timeline.js +81 -0
- package/dist/molecules/jl-toast.css_raw.js +3 -0
- package/dist/molecules/jl-toast.d.ts +75 -0
- package/dist/molecules/jl-toast.js +274 -0
- package/dist/molecules/jl-tooltip.css_raw.js +3 -0
- package/dist/molecules/jl-tooltip.d.ts +44 -0
- package/dist/molecules/jl-tooltip.js +160 -0
- package/dist/organisms/jl-command-palette.css_raw.js +3 -0
- package/dist/organisms/jl-command-palette.d.ts +60 -0
- package/dist/organisms/jl-command-palette.js +285 -0
- package/dist/organisms/jl-confirm.css_raw.js +3 -0
- package/dist/organisms/jl-confirm.d.ts +56 -0
- package/dist/organisms/jl-confirm.js +132 -0
- package/dist/organisms/jl-dialog.css_raw.js +3 -0
- package/dist/organisms/jl-dialog.d.ts +43 -0
- package/dist/organisms/jl-dialog.js +120 -0
- package/dist/organisms/jl-drawer.css_raw.js +3 -0
- package/dist/organisms/jl-drawer.d.ts +50 -0
- package/dist/organisms/jl-drawer.js +141 -0
- package/dist/organisms/jl-navbar.css_raw.js +3 -0
- package/dist/organisms/jl-navbar.d.ts +32 -0
- package/dist/organisms/jl-navbar.js +121 -0
- package/dist/organisms/jl-page-header.css_raw.js +3 -0
- package/dist/organisms/jl-page-header.d.ts +26 -0
- package/dist/organisms/jl-page-header.js +85 -0
- package/dist/organisms/jl-side-nav.css_raw.js +3 -0
- package/dist/organisms/jl-side-nav.d.ts +60 -0
- package/dist/organisms/jl-side-nav.js +269 -0
- package/dist/organisms/jl-table.css_raw.js +3 -0
- package/dist/organisms/jl-table.d.ts +109 -0
- package/dist/organisms/jl-table.js +318 -0
- package/dist/organisms/jl-toaster.css_raw.js +3 -0
- package/dist/organisms/jl-toaster.d.ts +48 -0
- package/dist/organisms/jl-toaster.js +142 -0
- package/dist/organisms/jl-tree.css_raw.js +3 -0
- package/dist/organisms/jl-tree.d.ts +59 -0
- package/dist/organisms/jl-tree.js +198 -0
- package/dist/styles/base.css_raw.js +3 -0
- package/dist/styles/control.css_raw.js +3 -0
- package/dist/styles/floating.css_raw.js +3 -0
- package/dist/styles/shared.d.ts +8 -0
- package/dist/styles/shared.js +14 -0
- package/dist/styles/tokens.css_raw.js +3 -0
- package/dist/templates/jl-app-shell.css_raw.js +3 -0
- package/dist/templates/jl-app-shell.d.ts +37 -0
- package/dist/templates/jl-app-shell.js +150 -0
- package/dist/templates/jl-split-view.css_raw.js +3 -0
- package/dist/templates/jl-split-view.d.ts +49 -0
- package/dist/templates/jl-split-view.js +149 -0
- package/dist/tokens.css +188 -0
- package/dist/types/calendar.types.d.ts +11 -0
- package/dist/types/files.types.d.ts +23 -0
- package/dist/types/floating.types.d.ts +13 -0
- package/dist/types/fuzzy.types.d.ts +10 -0
- package/dist/types/highlight.types.d.ts +6 -0
- package/dist/types/hotkey.types.d.ts +19 -0
- package/dist/types/jl-accordion.types.d.ts +2 -0
- package/dist/types/jl-avatar.types.d.ts +10 -0
- package/dist/types/jl-breadcrumb.types.d.ts +6 -0
- package/dist/types/jl-button.types.d.ts +3 -0
- package/dist/types/jl-card.types.d.ts +2 -0
- package/dist/types/jl-chip.types.d.ts +1 -0
- package/dist/types/jl-combobox.types.d.ts +8 -0
- package/dist/types/jl-command-palette.types.d.ts +22 -0
- package/dist/types/jl-confirm.types.d.ts +10 -0
- package/dist/types/jl-dialog.types.d.ts +1 -0
- package/dist/types/jl-divider.types.d.ts +2 -0
- package/dist/types/jl-drawer.types.d.ts +4 -0
- package/dist/types/jl-empty.types.d.ts +2 -0
- package/dist/types/jl-field.types.d.ts +2 -0
- package/dist/types/jl-input.types.d.ts +1 -0
- package/dist/types/jl-link.types.d.ts +2 -0
- package/dist/types/jl-menu.types.d.ts +22 -0
- package/dist/types/jl-navbar.types.d.ts +6 -0
- package/dist/types/jl-progress.types.d.ts +3 -0
- package/dist/types/jl-radio-group.types.d.ts +10 -0
- package/dist/types/jl-segmented.types.d.ts +7 -0
- package/dist/types/jl-select.types.d.ts +12 -0
- package/dist/types/jl-side-nav.types.d.ts +17 -0
- package/dist/types/jl-skeleton.types.d.ts +2 -0
- package/dist/types/jl-slider.types.d.ts +5 -0
- package/dist/types/jl-split-view.types.d.ts +2 -0
- package/dist/types/jl-stat.types.d.ts +2 -0
- package/dist/types/jl-steps.types.d.ts +8 -0
- package/dist/types/jl-switch.types.d.ts +1 -0
- package/dist/types/jl-table.types.d.ts +30 -0
- package/dist/types/jl-tabs.types.d.ts +2 -0
- package/dist/types/jl-timeline.types.d.ts +10 -0
- package/dist/types/jl-toast.types.d.ts +2 -0
- package/dist/types/jl-toaster.types.d.ts +23 -0
- package/dist/types/jl-tree.types.d.ts +26 -0
- package/dist/types/key-shortcut.types.d.ts +9 -0
- package/dist/types/keycaps.types.d.ts +7 -0
- package/dist/types/option-filter.types.d.ts +10 -0
- package/dist/types/options.types.d.ts +4 -0
- package/dist/types/pagination.types.d.ts +8 -0
- package/dist/types/roving-index.types.d.ts +2 -0
- package/dist/types/sparkline.types.d.ts +11 -0
- package/package.json +102 -4
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { jsonOf } from "../lib/attributes.js";
|
|
4
|
+
import { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { setOrRemove, uniqueId } from "../lib/dom.js";
|
|
6
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
7
|
+
import { findItem, firstLeaf, pathInSections } from "../lib/nav-tree.js";
|
|
8
|
+
import jl_side_nav_default from "./jl-side-nav.css_raw.js";
|
|
9
|
+
import { css } from "@jadis/core";
|
|
10
|
+
import { ChevronRight } from "lucide";
|
|
11
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
12
|
+
// src/organisms/jl-side-nav.tsx
|
|
13
|
+
registerIcons({ "chevron-right": ChevronRight });
|
|
14
|
+
var ICON_SIZE = 16;
|
|
15
|
+
var CHEVRON_SIZE = 14;
|
|
16
|
+
var DEFAULT_LABEL = "Main";
|
|
17
|
+
var ROW_SELECTOR = ".item";
|
|
18
|
+
/** A JSON attribute that should hold a list of sections; anything else gives none. */
|
|
19
|
+
var parseSections = (value) => {
|
|
20
|
+
const parsed = jsonOf([])(value);
|
|
21
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
22
|
+
};
|
|
23
|
+
var hasChildren = (item) => (item.children?.length ?? 0) > 0;
|
|
24
|
+
/** A row is reachable when no closed list hides it. */
|
|
25
|
+
var isReachable = (row) => row.closest("[hidden]") === null && row.offsetParent !== null;
|
|
26
|
+
/**
|
|
27
|
+
* The navigation of an app, down its side: sections of items with icons, badges and nested items that open with
|
|
28
|
+
* a chevron. `value` is the active item (its parents open to show it); `collapsed` makes it a rail of icons, with
|
|
29
|
+
* the labels as tooltips. Items with an `href` are links; the others fire `select`.
|
|
30
|
+
*/
|
|
31
|
+
var JlSideNav = class extends JlElement {
|
|
32
|
+
static selector = "jl-side-nav";
|
|
33
|
+
events = this.useEvents();
|
|
34
|
+
sections = this.useChange([], () => this.render(), {
|
|
35
|
+
attribute: "sections",
|
|
36
|
+
parse: parseSections
|
|
37
|
+
});
|
|
38
|
+
/** The id of the active item. */
|
|
39
|
+
value = this.useChange("", (value) => this.renderActive(value), { attribute: "value" });
|
|
40
|
+
/** Shows only the icons, in a narrow rail; the labels become tooltips. */
|
|
41
|
+
collapsed = this.useChange(false, (value) => {
|
|
42
|
+
this.reflect("collapsed", value);
|
|
43
|
+
this.render();
|
|
44
|
+
}, { attribute: "collapsed" });
|
|
45
|
+
/** The name of the navigation for screen readers. */
|
|
46
|
+
label = this.useChange(DEFAULT_LABEL, (value) => {
|
|
47
|
+
this._refs.nav.setAttribute("aria-label", value === "" ? DEFAULT_LABEL : value);
|
|
48
|
+
}, { attribute: "label" });
|
|
49
|
+
_prefix = uniqueId("jl-side-nav");
|
|
50
|
+
/** The parents people opened, or that hold the active item. */
|
|
51
|
+
_state = { open: /* @__PURE__ */ new Set() };
|
|
52
|
+
_refs = this.useRefs((ref) => ({
|
|
53
|
+
nav: ref("nav"),
|
|
54
|
+
sections: ref(".sections")
|
|
55
|
+
}));
|
|
56
|
+
templateHtml() {
|
|
57
|
+
return /* @__PURE__ */ jsxs("nav", {
|
|
58
|
+
attrs: { part: "base" },
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ jsx("slot", { name: "header" }),
|
|
61
|
+
/* @__PURE__ */ jsx("div", { class: "sections" }),
|
|
62
|
+
/* @__PURE__ */ jsx("slot", { name: "footer" })
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
templateCss() {
|
|
67
|
+
return css`${BASE_STYLES}${jl_side_nav_default}`;
|
|
68
|
+
}
|
|
69
|
+
onConnect() {
|
|
70
|
+
this.on(this._refs.sections, "click", (event) => this.onClick(event));
|
|
71
|
+
this.on(this._refs.sections, "keydown", (event) => this.onKey(event));
|
|
72
|
+
}
|
|
73
|
+
/** Opens or closes a parent item. */
|
|
74
|
+
toggleItem(id, force) {
|
|
75
|
+
if (force ?? !this._state.open.has(id)) this._state.open.add(id);
|
|
76
|
+
else this._state.open.delete(id);
|
|
77
|
+
this.whenRendered(() => this.renderOpen());
|
|
78
|
+
}
|
|
79
|
+
onClick(event) {
|
|
80
|
+
const target = event.target;
|
|
81
|
+
const expander = target.closest(".expander");
|
|
82
|
+
if (expander !== null) {
|
|
83
|
+
this.toggleItem(expander.dataset.id ?? "");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const row = target.closest(ROW_SELECTOR);
|
|
87
|
+
const item = findItem(this.sections.get(), row?.dataset.id ?? "");
|
|
88
|
+
if (item === null) return;
|
|
89
|
+
if (hasChildren(item) && item.href === void 0 && !this.collapsed.get()) {
|
|
90
|
+
this.toggleItem(item.id);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.choose(row?.dataset.target ?? item.id);
|
|
94
|
+
}
|
|
95
|
+
choose(id) {
|
|
96
|
+
this.value.set(id);
|
|
97
|
+
this.events.emit("select", id);
|
|
98
|
+
}
|
|
99
|
+
/** Up and Down move between the rows people can see, Right opens a parent, Left closes it or goes up to it. */
|
|
100
|
+
onKey(event) {
|
|
101
|
+
const row = event.target.closest(ROW_SELECTOR);
|
|
102
|
+
if (row === null) return;
|
|
103
|
+
const rows = [...this._refs.sections.querySelectorAll(ROW_SELECTOR)].filter(isReachable);
|
|
104
|
+
const index = rows.indexOf(row);
|
|
105
|
+
const id = row.dataset.id ?? "";
|
|
106
|
+
const move = (/* @__PURE__ */ new Map([
|
|
107
|
+
["ArrowDown", () => rows[Math.min(rows.length - 1, index + 1)]?.focus()],
|
|
108
|
+
["ArrowLeft", () => this.closeOrGoUp(id, row)],
|
|
109
|
+
["ArrowRight", () => this.openOrGoDown(id, row)],
|
|
110
|
+
["ArrowUp", () => rows[Math.max(0, index - 1)]?.focus()],
|
|
111
|
+
["End", () => rows.at(-1)?.focus()],
|
|
112
|
+
["Home", () => rows[0]?.focus()]
|
|
113
|
+
])).get(event.key);
|
|
114
|
+
if (move !== void 0) {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
move();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
openOrGoDown(id, row) {
|
|
120
|
+
if (row.dataset.parent === void 0 || this.collapsed.get()) return;
|
|
121
|
+
if (!this._state.open.has(id)) {
|
|
122
|
+
this.toggleItem(id, true);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
row.closest("li")?.querySelector(`.children ${ROW_SELECTOR}`)?.focus();
|
|
126
|
+
}
|
|
127
|
+
closeOrGoUp(id, row) {
|
|
128
|
+
if (row.dataset.parent !== void 0 && this._state.open.has(id)) {
|
|
129
|
+
this.toggleItem(id, false);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
row.closest(".children")?.closest("li")?.querySelector(ROW_SELECTOR)?.focus();
|
|
133
|
+
}
|
|
134
|
+
render() {
|
|
135
|
+
for (const id of pathInSections(this.sections.get(), this.value.get()).slice(0, -1)) this._state.open.add(id);
|
|
136
|
+
this._refs.sections.replaceChildren(...this.sections.get().map((section, index) => this.sectionNode(section, index)));
|
|
137
|
+
this.renderOpen();
|
|
138
|
+
this.renderActive(this.value.get());
|
|
139
|
+
}
|
|
140
|
+
sectionNode(section, index) {
|
|
141
|
+
const headingId = `${this._prefix}-section-${index}`;
|
|
142
|
+
const label = section.label ?? "";
|
|
143
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
144
|
+
class: "section",
|
|
145
|
+
children: [label === "" ? null : /* @__PURE__ */ jsx("p", {
|
|
146
|
+
class: "section-label",
|
|
147
|
+
id: headingId,
|
|
148
|
+
children: label
|
|
149
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
150
|
+
class: "items",
|
|
151
|
+
role: "list",
|
|
152
|
+
"aria-labelledby": label === "" ? void 0 : headingId,
|
|
153
|
+
children: section.items.map((item) => this.itemNode(item, 0))
|
|
154
|
+
})]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
itemNode(item, depth) {
|
|
158
|
+
const parent = hasChildren(item);
|
|
159
|
+
const childrenId = `${this._prefix}-${item.id}-children`;
|
|
160
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
161
|
+
class: "entry",
|
|
162
|
+
children: [
|
|
163
|
+
this.rowNode(item, depth, childrenId),
|
|
164
|
+
parent && item.href !== void 0 && !this.collapsed.get() ? /* @__PURE__ */ jsx("button", {
|
|
165
|
+
class: "expander",
|
|
166
|
+
type: "button",
|
|
167
|
+
"data-id": item.id,
|
|
168
|
+
"aria-label": `Show the pages of ${item.label}`,
|
|
169
|
+
"aria-controls": childrenId,
|
|
170
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
171
|
+
name: "chevron-right",
|
|
172
|
+
size: CHEVRON_SIZE
|
|
173
|
+
})
|
|
174
|
+
}) : null,
|
|
175
|
+
parent ? /* @__PURE__ */ jsx("ul", {
|
|
176
|
+
class: "children",
|
|
177
|
+
id: childrenId,
|
|
178
|
+
role: "list",
|
|
179
|
+
hidden: true,
|
|
180
|
+
children: (item.children ?? []).map((child) => this.itemNode(child, depth + 1))
|
|
181
|
+
}) : null
|
|
182
|
+
]
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The row of an item: a link when it has an `href`, otherwise a button. In the rail, a parent leads to its first
|
|
187
|
+
* leaf, since its children cannot show there.
|
|
188
|
+
*/
|
|
189
|
+
rowNode(item, depth, childrenId) {
|
|
190
|
+
const collapsed = this.collapsed.get();
|
|
191
|
+
const parent = hasChildren(item);
|
|
192
|
+
const target = collapsed ? firstLeaf(item) : item;
|
|
193
|
+
const toggles = parent && item.href === void 0 && !collapsed;
|
|
194
|
+
const content = this.rowContent(item, toggles);
|
|
195
|
+
const attributes = {
|
|
196
|
+
class: "item",
|
|
197
|
+
"data-depth": String(depth),
|
|
198
|
+
"data-id": item.id,
|
|
199
|
+
"data-parent": parent ? "" : void 0,
|
|
200
|
+
"data-target": target.id,
|
|
201
|
+
title: collapsed ? item.label : void 0
|
|
202
|
+
};
|
|
203
|
+
if (target.href !== void 0) return /* @__PURE__ */ jsx("a", {
|
|
204
|
+
href: target.href,
|
|
205
|
+
...attributes,
|
|
206
|
+
children: content
|
|
207
|
+
});
|
|
208
|
+
return /* @__PURE__ */ jsx("button", {
|
|
209
|
+
type: "button",
|
|
210
|
+
"aria-expanded": toggles ? "false" : void 0,
|
|
211
|
+
"aria-controls": toggles ? childrenId : void 0,
|
|
212
|
+
...attributes,
|
|
213
|
+
children: content
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
rowContent(item, toggles) {
|
|
217
|
+
const badge = item.badge === void 0 ? "" : String(item.badge);
|
|
218
|
+
return [
|
|
219
|
+
item.icon === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
220
|
+
class: "item-initial",
|
|
221
|
+
"aria-hidden": "true",
|
|
222
|
+
children: item.label.charAt(0)
|
|
223
|
+
}) : /* @__PURE__ */ jsx(JlIcon, {
|
|
224
|
+
class: "item-icon",
|
|
225
|
+
name: item.icon,
|
|
226
|
+
size: ICON_SIZE
|
|
227
|
+
}),
|
|
228
|
+
/* @__PURE__ */ jsx("span", {
|
|
229
|
+
class: "item-label",
|
|
230
|
+
children: item.label
|
|
231
|
+
}),
|
|
232
|
+
badge === "" ? null : /* @__PURE__ */ jsx("span", {
|
|
233
|
+
class: "item-badge",
|
|
234
|
+
children: badge
|
|
235
|
+
}),
|
|
236
|
+
toggles ? /* @__PURE__ */ jsx(JlIcon, {
|
|
237
|
+
class: "chevron",
|
|
238
|
+
name: "chevron-right",
|
|
239
|
+
size: CHEVRON_SIZE
|
|
240
|
+
}) : null
|
|
241
|
+
].filter((node) => node !== null);
|
|
242
|
+
}
|
|
243
|
+
/** Shows the children of open parents, and says so on their chevrons. */
|
|
244
|
+
renderOpen() {
|
|
245
|
+
for (const list of this._refs.sections.querySelectorAll(".children")) {
|
|
246
|
+
const entry = list.closest("li");
|
|
247
|
+
const id = (entry?.querySelector(ROW_SELECTOR))?.dataset.id ?? "";
|
|
248
|
+
const open = this._state.open.has(id);
|
|
249
|
+
list.hidden = !open;
|
|
250
|
+
entry?.classList.toggle("open", open);
|
|
251
|
+
for (const control of entry?.querySelectorAll(`[aria-controls="${list.id}"]`) ?? []) control.setAttribute("aria-expanded", String(open));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** Marks the active row, and the parents above it; opens them so it shows. */
|
|
255
|
+
renderActive(value) {
|
|
256
|
+
const path = pathInSections(this.sections.get(), value);
|
|
257
|
+
const opened = path.slice(0, -1).filter((id) => !this._state.open.has(id));
|
|
258
|
+
for (const id of opened) this._state.open.add(id);
|
|
259
|
+
if (opened.length > 0) this.renderOpen();
|
|
260
|
+
for (const row of this._refs.sections.querySelectorAll(ROW_SELECTOR)) {
|
|
261
|
+
const id = row.dataset.id ?? "";
|
|
262
|
+
const active = id === value || this.collapsed.get() && row.dataset.target === value;
|
|
263
|
+
setOrRemove(row, "aria-current", active ? "page" : "");
|
|
264
|
+
row.classList.toggle("holds-active", !active && path.includes(id));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
JlSideNav.register();
|
|
269
|
+
export { JlSideNav };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/organisms/jl-table.css?raw
|
|
2
|
+
var jl_table_default = ":host {\n display: block;\n /* The table scrolls sideways inside its frame: its width never pushes the page, or a grid around it, wider. */\n contain: inline-size;\n --max-height: none;\n --cell-y: 10px;\n --cell-x: 14px;\n}\n\n:host([dense]) {\n --cell-y: 6px;\n --cell-x: 12px;\n}\n\n.scroll {\n max-height: var(--max-height);\n overflow: auto;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius);\n background: var(--jl-surface);\n overscroll-behavior-x: contain;\n}\n\ntable {\n width: 100%;\n border-collapse: separate;\n border-spacing: 0;\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n line-height: 20px;\n}\n\nth,\ntd {\n padding: var(--cell-y) var(--cell-x);\n white-space: nowrap;\n vertical-align: middle;\n}\n\nth {\n position: sticky;\n top: 0;\n z-index: 1;\n border-bottom: 1px solid var(--jl-line);\n background: var(--jl-surface-2);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-weight: 600;\n letter-spacing: 0.01em;\n text-align: left;\n}\n\ntd {\n border-top: 1px solid var(--jl-line-soft);\n}\n\ntbody tr:first-child td {\n border-top: none;\n}\n\ntbody tr.empty td {\n padding: 40px var(--cell-x);\n color: var(--jl-muted);\n text-align: center;\n}\n\n.align-left {\n text-align: left;\n}\n\n.align-center {\n text-align: center;\n}\n\n.align-right {\n text-align: right;\n}\n\n.sort {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n margin: -2px -4px;\n padding: 2px 4px;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n color: inherit;\n font-weight: inherit;\n letter-spacing: inherit;\n cursor: pointer;\n}\n\n.sort:hover {\n color: var(--jl-text);\n}\n\n.sort-icon {\n color: var(--jl-faint);\n}\n\nth[data-sorted] {\n color: var(--jl-text);\n}\n\nth[data-sorted] .sort-icon {\n color: var(--jl-accent);\n}\n\n/* Selected rows keep their shade: the hover and the stripes leave them alone. */\ntbody tr[data-selected] td {\n background: var(--jl-accent-soft);\n}\n\ntbody tr:not(.empty, .skeleton, [data-selected]):hover td {\n background: var(--jl-surface-2);\n}\n\n:host([striped]) tbody tr:nth-child(even):not(.empty, [data-selected]) td {\n background: var(--jl-surface-2);\n}\n\n:host([striped]) tbody tr:not(.empty, .skeleton, [data-selected]):hover td {\n background: var(--jl-surface-3);\n}\n\n.format-number {\n font-variant-numeric: tabular-nums;\n}\n\n.format-date {\n color: var(--jl-text-2);\n font-variant-numeric: tabular-nums;\n}\n\ncode {\n padding: 2px 6px;\n border-radius: var(--jl-radius-xs);\n background: var(--jl-code-bg);\n font-size: var(--jl-font-xs);\n}\n\n.select-cell {\n width: 44px;\n padding-right: 0;\n line-height: 0;\n}\n\n/* The checkbox: the native input keeps the keyboard and the states, the mark beside it draws the tick. */\n.check {\n position: relative;\n display: inline-flex;\n}\n\n.checkbox {\n width: 16px;\n height: 16px;\n margin: 0;\n border: 1.5px solid var(--jl-line-strong);\n border-radius: 4px;\n background: var(--jl-surface);\n cursor: pointer;\n appearance: none;\n transition:\n background var(--jl-fast) var(--jl-ease),\n border-color var(--jl-fast) var(--jl-ease);\n}\n\n.checkbox:hover {\n border-color: var(--jl-muted);\n}\n\n.checkbox:checked,\n.checkbox:indeterminate {\n border-color: var(--jl-accent);\n background: var(--jl-accent);\n}\n\n.checkbox:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.mark {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.checkbox:checked + .mark::after {\n position: absolute;\n top: 2.5px;\n left: 5.5px;\n width: 5px;\n height: 9px;\n border: solid var(--jl-on-accent);\n border-width: 0 2px 2px 0;\n rotate: 45deg;\n content: \"\";\n}\n\n.checkbox:indeterminate + .mark::after {\n position: absolute;\n top: 7px;\n right: 4px;\n left: 4px;\n height: 2px;\n border-radius: 1px;\n background: var(--jl-on-accent);\n content: \"\";\n}\n\n.empty-content {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n\n.empty jl-icon {\n color: var(--jl-faint);\n}\n\n.bone {\n display: inline-block;\n width: 70%;\n max-width: 160px;\n height: 10px;\n border-radius: var(--jl-radius-pill);\n background:\n linear-gradient(90deg, var(--jl-surface-3) 30%, var(--jl-surface-2) 50%, var(--jl-surface-3) 70%) 0 0 / 300%\n 100%;\n vertical-align: middle;\n animation: shimmer 1.4s linear infinite;\n}\n\n.bone.box {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n}\n\ntbody tr.skeleton:nth-child(3n + 2) .bone:not(.box) {\n width: 50%;\n}\n\ntbody tr.skeleton:nth-child(3n) .bone:not(.box) {\n width: 85%;\n}\n\n/* Shortened to a millisecond by the base styles, an endless shimmer would flicker: it stands still instead. */\n@media (prefers-reduced-motion: reduce) {\n .bone {\n animation: none;\n }\n}\n\n@keyframes shimmer {\n from {\n background-position: 100% 0;\n }\n\n to {\n background-position: 0 0;\n }\n}\n";
|
|
3
|
+
export { jl_table_default as default };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { TableCellRenderer, TableColumn, TableRow, TableRowKey, TableSort, TableSortDirection } from '../types/jl-table.types';
|
|
3
|
+
export declare const TABLE_SORT_DIRECTIONS: readonly TableSortDirection[];
|
|
4
|
+
/**
|
|
5
|
+
* Rows of data under column headings. `columns` say what each column shows and how (`format`: text, number, date,
|
|
6
|
+
* badge, code); `rows` are plain objects. Sortable headings cycle ascending, descending and unsorted, and sort the
|
|
7
|
+
* rows in place; `selectable` adds a checkbox column. The header stays on top while the rows scroll within
|
|
8
|
+
* `max-height`, and the table scrolls sideways on narrow screens. Cells of a column can be drawn by code with
|
|
9
|
+
* `renderers`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class JlTable extends JlElement {
|
|
12
|
+
static readonly selector = "jl-table";
|
|
13
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
14
|
+
sort: TableSort;
|
|
15
|
+
selection: TableRowKey[];
|
|
16
|
+
}>>;
|
|
17
|
+
readonly columns: Readonly<Readonly<{
|
|
18
|
+
get(): TableColumn[];
|
|
19
|
+
set(setter: TableColumn[] | ((prevState: TableColumn[]) => TableColumn[])): void;
|
|
20
|
+
}>>;
|
|
21
|
+
readonly rows: Readonly<Readonly<{
|
|
22
|
+
get(): TableRow[];
|
|
23
|
+
set(setter: TableRow[] | ((prevState: TableRow[]) => TableRow[])): void;
|
|
24
|
+
}>>;
|
|
25
|
+
/** The field that tells rows apart, for the selection. */
|
|
26
|
+
readonly rowKey: Readonly<Readonly<{
|
|
27
|
+
get(): string;
|
|
28
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
29
|
+
}>>;
|
|
30
|
+
/** The column the rows are sorted by. */
|
|
31
|
+
readonly sortKey: Readonly<Readonly<{
|
|
32
|
+
get(): string;
|
|
33
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
34
|
+
}>>;
|
|
35
|
+
readonly sortDirection: Readonly<Readonly<{
|
|
36
|
+
get(): TableSortDirection;
|
|
37
|
+
set(setter: ((prevState: TableSortDirection) => TableSortDirection) | TableSortDirection): void;
|
|
38
|
+
}>>;
|
|
39
|
+
/** Adds a checkbox to each row, and one in the header that selects them all. */
|
|
40
|
+
readonly selectable: Readonly<Readonly<{
|
|
41
|
+
get(): boolean;
|
|
42
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
43
|
+
}>>;
|
|
44
|
+
/** The keys of the selected rows. */
|
|
45
|
+
readonly selected: Readonly<Readonly<{
|
|
46
|
+
get(): TableRowKey[];
|
|
47
|
+
set(setter: TableRowKey[] | ((prevState: TableRowKey[]) => TableRowKey[])): void;
|
|
48
|
+
}>>;
|
|
49
|
+
/** Shorter rows, for long lists. */
|
|
50
|
+
readonly dense: Readonly<Readonly<{
|
|
51
|
+
get(): boolean;
|
|
52
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
53
|
+
}>>;
|
|
54
|
+
/** Shades every other row. */
|
|
55
|
+
readonly striped: Readonly<Readonly<{
|
|
56
|
+
get(): boolean;
|
|
57
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
58
|
+
}>>;
|
|
59
|
+
/** A CSS height past which the rows scroll under the header: `320px`, `50vh`; a number is pixels. */
|
|
60
|
+
readonly maxHeight: Readonly<Readonly<{
|
|
61
|
+
get(): string;
|
|
62
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
63
|
+
}>>;
|
|
64
|
+
/** What the table says when it has no rows. */
|
|
65
|
+
readonly emptyText: Readonly<Readonly<{
|
|
66
|
+
get(): string;
|
|
67
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
68
|
+
}>>;
|
|
69
|
+
/** Shows placeholder rows while the data loads. */
|
|
70
|
+
readonly loading: Readonly<Readonly<{
|
|
71
|
+
get(): boolean;
|
|
72
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
73
|
+
}>>;
|
|
74
|
+
/** The table's name for screen readers. */
|
|
75
|
+
readonly label: Readonly<Readonly<{
|
|
76
|
+
get(): string;
|
|
77
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
78
|
+
}>>;
|
|
79
|
+
/** Draws the cells of some columns by code, by column key: `{ name: (row) => <strong>{row.name}</strong> }`. */
|
|
80
|
+
readonly renderers: Readonly<Readonly<{
|
|
81
|
+
get(): Record<string, TableCellRenderer>;
|
|
82
|
+
set(setter: Record<string, TableCellRenderer> | ((prevState: Record<string, TableCellRenderer>) => Record<string, TableCellRenderer>)): void;
|
|
83
|
+
}>>;
|
|
84
|
+
private readonly _pending;
|
|
85
|
+
/** The keys of the rows as drawn, by their place: the checkboxes carry the place. */
|
|
86
|
+
private readonly _view;
|
|
87
|
+
private readonly _refs;
|
|
88
|
+
templateHtml(): Node;
|
|
89
|
+
templateCss(): string;
|
|
90
|
+
onConnect(): void;
|
|
91
|
+
/** Draws on the next microtask: the inputs set while connecting give one drawing, not one each. */
|
|
92
|
+
private schedule;
|
|
93
|
+
private flush;
|
|
94
|
+
private sortBy;
|
|
95
|
+
private changeSelection;
|
|
96
|
+
private renderMaxHeight;
|
|
97
|
+
private renderHead;
|
|
98
|
+
private headerCell;
|
|
99
|
+
/** The arrows and `aria-sort` of the headings, changed in place so a pressed heading keeps the focus. */
|
|
100
|
+
private renderSortState;
|
|
101
|
+
private sortedRows;
|
|
102
|
+
private renderBody;
|
|
103
|
+
private bodyRow;
|
|
104
|
+
private cell;
|
|
105
|
+
private skeletonRows;
|
|
106
|
+
private emptyRow;
|
|
107
|
+
/** The checkboxes and the shading of selected rows, changed in place. */
|
|
108
|
+
private renderSelection;
|
|
109
|
+
}
|