@syware/jlib 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +116 -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 +96 -4
|
@@ -0,0 +1,318 @@
|
|
|
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 { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
6
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
7
|
+
import { JlBadge } from "../atoms/jl-badge.js";
|
|
8
|
+
import { columnAlign, columnWidth, formatCell, nextSortDirection, rowKeyOf, selectionState, sortRows, toggleAll, toggleKey } from "../lib/table.js";
|
|
9
|
+
import jl_table_default from "./jl-table.css_raw.js";
|
|
10
|
+
import { css } from "@jadis/core";
|
|
11
|
+
import { ArrowDown, ArrowUp, ChevronsUpDown, Inbox } from "lucide";
|
|
12
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
13
|
+
// src/organisms/jl-table.tsx
|
|
14
|
+
registerIcons({
|
|
15
|
+
"arrow-down": ArrowDown,
|
|
16
|
+
"arrow-up": ArrowUp,
|
|
17
|
+
"chevrons-up-down": ChevronsUpDown,
|
|
18
|
+
inbox: Inbox
|
|
19
|
+
});
|
|
20
|
+
var TABLE_SORT_DIRECTIONS = [
|
|
21
|
+
"none",
|
|
22
|
+
"asc",
|
|
23
|
+
"desc"
|
|
24
|
+
];
|
|
25
|
+
var DEFAULT_ROW_KEY = "id";
|
|
26
|
+
var DEFAULT_EMPTY_TEXT = "No data";
|
|
27
|
+
var SKELETON_ROWS = 5;
|
|
28
|
+
var SORT_ICON_SIZE = 14;
|
|
29
|
+
var EMPTY_ICON_SIZE = 18;
|
|
30
|
+
var NO_SORT = "none";
|
|
31
|
+
var ARIA_SORT = {
|
|
32
|
+
asc: "ascending",
|
|
33
|
+
desc: "descending",
|
|
34
|
+
none: ""
|
|
35
|
+
};
|
|
36
|
+
var SORT_ICONS = {
|
|
37
|
+
asc: "arrow-up",
|
|
38
|
+
desc: "arrow-down",
|
|
39
|
+
none: "chevrons-up-down"
|
|
40
|
+
};
|
|
41
|
+
var CELL_MAKERS = {
|
|
42
|
+
badge: (text, column) => text === "" ? "" : /* @__PURE__ */ jsx(JlBadge, {
|
|
43
|
+
label: text,
|
|
44
|
+
tone: column.tones?.[text] ?? "neutral"
|
|
45
|
+
}),
|
|
46
|
+
code: (text) => text === "" ? "" : /* @__PURE__ */ jsx("code", { children: text }),
|
|
47
|
+
date: (text) => text,
|
|
48
|
+
number: (text) => text,
|
|
49
|
+
text: (text) => text
|
|
50
|
+
};
|
|
51
|
+
/** A checkbox drawn by the table: a native input for the keyboard and the form of things, its mark in CSS. */
|
|
52
|
+
function checkbox(label, index) {
|
|
53
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
54
|
+
class: "check",
|
|
55
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
56
|
+
type: "checkbox",
|
|
57
|
+
class: "checkbox",
|
|
58
|
+
"aria-label": label,
|
|
59
|
+
"data-index": index ?? void 0
|
|
60
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
61
|
+
class: "mark",
|
|
62
|
+
"aria-hidden": "true"
|
|
63
|
+
})]
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Rows of data under column headings. `columns` say what each column shows and how (`format`: text, number, date,
|
|
68
|
+
* badge, code); `rows` are plain objects. Sortable headings cycle ascending, descending and unsorted, and sort the
|
|
69
|
+
* rows in place; `selectable` adds a checkbox column. The header stays on top while the rows scroll within
|
|
70
|
+
* `max-height`, and the table scrolls sideways on narrow screens. Cells of a column can be drawn by code with
|
|
71
|
+
* `renderers`.
|
|
72
|
+
*/
|
|
73
|
+
var JlTable = class extends JlElement {
|
|
74
|
+
static selector = "jl-table";
|
|
75
|
+
events = this.useEvents();
|
|
76
|
+
columns = this.useChange([], () => this.schedule("head", "body"), {
|
|
77
|
+
attribute: "columns",
|
|
78
|
+
parse: jsonOf([])
|
|
79
|
+
});
|
|
80
|
+
rows = this.useChange([], () => this.schedule("body"), {
|
|
81
|
+
attribute: "rows",
|
|
82
|
+
parse: jsonOf([])
|
|
83
|
+
});
|
|
84
|
+
/** The field that tells rows apart, for the selection. */
|
|
85
|
+
rowKey = this.useChange(DEFAULT_ROW_KEY, () => this.schedule("body"), { attribute: "row-key" });
|
|
86
|
+
/** The column the rows are sorted by. */
|
|
87
|
+
sortKey = this.useChange("", () => this.schedule("body"), { attribute: "sort-key" });
|
|
88
|
+
sortDirection = this.useChange(NO_SORT, () => this.schedule("body"), {
|
|
89
|
+
attribute: "sort-direction",
|
|
90
|
+
parse: oneOf(TABLE_SORT_DIRECTIONS, NO_SORT)
|
|
91
|
+
});
|
|
92
|
+
/** Adds a checkbox to each row, and one in the header that selects them all. */
|
|
93
|
+
selectable = this.useChange(false, (value) => {
|
|
94
|
+
this.reflect("selectable", value);
|
|
95
|
+
this.schedule("head", "body");
|
|
96
|
+
}, { attribute: "selectable" });
|
|
97
|
+
/** The keys of the selected rows. */
|
|
98
|
+
selected = this.useChange([], () => this.renderSelection(), {
|
|
99
|
+
attribute: "selected",
|
|
100
|
+
parse: jsonOf([])
|
|
101
|
+
});
|
|
102
|
+
/** Shorter rows, for long lists. */
|
|
103
|
+
dense = this.useChange(false, (value) => this.reflect("dense", value), { attribute: "dense" });
|
|
104
|
+
/** Shades every other row. */
|
|
105
|
+
striped = this.useChange(false, (value) => this.reflect("striped", value), { attribute: "striped" });
|
|
106
|
+
/** A CSS height past which the rows scroll under the header: `320px`, `50vh`; a number is pixels. */
|
|
107
|
+
maxHeight = this.useChange("", (value) => this.renderMaxHeight(value), { attribute: "max-height" });
|
|
108
|
+
/** What the table says when it has no rows. */
|
|
109
|
+
emptyText = this.useChange(DEFAULT_EMPTY_TEXT, () => this.schedule("body"), { attribute: "empty-text" });
|
|
110
|
+
/** Shows placeholder rows while the data loads. */
|
|
111
|
+
loading = this.useChange(false, (value) => {
|
|
112
|
+
this.reflect("loading", value);
|
|
113
|
+
setOrRemove(this._refs.table, "aria-busy", value ? "true" : "");
|
|
114
|
+
this.schedule("body");
|
|
115
|
+
}, { attribute: "loading" });
|
|
116
|
+
/** The table's name for screen readers. */
|
|
117
|
+
label = this.useChange("", (value) => setOrRemove(this._refs.table, "aria-label", value), { attribute: "label" });
|
|
118
|
+
/** Draws the cells of some columns by code, by column key: `{ name: (row) => <strong>{row.name}</strong> }`. */
|
|
119
|
+
renderers = this.useChange({}, () => this.schedule("body"));
|
|
120
|
+
_pending = {
|
|
121
|
+
body: false,
|
|
122
|
+
head: false,
|
|
123
|
+
queued: false
|
|
124
|
+
};
|
|
125
|
+
/** The keys of the rows as drawn, by their place: the checkboxes carry the place. */
|
|
126
|
+
_view = { keys: [] };
|
|
127
|
+
_refs = this.useRefs((ref) => ({
|
|
128
|
+
body: ref("tbody"),
|
|
129
|
+
head: ref("thead"),
|
|
130
|
+
table: ref("table")
|
|
131
|
+
}));
|
|
132
|
+
templateHtml() {
|
|
133
|
+
return /* @__PURE__ */ jsx("div", {
|
|
134
|
+
class: "scroll",
|
|
135
|
+
attrs: { part: "base" },
|
|
136
|
+
children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", {}), /* @__PURE__ */ jsx("tbody", {})] })
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
templateCss() {
|
|
140
|
+
return css`${BASE_STYLES}${jl_table_default}`;
|
|
141
|
+
}
|
|
142
|
+
onConnect() {
|
|
143
|
+
this.on(this._refs.head, "click", (event) => {
|
|
144
|
+
const key = event.target.closest("button.sort")?.dataset.key;
|
|
145
|
+
if (key !== void 0) this.sortBy(key);
|
|
146
|
+
});
|
|
147
|
+
this.on(this._refs.table, "change", (event) => {
|
|
148
|
+
const input = event.target;
|
|
149
|
+
if (input instanceof HTMLInputElement && input.classList.contains("checkbox")) this.changeSelection(input);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Draws on the next microtask: the inputs set while connecting give one drawing, not one each. */
|
|
153
|
+
schedule(...parts) {
|
|
154
|
+
for (const part of parts) this._pending[part] = true;
|
|
155
|
+
if (this._pending.queued) return;
|
|
156
|
+
this._pending.queued = true;
|
|
157
|
+
queueMicrotask(() => this.flush());
|
|
158
|
+
}
|
|
159
|
+
flush() {
|
|
160
|
+
const { body, head } = this._pending;
|
|
161
|
+
this._pending.body = false;
|
|
162
|
+
this._pending.head = false;
|
|
163
|
+
this._pending.queued = false;
|
|
164
|
+
if (head) this.renderHead();
|
|
165
|
+
this.renderSortState();
|
|
166
|
+
if (body) this.renderBody();
|
|
167
|
+
this.renderSelection();
|
|
168
|
+
}
|
|
169
|
+
sortBy(key) {
|
|
170
|
+
const current = key === this.sortKey.get() ? this.sortDirection.get() : NO_SORT;
|
|
171
|
+
const direction = nextSortDirection(current);
|
|
172
|
+
this.sortKey.set(key);
|
|
173
|
+
this.sortDirection.set(direction);
|
|
174
|
+
this.events.emit("sort", {
|
|
175
|
+
direction,
|
|
176
|
+
key
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
changeSelection(input) {
|
|
180
|
+
const index = input.dataset.index;
|
|
181
|
+
const selected = this.selected.get();
|
|
182
|
+
const keys = index === void 0 ? toggleAll(this._view.keys, new Set(selected)) : toggleKey(selected, this._view.keys[Number(index)] ?? Number(index), input.checked);
|
|
183
|
+
this.selected.set(keys);
|
|
184
|
+
this.events.emit("selection", keys);
|
|
185
|
+
}
|
|
186
|
+
renderMaxHeight(value) {
|
|
187
|
+
const height = /^\d+(\.\d+)?$/.test(value.trim()) ? `${value.trim()}px` : value;
|
|
188
|
+
this.style.setProperty("--max-height", height === "" ? "none" : height);
|
|
189
|
+
this.reflect("scrolls", height !== "");
|
|
190
|
+
}
|
|
191
|
+
renderHead() {
|
|
192
|
+
const selectAll = this.selectable.get() ? /* @__PURE__ */ jsx("th", {
|
|
193
|
+
class: "select-cell",
|
|
194
|
+
scope: "col",
|
|
195
|
+
children: checkbox("Select all rows", null)
|
|
196
|
+
}) : null;
|
|
197
|
+
this._refs.head.replaceChildren(/* @__PURE__ */ jsxs("tr", { children: [selectAll, this.columns.get().map((column) => this.headerCell(column))] }));
|
|
198
|
+
}
|
|
199
|
+
headerCell(column) {
|
|
200
|
+
const content = column.sortable ? /* @__PURE__ */ jsxs("button", {
|
|
201
|
+
type: "button",
|
|
202
|
+
class: "sort",
|
|
203
|
+
"data-key": column.key,
|
|
204
|
+
children: [/* @__PURE__ */ jsx("span", { children: column.label }), /* @__PURE__ */ jsx(JlIcon, {
|
|
205
|
+
class: "sort-icon",
|
|
206
|
+
size: SORT_ICON_SIZE
|
|
207
|
+
})]
|
|
208
|
+
}) : column.label;
|
|
209
|
+
const cell = /* @__PURE__ */ jsx("th", {
|
|
210
|
+
scope: "col",
|
|
211
|
+
class: `align-${columnAlign(column)}`,
|
|
212
|
+
"data-key": column.key,
|
|
213
|
+
children: content
|
|
214
|
+
});
|
|
215
|
+
cell.style.width = columnWidth(column.width);
|
|
216
|
+
return cell;
|
|
217
|
+
}
|
|
218
|
+
/** The arrows and `aria-sort` of the headings, changed in place so a pressed heading keeps the focus. */
|
|
219
|
+
renderSortState() {
|
|
220
|
+
const sortKey = this.sortKey.get();
|
|
221
|
+
for (const cell of this._refs.head.querySelectorAll("th[data-key]")) {
|
|
222
|
+
const direction = cell.dataset.key === sortKey ? this.sortDirection.get() : NO_SORT;
|
|
223
|
+
const sortable = cell.querySelector(".sort") !== null;
|
|
224
|
+
setOrRemove(cell, "aria-sort", sortable ? ARIA_SORT[direction] : "");
|
|
225
|
+
cell.querySelector(".sort-icon")?.name.set(SORT_ICONS[direction]);
|
|
226
|
+
cell.toggleAttribute("data-sorted", direction !== NO_SORT);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
sortedRows() {
|
|
230
|
+
const key = this.sortKey.get();
|
|
231
|
+
const format = this.columns.get().find((column) => column.key === key)?.format;
|
|
232
|
+
return sortRows(this.rows.get(), key, this.sortDirection.get(), format);
|
|
233
|
+
}
|
|
234
|
+
renderBody() {
|
|
235
|
+
const columns = this.columns.get();
|
|
236
|
+
const span = columns.length + Number(this.selectable.get());
|
|
237
|
+
if (this.loading.get()) {
|
|
238
|
+
this._view.keys = [];
|
|
239
|
+
this._refs.body.replaceChildren(...this.skeletonRows(columns));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const rows = this.rows.get();
|
|
243
|
+
if (rows.length === 0) {
|
|
244
|
+
this._view.keys = [];
|
|
245
|
+
this._refs.body.replaceChildren(this.emptyRow(span));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const keyField = this.rowKey.get();
|
|
249
|
+
const keys = new Map(rows.map((row, index) => [row, rowKeyOf(row, keyField, index)]));
|
|
250
|
+
const sorted = this.sortedRows();
|
|
251
|
+
this._view.keys = sorted.map((row, index) => keys.get(row) ?? index);
|
|
252
|
+
this._refs.body.replaceChildren(...sorted.map((row, index) => this.bodyRow(row, index, columns)));
|
|
253
|
+
}
|
|
254
|
+
bodyRow(row, index, columns) {
|
|
255
|
+
const first = columns[0];
|
|
256
|
+
const name = first === void 0 ? String(index + 1) : formatCell(row[first.key], first.format);
|
|
257
|
+
const select = this.selectable.get() ? /* @__PURE__ */ jsx("td", {
|
|
258
|
+
class: "select-cell",
|
|
259
|
+
children: checkbox(`Select ${name}`, index)
|
|
260
|
+
}) : null;
|
|
261
|
+
return /* @__PURE__ */ jsxs("tr", { children: [select, columns.map((column) => /* @__PURE__ */ jsx("td", {
|
|
262
|
+
class: `align-${columnAlign(column)} format-${column.format ?? "text"}`,
|
|
263
|
+
children: this.cell(row, column)
|
|
264
|
+
}))] });
|
|
265
|
+
}
|
|
266
|
+
cell(row, column) {
|
|
267
|
+
const renderer = this.renderers.get()[column.key];
|
|
268
|
+
if (renderer !== void 0) return renderer(row);
|
|
269
|
+
const format = column.format ?? "text";
|
|
270
|
+
return CELL_MAKERS[format](formatCell(row[column.key], format), column);
|
|
271
|
+
}
|
|
272
|
+
skeletonRows(columns) {
|
|
273
|
+
const select = this.selectable.get();
|
|
274
|
+
return Array.from({ length: SKELETON_ROWS }, () => /* @__PURE__ */ jsxs("tr", {
|
|
275
|
+
class: "skeleton",
|
|
276
|
+
children: [select ? /* @__PURE__ */ jsx("td", {
|
|
277
|
+
class: "select-cell",
|
|
278
|
+
children: /* @__PURE__ */ jsx("span", { class: "bone box" })
|
|
279
|
+
}) : null, columns.map((column) => /* @__PURE__ */ jsx("td", {
|
|
280
|
+
class: `align-${columnAlign(column)}`,
|
|
281
|
+
children: /* @__PURE__ */ jsx("span", { class: "bone" })
|
|
282
|
+
}))]
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
emptyRow(span) {
|
|
286
|
+
return /* @__PURE__ */ jsx("tr", {
|
|
287
|
+
class: "empty",
|
|
288
|
+
children: /* @__PURE__ */ jsx("td", {
|
|
289
|
+
colSpan: Math.max(1, span),
|
|
290
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
291
|
+
class: "empty-content",
|
|
292
|
+
children: [/* @__PURE__ */ jsx(JlIcon, {
|
|
293
|
+
name: "inbox",
|
|
294
|
+
size: EMPTY_ICON_SIZE
|
|
295
|
+
}), /* @__PURE__ */ jsx("span", { children: this.emptyText.get() })]
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/** The checkboxes and the shading of selected rows, changed in place. */
|
|
301
|
+
renderSelection() {
|
|
302
|
+
const selected = new Set(this.selected.get());
|
|
303
|
+
for (const input of this._refs.body.querySelectorAll("input.checkbox")) {
|
|
304
|
+
const key = this._view.keys[Number(input.dataset.index)];
|
|
305
|
+
const on = key !== void 0 && selected.has(key);
|
|
306
|
+
input.checked = on;
|
|
307
|
+
input.closest("tr")?.toggleAttribute("data-selected", on);
|
|
308
|
+
}
|
|
309
|
+
const all = this._refs.head.querySelector("input.checkbox");
|
|
310
|
+
if (all === null) return;
|
|
311
|
+
const state = selectionState(this._view.keys, selected);
|
|
312
|
+
all.checked = state === "all";
|
|
313
|
+
all.indeterminate = state === "some";
|
|
314
|
+
all.disabled = this._view.keys.length === 0;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
JlTable.register();
|
|
318
|
+
export { JlTable, TABLE_SORT_DIRECTIONS };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/organisms/jl-toaster.css?raw
|
|
2
|
+
var jl_toaster_default = ":host {\n display: contents;\n --edge: 16px;\n --width: 380px;\n}\n\n.region {\n position: fixed;\n inset: auto;\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: min(var(--width), calc(100vw - 2 * var(--edge)));\n margin: 0;\n padding: 0;\n overflow: visible;\n border: none;\n background: none;\n color: var(--jl-text);\n /* The empty space between toasts lets clicks through to the page. */\n pointer-events: none;\n}\n\n.region:not(:popover-open) {\n display: none;\n}\n\n::slotted(*) {\n pointer-events: auto;\n}\n\n::slotted([data-stacked]) {\n display: none;\n}\n\n:host([placement=\"bottom-right\"]) .region {\n right: var(--edge);\n bottom: var(--edge);\n}\n\n:host([placement=\"bottom-left\"]) .region {\n bottom: var(--edge);\n left: var(--edge);\n}\n\n:host([placement=\"bottom-center\"]) .region {\n bottom: var(--edge);\n left: 50%;\n translate: -50% 0;\n}\n\n/* At the top, the newest toast is the highest one, and toasts come in from above. */\n:host([placement^=\"top\"]) .region {\n top: var(--edge);\n flex-direction: column-reverse;\n}\n\n:host([placement^=\"top\"]) ::slotted(*) {\n --jl-toast-enter: -8px;\n}\n\n:host([placement=\"top-right\"]) .region {\n right: var(--edge);\n}\n\n:host([placement=\"top-center\"]) .region {\n left: 50%;\n translate: -50% 0;\n}\n";
|
|
3
|
+
export { jl_toaster_default as default };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { ToasterPlacement, ToastHandle, ToastOptions } from '../types/jl-toaster.types';
|
|
3
|
+
export declare const TOASTER_PLACEMENTS: readonly ToasterPlacement[];
|
|
4
|
+
/**
|
|
5
|
+
* The corner of the window where notifications stack, newest nearest the edge, at most `max` at a time. It lives in
|
|
6
|
+
* the top layer, so it shows over open dialogs, and its live region reads new toasts to screen readers. Pages
|
|
7
|
+
* usually call `toast({ heading, text, tone })`, which finds or makes the page's toaster.
|
|
8
|
+
*/
|
|
9
|
+
export declare class JlToaster extends JlElement {
|
|
10
|
+
static readonly selector = "jl-toaster";
|
|
11
|
+
readonly placement: Readonly<Readonly<{
|
|
12
|
+
get(): ToasterPlacement;
|
|
13
|
+
set(setter: ((prevState: ToasterPlacement) => ToasterPlacement) | ToasterPlacement): void;
|
|
14
|
+
}>>;
|
|
15
|
+
/** How many toasts show at once; older ones wait for newer ones to close. */
|
|
16
|
+
readonly max: Readonly<Readonly<{
|
|
17
|
+
get(): number;
|
|
18
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
19
|
+
}>>;
|
|
20
|
+
/** The region's name for screen readers. */
|
|
21
|
+
readonly label: Readonly<Readonly<{
|
|
22
|
+
get(): string;
|
|
23
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
24
|
+
}>>;
|
|
25
|
+
private readonly _watched;
|
|
26
|
+
private readonly _refs;
|
|
27
|
+
/** Shows a notification; `duration` is 5 seconds unless given, and it has a close button unless told not to. */
|
|
28
|
+
notify(options: ToastOptions): ToastHandle;
|
|
29
|
+
templateHtml(): Node;
|
|
30
|
+
templateCss(): string;
|
|
31
|
+
onConnect(): void;
|
|
32
|
+
onDisconnect(): void;
|
|
33
|
+
private toasts;
|
|
34
|
+
/** The newest `max` toasts show; the older ones wait. A dismissed toast leaves the toaster. */
|
|
35
|
+
private renderStack;
|
|
36
|
+
private watch;
|
|
37
|
+
private renderOpen;
|
|
38
|
+
/**
|
|
39
|
+
* Puts the toaster back on top of the top layer, above a dialog opened since it showed. Not while a toast holds
|
|
40
|
+
* the focus: hiding the region for a moment would drop it.
|
|
41
|
+
*/
|
|
42
|
+
private raise;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Shows a notification in the page's toaster, made on first use: `toast({ heading: 'Saved', tone: 'ok' })`.
|
|
46
|
+
* Returns a handle to close it early.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toast(options: ToastOptions): ToastHandle;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { clampedNumber, oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { JlToast } from "../molecules/jl-toast.js";
|
|
5
|
+
import jl_toaster_default from "./jl-toaster.css_raw.js";
|
|
6
|
+
import { css } from "@jadis/core";
|
|
7
|
+
import { jsx } from "@jadis/core/jsx-runtime";
|
|
8
|
+
// src/organisms/jl-toaster.tsx
|
|
9
|
+
var TOASTER_PLACEMENTS = [
|
|
10
|
+
"bottom-right",
|
|
11
|
+
"bottom-center",
|
|
12
|
+
"bottom-left",
|
|
13
|
+
"top-right",
|
|
14
|
+
"top-center"
|
|
15
|
+
];
|
|
16
|
+
var DEFAULT_MAX = 3;
|
|
17
|
+
var MAX_LIMIT = 20;
|
|
18
|
+
var DEFAULT_DURATION = 5e3;
|
|
19
|
+
var DEFAULT_LABEL = "Notifications";
|
|
20
|
+
var STACKED = "data-stacked";
|
|
21
|
+
var OPEN = ":popover-open";
|
|
22
|
+
/**
|
|
23
|
+
* The corner of the window where notifications stack, newest nearest the edge, at most `max` at a time. It lives in
|
|
24
|
+
* the top layer, so it shows over open dialogs, and its live region reads new toasts to screen readers. Pages
|
|
25
|
+
* usually call `toast({ heading, text, tone })`, which finds or makes the page's toaster.
|
|
26
|
+
*/
|
|
27
|
+
var JlToaster = class extends JlElement {
|
|
28
|
+
static selector = "jl-toaster";
|
|
29
|
+
placement = this.useChange("bottom-right", (value) => this.reflect("placement", value), {
|
|
30
|
+
attribute: "placement",
|
|
31
|
+
parse: oneOf(TOASTER_PLACEMENTS, "bottom-right")
|
|
32
|
+
});
|
|
33
|
+
/** How many toasts show at once; older ones wait for newer ones to close. */
|
|
34
|
+
max = this.useChange(DEFAULT_MAX, () => this.renderStack(), {
|
|
35
|
+
attribute: "max",
|
|
36
|
+
parse: clampedNumber(DEFAULT_MAX, 1, MAX_LIMIT)
|
|
37
|
+
});
|
|
38
|
+
/** The region's name for screen readers. */
|
|
39
|
+
label = this.useChange(DEFAULT_LABEL, (value) => this._refs.region.setAttribute("aria-label", value), { attribute: "label" });
|
|
40
|
+
_watched = /* @__PURE__ */ new WeakSet();
|
|
41
|
+
_refs = this.useRefs((ref) => ({
|
|
42
|
+
region: ref(".region"),
|
|
43
|
+
slot: ref("slot")
|
|
44
|
+
}));
|
|
45
|
+
/** Shows a notification; `duration` is 5 seconds unless given, and it has a close button unless told not to. */
|
|
46
|
+
notify(options) {
|
|
47
|
+
const toast = /* @__PURE__ */ jsx(JlToast, {
|
|
48
|
+
heading: options.heading,
|
|
49
|
+
text: options.text,
|
|
50
|
+
tone: options.tone,
|
|
51
|
+
icon: options.icon,
|
|
52
|
+
duration: options.duration ?? DEFAULT_DURATION,
|
|
53
|
+
closable: options.closable ?? true,
|
|
54
|
+
actionLabel: options.actionLabel
|
|
55
|
+
});
|
|
56
|
+
const onAction = options.onAction;
|
|
57
|
+
if (onAction !== void 0) toast.addEventListener("action", () => onAction());
|
|
58
|
+
this.append(toast);
|
|
59
|
+
this.whenRendered(() => this.raise());
|
|
60
|
+
return { dismiss: () => toast.dismiss() };
|
|
61
|
+
}
|
|
62
|
+
templateHtml() {
|
|
63
|
+
return /* @__PURE__ */ jsx("section", {
|
|
64
|
+
class: "region",
|
|
65
|
+
popover: "manual",
|
|
66
|
+
"aria-live": "polite",
|
|
67
|
+
"aria-relevant": "additions text",
|
|
68
|
+
attrs: { part: "base" },
|
|
69
|
+
children: /* @__PURE__ */ jsx("slot", {})
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
templateCss() {
|
|
73
|
+
return css`${BASE_STYLES}${jl_toaster_default}`;
|
|
74
|
+
}
|
|
75
|
+
onConnect() {
|
|
76
|
+
this.renderStack();
|
|
77
|
+
this.on(this._refs.slot, "slotchange", () => this.renderStack());
|
|
78
|
+
}
|
|
79
|
+
onDisconnect() {
|
|
80
|
+
const region = this._refs.region;
|
|
81
|
+
if (region.matches(OPEN)) region.hidePopover();
|
|
82
|
+
}
|
|
83
|
+
toasts() {
|
|
84
|
+
return [...this.children].filter((child) => child instanceof JlToast && !child.hidden);
|
|
85
|
+
}
|
|
86
|
+
/** The newest `max` toasts show; the older ones wait. A dismissed toast leaves the toaster. */
|
|
87
|
+
renderStack() {
|
|
88
|
+
if (!this.isConnected) return;
|
|
89
|
+
const toasts = this.toasts();
|
|
90
|
+
const hiddenCount = Math.max(0, toasts.length - this.max.get());
|
|
91
|
+
for (const [index, toast] of toasts.entries()) {
|
|
92
|
+
toast.toggleAttribute(STACKED, index < hiddenCount);
|
|
93
|
+
this.watch(toast);
|
|
94
|
+
}
|
|
95
|
+
this.renderOpen(toasts.length > 0);
|
|
96
|
+
}
|
|
97
|
+
watch(toast) {
|
|
98
|
+
if (this._watched.has(toast)) return;
|
|
99
|
+
this._watched.add(toast);
|
|
100
|
+
toast.addEventListener("dismiss", () => toast.remove(), { once: true });
|
|
101
|
+
}
|
|
102
|
+
renderOpen(open) {
|
|
103
|
+
const region = this._refs.region;
|
|
104
|
+
if (open === region.matches(OPEN)) return;
|
|
105
|
+
if (open) {
|
|
106
|
+
region.showPopover();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
region.hidePopover();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Puts the toaster back on top of the top layer, above a dialog opened since it showed. Not while a toast holds
|
|
113
|
+
* the focus: hiding the region for a moment would drop it.
|
|
114
|
+
*/
|
|
115
|
+
raise() {
|
|
116
|
+
const region = this._refs.region;
|
|
117
|
+
if (!region.matches(OPEN)) {
|
|
118
|
+
this.renderStack();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (this.matches(":focus-within")) return;
|
|
122
|
+
region.hidePopover();
|
|
123
|
+
region.showPopover();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
JlToaster.register();
|
|
127
|
+
/** The page's toaster: the first one in the document, or a new one at the end of the body. */
|
|
128
|
+
function pageToaster() {
|
|
129
|
+
const found = document.querySelector(JlToaster.selector);
|
|
130
|
+
if (found !== null) return found;
|
|
131
|
+
const toaster = new JlToaster();
|
|
132
|
+
document.body.append(toaster);
|
|
133
|
+
return toaster;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Shows a notification in the page's toaster, made on first use: `toast({ heading: 'Saved', tone: 'ok' })`.
|
|
137
|
+
* Returns a handle to close it early.
|
|
138
|
+
*/
|
|
139
|
+
function toast(options) {
|
|
140
|
+
return pageToaster().notify(options);
|
|
141
|
+
}
|
|
142
|
+
export { JlToaster, TOASTER_PLACEMENTS, toast };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/organisms/jl-tree.css?raw
|
|
2
|
+
var jl_tree_default = ":host {\n display: block;\n --indent: 16px;\n --row-height: 30px;\n}\n\n.tree {\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n}\n\n.group {\n position: relative;\n}\n\n/* A quiet line under each open folder, from its chevron down along its children. */\n.group::before {\n position: absolute;\n top: 0;\n bottom: 4px;\n left: calc(8px + var(--level) * var(--indent) + 6.5px);\n width: 1px;\n background: var(--jl-line);\n content: \"\";\n}\n\n.item:focus-visible {\n box-shadow: none;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 6px;\n height: var(--row-height);\n padding: 0 8px 0 calc(8px + var(--level) * var(--indent));\n border-radius: var(--jl-radius-sm);\n cursor: pointer;\n user-select: none;\n}\n\n.row:hover {\n background: var(--jl-surface-2);\n color: var(--jl-text);\n}\n\n.item:focus-visible > .row {\n box-shadow: var(--jl-focus-ring);\n}\n\n.item[aria-selected=\"true\"] > .row {\n background: var(--jl-accent-soft);\n color: var(--jl-text);\n font-weight: 600;\n}\n\n.chevron {\n display: inline-flex;\n flex: none;\n justify-content: center;\n width: 14px;\n color: var(--jl-faint);\n transition: rotate var(--jl-fast) var(--jl-ease);\n}\n\n.item[aria-expanded=\"true\"] > .row > .chevron {\n rotate: 90deg;\n}\n\n.icon {\n color: var(--jl-muted);\n}\n\n.item[aria-selected=\"true\"] > .row > .icon {\n color: var(--jl-accent);\n}\n\n.label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n";
|
|
3
|
+
export { jl_tree_default as default };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { TreeItem } from '../types/jl-tree.types';
|
|
3
|
+
/**
|
|
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. The keyboard follows the tree pattern: Up and Down move, Right opens or enters a
|
|
6
|
+
* folder, Left closes it or goes to the parent, Home and End jump, Enter selects. Emits `select` and `toggle`.
|
|
7
|
+
*/
|
|
8
|
+
export declare class JlTree extends JlElement {
|
|
9
|
+
static readonly selector = "jl-tree";
|
|
10
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
11
|
+
select: string;
|
|
12
|
+
toggle: {
|
|
13
|
+
id: string;
|
|
14
|
+
open: boolean;
|
|
15
|
+
};
|
|
16
|
+
}>>;
|
|
17
|
+
readonly items: Readonly<Readonly<{
|
|
18
|
+
get(): TreeItem[];
|
|
19
|
+
set(setter: TreeItem[] | ((prevState: TreeItem[]) => TreeItem[])): void;
|
|
20
|
+
}>>;
|
|
21
|
+
/** The ids of the open folders. */
|
|
22
|
+
readonly expanded: Readonly<Readonly<{
|
|
23
|
+
get(): string[];
|
|
24
|
+
set(setter: string[] | ((prevState: string[]) => string[])): void;
|
|
25
|
+
}>>;
|
|
26
|
+
/** The id of the selected item. */
|
|
27
|
+
readonly value: Readonly<Readonly<{
|
|
28
|
+
get(): string;
|
|
29
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
30
|
+
}>>;
|
|
31
|
+
/** The tree's name for screen readers. */
|
|
32
|
+
readonly label: Readonly<Readonly<{
|
|
33
|
+
get(): string;
|
|
34
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
35
|
+
}>>;
|
|
36
|
+
private readonly _rows;
|
|
37
|
+
private readonly _focus;
|
|
38
|
+
private readonly _refs;
|
|
39
|
+
/** Opens or closes a folder, as a press on its chevron would. */
|
|
40
|
+
toggle(id: string, open: boolean): void;
|
|
41
|
+
templateHtml(): Node;
|
|
42
|
+
templateCss(): string;
|
|
43
|
+
onConnect(): void;
|
|
44
|
+
private idOf;
|
|
45
|
+
/** A press selects the item; on a folder it also opens or closes it, and the chevron only does that. */
|
|
46
|
+
private press;
|
|
47
|
+
private handleKey;
|
|
48
|
+
private select;
|
|
49
|
+
private findItem;
|
|
50
|
+
/** One item takes the Tab stop: the focused one, else the selected one, else the first. */
|
|
51
|
+
private moveFocus;
|
|
52
|
+
private renderItems;
|
|
53
|
+
private itemNode;
|
|
54
|
+
private renderExpanded;
|
|
55
|
+
private isVisible;
|
|
56
|
+
/** The item itself when it shows, else the nearest folder around it that does: the focus goes there. */
|
|
57
|
+
private visibleAncestor;
|
|
58
|
+
private renderSelection;
|
|
59
|
+
}
|