@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,37 @@
|
|
|
1
|
+
// src/lib/split.ts
|
|
2
|
+
var FULL = 100;
|
|
3
|
+
var HALF = 50;
|
|
4
|
+
/** How far one arrow press moves the divider, in percent; ten times that with Shift. */
|
|
5
|
+
var KEY_STEP = 2;
|
|
6
|
+
var LARGE_STEP = 10;
|
|
7
|
+
var KEYS = {
|
|
8
|
+
horizontal: {
|
|
9
|
+
back: "ArrowLeft",
|
|
10
|
+
forward: "ArrowRight"
|
|
11
|
+
},
|
|
12
|
+
vertical: {
|
|
13
|
+
back: "ArrowUp",
|
|
14
|
+
forward: "ArrowDown"
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** The smallest share each pane keeps, in percent: never under 0 nor over half. */
|
|
18
|
+
var clampMin = (min) => Math.min(HALF, Math.max(0, min));
|
|
19
|
+
/** A divider position kept so that each pane keeps at least `min` percent. */
|
|
20
|
+
var clampSplit = (position, min) => Math.min(FULL - clampMin(min), Math.max(clampMin(min), position));
|
|
21
|
+
/** Where a pointer sits along the split, in percent of its length from its start; the middle when it has none. */
|
|
22
|
+
var positionAt = (pointer, start, length) => length > 0 ? (pointer - start) / length * FULL : HALF;
|
|
23
|
+
/**
|
|
24
|
+
* Where a key moves the divider: arrows along the split by a step (a large one with Shift), Home and End to the
|
|
25
|
+
* ends; null for any other key.
|
|
26
|
+
*/
|
|
27
|
+
function positionAfterKey(position, key, orientation, min, large = false) {
|
|
28
|
+
const step = large ? LARGE_STEP : KEY_STEP;
|
|
29
|
+
const next = (/* @__PURE__ */ new Map([
|
|
30
|
+
[KEYS[orientation].back, position - step],
|
|
31
|
+
[KEYS[orientation].forward, position + step],
|
|
32
|
+
["End", FULL],
|
|
33
|
+
["Home", 0]
|
|
34
|
+
])).get(key);
|
|
35
|
+
return next === void 0 ? null : clampSplit(next, min);
|
|
36
|
+
}
|
|
37
|
+
export { clampMin, clampSplit, positionAfterKey, positionAt };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether an event started on a control inside an interactive surface (a button in a card's footer, a switch at
|
|
3
|
+
* the end of a row), across shadow roots: the surface then leaves the press to that control.
|
|
4
|
+
*/
|
|
5
|
+
export declare function startedOnControl(event: Event, surface: Element): boolean;
|
|
6
|
+
/** Whether a key activates a focused button-like surface, as Enter and Space do a native button. */
|
|
7
|
+
export declare const isActivationKey: (event: KeyboardEvent) => boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/lib/surface.ts
|
|
2
|
+
var CONTROL_SELECTOR = [
|
|
3
|
+
"a[href]",
|
|
4
|
+
"button",
|
|
5
|
+
"input",
|
|
6
|
+
"select",
|
|
7
|
+
"textarea",
|
|
8
|
+
"summary",
|
|
9
|
+
"label",
|
|
10
|
+
"[contenteditable=\"true\"]",
|
|
11
|
+
"[role=\"button\"]",
|
|
12
|
+
"[role=\"link\"]",
|
|
13
|
+
"[role=\"checkbox\"]",
|
|
14
|
+
"[role=\"switch\"]",
|
|
15
|
+
"[role=\"menuitem\"]"
|
|
16
|
+
].join(", ");
|
|
17
|
+
var ACTIVATION_KEYS = ["Enter", " "];
|
|
18
|
+
/**
|
|
19
|
+
* Whether an event started on a control inside an interactive surface (a button in a card's footer, a switch at
|
|
20
|
+
* the end of a row), across shadow roots: the surface then leaves the press to that control.
|
|
21
|
+
*/
|
|
22
|
+
function startedOnControl(event, surface) {
|
|
23
|
+
const path = event.composedPath();
|
|
24
|
+
return path.slice(0, Math.max(0, path.indexOf(surface))).some((target) => target instanceof Element && target.matches(CONTROL_SELECTOR));
|
|
25
|
+
}
|
|
26
|
+
/** Whether a key activates a focused button-like surface, as Enter and Space do a native button. */
|
|
27
|
+
var isActivationKey = (event) => ACTIVATION_KEYS.includes(event.key);
|
|
28
|
+
export { isActivationKey, startedOnControl };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TableAlign, TableCellFormat, TableColumn, TableRow, TableRowKey, TableSelectionState, TableSortDirection } from '../types/jl-table.types';
|
|
2
|
+
export declare const nextSortDirection: (direction: TableSortDirection) => TableSortDirection;
|
|
3
|
+
/** A cell's value as a number, `NaN` when it is not one. */
|
|
4
|
+
export declare function toNumber(value: unknown): number;
|
|
5
|
+
/** A cell's value as a time in milliseconds: a `Date`, a timestamp, or a date string; `NaN` otherwise. */
|
|
6
|
+
export declare function toTime(value: unknown): number;
|
|
7
|
+
/**
|
|
8
|
+
* Orders two cell values ascending: numbers by value, dates by time, text with `localeCompare`. Empty and
|
|
9
|
+
* unreadable values come after the others.
|
|
10
|
+
*/
|
|
11
|
+
export declare function compareCells(a: unknown, b: unknown, format: TableCellFormat, locale?: string): number;
|
|
12
|
+
/**
|
|
13
|
+
* The rows ordered by one column; `none` gives them back as they came. The sort is stable, and empty values stay
|
|
14
|
+
* at the end in both directions.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sortRows(rows: readonly TableRow[], key: string, direction: TableSortDirection, format?: TableCellFormat, locale?: string): TableRow[];
|
|
17
|
+
/** A cell's text in its column's format; empty for a missing value. */
|
|
18
|
+
export declare function formatCell(value: unknown, format?: TableCellFormat, locale?: string): string;
|
|
19
|
+
/** Where a column's cells align: as asked, otherwise numbers right and the rest left. */
|
|
20
|
+
export declare function columnAlign(column: TableColumn): TableAlign;
|
|
21
|
+
/** A CSS width from a column's `width`: numbers are pixels. */
|
|
22
|
+
export declare function columnWidth(width: string | number | undefined): string;
|
|
23
|
+
/** A row's identity, read from its key field; its position when the field is missing. */
|
|
24
|
+
export declare function rowKeyOf(row: TableRow, keyField: string, index: number): TableRowKey;
|
|
25
|
+
/** What the select-all box shows: empty, a dash for some rows, a tick for all of them. */
|
|
26
|
+
export declare function selectionState(keys: readonly TableRowKey[], selected: ReadonlySet<TableRowKey>): TableSelectionState;
|
|
27
|
+
/** The selection after the select-all box is pressed: every row when not all were selected, else none of them. */
|
|
28
|
+
export declare function toggleAll(keys: readonly TableRowKey[], selected: ReadonlySet<TableRowKey>): TableRowKey[];
|
|
29
|
+
/** The selection with one row added or taken out. */
|
|
30
|
+
export declare function toggleKey(selected: readonly TableRowKey[], key: TableRowKey, on: boolean): TableRowKey[];
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/lib/table.ts
|
|
2
|
+
/** What a header press does to a column's order: ascending, then descending, then as the rows came. */
|
|
3
|
+
var NEXT_DIRECTION = {
|
|
4
|
+
asc: "desc",
|
|
5
|
+
desc: "none",
|
|
6
|
+
none: "asc"
|
|
7
|
+
};
|
|
8
|
+
var DIRECTION_SIGN = {
|
|
9
|
+
asc: 1,
|
|
10
|
+
desc: -1,
|
|
11
|
+
none: 0
|
|
12
|
+
};
|
|
13
|
+
var DATE_FORMAT = { dateStyle: "medium" };
|
|
14
|
+
var nextSortDirection = (direction) => NEXT_DIRECTION[direction];
|
|
15
|
+
var isEmpty = (value) => value === null || value === void 0 || value === "";
|
|
16
|
+
/** A cell's value as a number, `NaN` when it is not one. */
|
|
17
|
+
function toNumber(value) {
|
|
18
|
+
if (typeof value === "number") return value;
|
|
19
|
+
if (typeof value === "string" && value.trim() !== "") return Number(value);
|
|
20
|
+
return NaN;
|
|
21
|
+
}
|
|
22
|
+
/** A cell's value as a time in milliseconds: a `Date`, a timestamp, or a date string; `NaN` otherwise. */
|
|
23
|
+
function toTime(value) {
|
|
24
|
+
if (value instanceof Date) return value.getTime();
|
|
25
|
+
if (typeof value === "number") return value;
|
|
26
|
+
if (typeof value === "string" && value.trim() !== "") return Date.parse(value);
|
|
27
|
+
return NaN;
|
|
28
|
+
}
|
|
29
|
+
var compareNumbers = (a, b) => a - b;
|
|
30
|
+
var COMPARERS = {
|
|
31
|
+
badge: (a, b, locale) => String(a).localeCompare(String(b), locale),
|
|
32
|
+
code: (a, b, locale) => String(a).localeCompare(String(b), locale),
|
|
33
|
+
date: (a, b) => compareNumbers(toTime(a), toTime(b)),
|
|
34
|
+
number: (a, b) => compareNumbers(toNumber(a), toNumber(b)),
|
|
35
|
+
text: (a, b, locale) => String(a).localeCompare(String(b), locale, { numeric: true })
|
|
36
|
+
};
|
|
37
|
+
/** Whether a value cannot be ordered in the format: empty, or not a number or a date where one is expected. */
|
|
38
|
+
function isUnorderable(value, format) {
|
|
39
|
+
if (isEmpty(value)) return true;
|
|
40
|
+
return {
|
|
41
|
+
badge: () => false,
|
|
42
|
+
code: () => false,
|
|
43
|
+
date: () => Number.isNaN(toTime(value)),
|
|
44
|
+
number: () => Number.isNaN(toNumber(value)),
|
|
45
|
+
text: () => false
|
|
46
|
+
}[format]();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The rows ordered by one column; `none` gives them back as they came. The sort is stable, and empty values stay
|
|
50
|
+
* at the end in both directions.
|
|
51
|
+
*/
|
|
52
|
+
function sortRows(rows, key, direction, format = "text", locale) {
|
|
53
|
+
const sign = DIRECTION_SIGN[direction];
|
|
54
|
+
if (sign === 0 || key === "") return [...rows];
|
|
55
|
+
return [...rows].sort((a, b) => {
|
|
56
|
+
const aMissing = isUnorderable(a[key], format);
|
|
57
|
+
const bMissing = isUnorderable(b[key], format);
|
|
58
|
+
if (aMissing || bMissing) return Number(aMissing) - Number(bMissing);
|
|
59
|
+
return sign * COMPARERS[format](a[key], b[key], locale);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
var FORMATTERS = {
|
|
63
|
+
badge: (value) => String(value),
|
|
64
|
+
code: (value) => String(value),
|
|
65
|
+
date: (value, locale) => {
|
|
66
|
+
const time = toTime(value);
|
|
67
|
+
return Number.isNaN(time) ? String(value) : new Intl.DateTimeFormat(locale, DATE_FORMAT).format(time);
|
|
68
|
+
},
|
|
69
|
+
number: (value, locale) => {
|
|
70
|
+
const number = toNumber(value);
|
|
71
|
+
return Number.isNaN(number) ? String(value) : new Intl.NumberFormat(locale).format(number);
|
|
72
|
+
},
|
|
73
|
+
text: (value) => String(value)
|
|
74
|
+
};
|
|
75
|
+
/** A cell's text in its column's format; empty for a missing value. */
|
|
76
|
+
function formatCell(value, format = "text", locale) {
|
|
77
|
+
if (isEmpty(value)) return "";
|
|
78
|
+
return FORMATTERS[format](value, locale);
|
|
79
|
+
}
|
|
80
|
+
/** Where a column's cells align: as asked, otherwise numbers right and the rest left. */
|
|
81
|
+
function columnAlign(column) {
|
|
82
|
+
return column.align ?? (column.format === "number" ? "right" : "left");
|
|
83
|
+
}
|
|
84
|
+
/** A CSS width from a column's `width`: numbers are pixels. */
|
|
85
|
+
function columnWidth(width) {
|
|
86
|
+
if (width === void 0) return "";
|
|
87
|
+
return typeof width === "number" ? `${width}px` : width;
|
|
88
|
+
}
|
|
89
|
+
/** A row's identity, read from its key field; its position when the field is missing. */
|
|
90
|
+
function rowKeyOf(row, keyField, index) {
|
|
91
|
+
const value = row[keyField];
|
|
92
|
+
return typeof value === "string" || typeof value === "number" ? value : index;
|
|
93
|
+
}
|
|
94
|
+
/** What the select-all box shows: empty, a dash for some rows, a tick for all of them. */
|
|
95
|
+
function selectionState(keys, selected) {
|
|
96
|
+
const count = keys.filter((key) => selected.has(key)).length;
|
|
97
|
+
if (count === 0) return "none";
|
|
98
|
+
return count === keys.length ? "all" : "some";
|
|
99
|
+
}
|
|
100
|
+
/** The selection after the select-all box is pressed: every row when not all were selected, else none of them. */
|
|
101
|
+
function toggleAll(keys, selected) {
|
|
102
|
+
const others = [...selected].filter((key) => !keys.includes(key));
|
|
103
|
+
return selectionState(keys, selected) === "all" ? others : [...others, ...keys];
|
|
104
|
+
}
|
|
105
|
+
/** The selection with one row added or taken out. */
|
|
106
|
+
function toggleKey(selected, key, on) {
|
|
107
|
+
const without = selected.filter((candidate) => candidate !== key);
|
|
108
|
+
return on ? [...without, key] : without;
|
|
109
|
+
}
|
|
110
|
+
export { columnAlign, columnWidth, formatCell, nextSortDirection, rowKeyOf, selectionState, sortRows, toNumber, toTime, toggleAll, toggleKey };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The tags in a text: `"a, b,,c"` gives `["a", "b", "c"]`. */
|
|
2
|
+
export declare const splitTags: (text: string) => string[];
|
|
3
|
+
/** Whether a text holds a separator, so that it adds several tags rather than being typed as one. */
|
|
4
|
+
export declare const hasTagSeparator: (text: string) => boolean;
|
|
5
|
+
/** Whether the list already holds the tag, whatever its case. */
|
|
6
|
+
export declare const hasTag: (tags: readonly string[], tag: string) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Adds tags to a list: blanks are dropped, a tag already there (whatever its case) is a duplicate, and nothing is
|
|
9
|
+
* added past `max` tags (0: no limit). Gives the new list and the duplicates met, so the field can point at them.
|
|
10
|
+
*/
|
|
11
|
+
export declare function addTags(tags: readonly string[], candidates: readonly string[], max: number): {
|
|
12
|
+
tags: string[];
|
|
13
|
+
duplicates: string[];
|
|
14
|
+
};
|
package/dist/lib/tags.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/lib/tags.ts
|
|
2
|
+
var TAG_SEPARATORS = /[,\n\r\t]/;
|
|
3
|
+
/** The tags in a text: `"a, b,,c"` gives `["a", "b", "c"]`. */
|
|
4
|
+
var splitTags = (text) => text.split(TAG_SEPARATORS).map((tag) => tag.trim()).filter((tag) => tag !== "");
|
|
5
|
+
/** Whether a text holds a separator, so that it adds several tags rather than being typed as one. */
|
|
6
|
+
var hasTagSeparator = (text) => TAG_SEPARATORS.test(text);
|
|
7
|
+
var sameTag = (a, b) => a.toLocaleLowerCase() === b.toLocaleLowerCase();
|
|
8
|
+
/** Whether the list already holds the tag, whatever its case. */
|
|
9
|
+
var hasTag = (tags, tag) => tags.some((existing) => sameTag(existing, tag));
|
|
10
|
+
/**
|
|
11
|
+
* Adds tags to a list: blanks are dropped, a tag already there (whatever its case) is a duplicate, and nothing is
|
|
12
|
+
* added past `max` tags (0: no limit). Gives the new list and the duplicates met, so the field can point at them.
|
|
13
|
+
*/
|
|
14
|
+
function addTags(tags, candidates, max) {
|
|
15
|
+
const result = {
|
|
16
|
+
duplicates: [],
|
|
17
|
+
tags: [...tags]
|
|
18
|
+
};
|
|
19
|
+
for (const candidate of candidates.map((tag) => tag.trim()).filter((tag) => tag !== "")) {
|
|
20
|
+
if (hasTag(result.tags, candidate)) {
|
|
21
|
+
result.duplicates.push(candidate);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (max > 0 && result.tags.length >= max) break;
|
|
25
|
+
result.tags.push(candidate);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
export { addTags, hasTag, hasTagSeparator, splitTags };
|
package/dist/lib/text.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/lib/text.ts
|
|
2
|
+
var LEADING_SPACE = /^[ \t]*/;
|
|
3
|
+
/**
|
|
4
|
+
* Removes the indentation every line shares, and the blank lines around the text: code written indented inside
|
|
5
|
+
* an HTML element reads as it was meant.
|
|
6
|
+
*/
|
|
7
|
+
function dedent(text) {
|
|
8
|
+
const lines = text.replace(/^\s*\n/, "").trimEnd().split("\n");
|
|
9
|
+
const indents = lines.filter((line) => line.trim() !== "").map((line) => LEADING_SPACE.exec(line)?.[0].length ?? 0);
|
|
10
|
+
const shared = indents.length === 0 ? 0 : Math.min(...indents);
|
|
11
|
+
return lines.map((line) => line.slice(shared)).join("\n");
|
|
12
|
+
}
|
|
13
|
+
export { dedent };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TOKEN_STYLES } from "../styles/shared.js";
|
|
2
|
+
// src/lib/tokens.ts
|
|
3
|
+
var adopted = { sheet: null };
|
|
4
|
+
/**
|
|
5
|
+
* Gives the document the design tokens, once. They live in the `jlib` cascade layer, so the page overrides any of
|
|
6
|
+
* them with a plain rule: `:root { --jl-accent: #7c3aed; }`.
|
|
7
|
+
*/
|
|
8
|
+
function adoptTokens() {
|
|
9
|
+
if (adopted.sheet !== null || typeof document === "undefined") return;
|
|
10
|
+
const sheet = new CSSStyleSheet();
|
|
11
|
+
sheet.replaceSync(TOKEN_STYLES);
|
|
12
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
13
|
+
adopted.sheet = sheet;
|
|
14
|
+
}
|
|
15
|
+
export { adoptTokens };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TreeAction, TreeItem, VisibleTreeItem } from '../types/jl-tree.types';
|
|
2
|
+
/** An item with `children`, even empty, is a folder. */
|
|
3
|
+
export declare const isFolder: (item: TreeItem) => boolean;
|
|
4
|
+
/** The items a person sees, in order: the roots, and the children of every open folder, with their depth. */
|
|
5
|
+
export declare function visibleItems(items: readonly TreeItem[], expanded: ReadonlySet<string>, level?: number, parent?: string | null): VisibleTreeItem[];
|
|
6
|
+
/** Every item of the tree with its parent, open or not. */
|
|
7
|
+
export declare function allItems(items: readonly TreeItem[], level?: number, parent?: string | null): VisibleTreeItem[];
|
|
8
|
+
/** The ids of the folders around an item, from the root down: open them to show it. */
|
|
9
|
+
export declare function ancestorsOf(items: readonly TreeItem[], id: string): string[];
|
|
10
|
+
/** The open folders after one is opened or closed. */
|
|
11
|
+
export declare function withExpanded(expanded: readonly string[], id: string, open: boolean): string[];
|
|
12
|
+
/**
|
|
13
|
+
* What a key does on the focused item, by the tree pattern: Up and Down move, Right opens or enters, Left closes
|
|
14
|
+
* or goes to the parent, Home and End jump, Enter and Space select. `null` for a key the tree leaves alone.
|
|
15
|
+
*/
|
|
16
|
+
export declare function treeKeyAction(items: readonly TreeItem[], expanded: ReadonlySet<string>, focused: string, key: string): TreeAction | null;
|
package/dist/lib/tree.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/lib/tree.ts
|
|
2
|
+
/** An item with `children`, even empty, is a folder. */
|
|
3
|
+
var isFolder = (item) => Array.isArray(item.children);
|
|
4
|
+
/** The items a person sees, in order: the roots, and the children of every open folder, with their depth. */
|
|
5
|
+
function visibleItems(items, expanded, level = 1, parent = null) {
|
|
6
|
+
return items.flatMap((item) => {
|
|
7
|
+
const own = {
|
|
8
|
+
item,
|
|
9
|
+
level,
|
|
10
|
+
parent
|
|
11
|
+
};
|
|
12
|
+
if (!(isFolder(item) && expanded.has(item.id))) return [own];
|
|
13
|
+
return [own, ...visibleItems(item.children ?? [], expanded, level + 1, item.id)];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Every item of the tree with its parent, open or not. */
|
|
17
|
+
function allItems(items, level = 1, parent = null) {
|
|
18
|
+
return items.flatMap((item) => [{
|
|
19
|
+
item,
|
|
20
|
+
level,
|
|
21
|
+
parent
|
|
22
|
+
}, ...allItems(item.children ?? [], level + 1, item.id)]);
|
|
23
|
+
}
|
|
24
|
+
/** The open folders after one is opened or closed. */
|
|
25
|
+
function withExpanded(expanded, id, open) {
|
|
26
|
+
const others = expanded.filter((candidate) => candidate !== id);
|
|
27
|
+
return open ? [...others, id] : others;
|
|
28
|
+
}
|
|
29
|
+
var focusAt = (visible, index) => {
|
|
30
|
+
const target = visible[index];
|
|
31
|
+
return target === void 0 ? null : {
|
|
32
|
+
id: target.item.id,
|
|
33
|
+
kind: "focus"
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/** Right: opens a closed folder, enters an open one; nothing on a file. */
|
|
37
|
+
function moveRight(visible, index, expanded) {
|
|
38
|
+
const current = visible[index];
|
|
39
|
+
if (current === void 0 || !isFolder(current.item)) return null;
|
|
40
|
+
if (!expanded.has(current.item.id)) return {
|
|
41
|
+
id: current.item.id,
|
|
42
|
+
kind: "toggle",
|
|
43
|
+
open: true
|
|
44
|
+
};
|
|
45
|
+
return visible[index + 1]?.parent === current.item.id ? focusAt(visible, index + 1) : null;
|
|
46
|
+
}
|
|
47
|
+
/** Left: closes an open folder, otherwise goes to the parent. */
|
|
48
|
+
function moveLeft(visible, index, expanded) {
|
|
49
|
+
const current = visible[index];
|
|
50
|
+
if (current === void 0) return null;
|
|
51
|
+
if (isFolder(current.item) && expanded.has(current.item.id)) return {
|
|
52
|
+
id: current.item.id,
|
|
53
|
+
kind: "toggle",
|
|
54
|
+
open: false
|
|
55
|
+
};
|
|
56
|
+
return current.parent === null ? null : {
|
|
57
|
+
id: current.parent,
|
|
58
|
+
kind: "focus"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var selectAt = (visible, index) => {
|
|
62
|
+
const current = visible[index];
|
|
63
|
+
return current === void 0 ? null : {
|
|
64
|
+
id: current.item.id,
|
|
65
|
+
kind: "select"
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
var KEY_HANDLERS = /* @__PURE__ */ new Map([
|
|
69
|
+
[" ", selectAt],
|
|
70
|
+
["ArrowDown", (visible, index) => focusAt(visible, index + 1)],
|
|
71
|
+
["ArrowLeft", moveLeft],
|
|
72
|
+
["ArrowRight", moveRight],
|
|
73
|
+
["ArrowUp", (visible, index) => focusAt(visible, index - 1)],
|
|
74
|
+
["End", (visible) => focusAt(visible, visible.length - 1)],
|
|
75
|
+
["Enter", selectAt],
|
|
76
|
+
["Home", (visible) => focusAt(visible, 0)]
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* What a key does on the focused item, by the tree pattern: Up and Down move, Right opens or enters, Left closes
|
|
80
|
+
* or goes to the parent, Home and End jump, Enter and Space select. `null` for a key the tree leaves alone.
|
|
81
|
+
*/
|
|
82
|
+
function treeKeyAction(items, expanded, focused, key) {
|
|
83
|
+
const handler = KEY_HANDLERS.get(key);
|
|
84
|
+
if (handler === void 0) return null;
|
|
85
|
+
const visible = visibleItems(items, expanded);
|
|
86
|
+
const index = visible.findIndex((entry) => entry.item.id === focused);
|
|
87
|
+
return handler(visible, Math.max(0, index), expanded);
|
|
88
|
+
}
|
|
89
|
+
export { allItems, isFolder, treeKeyAction, visibleItems, withExpanded };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/lib/trend.ts
|
|
2
|
+
var MINUS_SIGN = "−";
|
|
3
|
+
var SIGNED_NUMBER = /^\s*([+\-−])?\D*([\d.,]+)?/;
|
|
4
|
+
var SIGN_TRENDS = {
|
|
5
|
+
"-": "down",
|
|
6
|
+
"+": "up",
|
|
7
|
+
[MINUS_SIGN]: "down"
|
|
8
|
+
};
|
|
9
|
+
/** Which way a delta written as text went: a plus is up, a minus is down, zero or no sign is flat. */
|
|
10
|
+
function trendOfDelta(delta) {
|
|
11
|
+
const [, sign = "", digits = ""] = SIGNED_NUMBER.exec(delta) ?? [];
|
|
12
|
+
if (Number.parseFloat(digits.replace(/,/g, "")) === 0) return "flat";
|
|
13
|
+
return SIGN_TRENDS[sign] ?? "flat";
|
|
14
|
+
}
|
|
15
|
+
export { trendOfDelta };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element that carries a trigger's accessible state: the inner `base` part of a component (where its focus
|
|
3
|
+
* and its name are), or else the element itself.
|
|
4
|
+
*/
|
|
5
|
+
export declare function accessibleTarget(element: Element): Element;
|
|
6
|
+
/** Sets an ARIA attribute on a trigger, where screen readers read it; `null` removes it. */
|
|
7
|
+
export declare function setTriggerAria(element: Element, name: string, value: string | null): void;
|
|
8
|
+
/** The element that has the focus within a document or a shadow root, looking inside nested shadow roots. */
|
|
9
|
+
export declare function deepActiveElement(root: DocumentOrShadowRoot): Element | null;
|
|
10
|
+
/** The first control people can focus among some elements and everything inside them, in document order. */
|
|
11
|
+
export declare function firstFocusable(elements: readonly Element[]): HTMLElement | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/lib/trigger.ts
|
|
2
|
+
var NATIVE_FOCUSABLE = "a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex=\"-1\"]), [contenteditable=\"true\"]";
|
|
3
|
+
var BASE_PART = "[part~=\"base\"]";
|
|
4
|
+
/**
|
|
5
|
+
* The element that carries a trigger's accessible state: the inner `base` part of a component (where its focus
|
|
6
|
+
* and its name are), or else the element itself.
|
|
7
|
+
*/
|
|
8
|
+
function accessibleTarget(element) {
|
|
9
|
+
return element.shadowRoot?.querySelector(BASE_PART) ?? element;
|
|
10
|
+
}
|
|
11
|
+
/** Sets an ARIA attribute on a trigger, where screen readers read it; `null` removes it. */
|
|
12
|
+
function setTriggerAria(element, name, value) {
|
|
13
|
+
const target = accessibleTarget(element);
|
|
14
|
+
if (value === null) {
|
|
15
|
+
target.removeAttribute(name);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
target.setAttribute(name, value);
|
|
19
|
+
}
|
|
20
|
+
/** The element that has the focus within a document or a shadow root, looking inside nested shadow roots. */
|
|
21
|
+
function deepActiveElement(root) {
|
|
22
|
+
const active = root.activeElement;
|
|
23
|
+
const inner = active?.shadowRoot ?? null;
|
|
24
|
+
if (inner === null || inner.activeElement === null) return active;
|
|
25
|
+
return deepActiveElement(inner);
|
|
26
|
+
}
|
|
27
|
+
/** A form-associated custom element (jl-input, jl-button…) is a control: its own `focus()` reaches its inside. */
|
|
28
|
+
var isFormComponent = (element) => element.constructor.formAssociated === true;
|
|
29
|
+
var canFocus = (element) => element instanceof HTMLElement && (element.matches(NATIVE_FOCUSABLE) || isFormComponent(element)) && !element.matches(":disabled") && element.checkVisibility();
|
|
30
|
+
/** The first control people can focus among some elements and everything inside them, in document order. */
|
|
31
|
+
function firstFocusable(elements) {
|
|
32
|
+
return elements.flatMap((element) => [element, ...element.querySelectorAll("*")]).find(canFocus) ?? null;
|
|
33
|
+
}
|
|
34
|
+
export { accessibleTarget, deepActiveElement, firstFocusable, setTriggerAria };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-accordion-item.css?raw
|
|
2
|
+
var jl_accordion_item_default = ":host {\n display: block;\n border-bottom: 1px solid var(--jl-line);\n /* Lets the content's height animate from 0 to auto. */\n interpolate-size: allow-keywords;\n --pad-x: 0px;\n}\n\nsummary {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 48px;\n padding: 12px var(--pad-x);\n border-radius: var(--jl-radius-xs);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n font-weight: 600;\n line-height: 1.4;\n list-style: none;\n cursor: pointer;\n user-select: none;\n}\n\nsummary::-webkit-details-marker {\n display: none;\n}\n\n.heading {\n flex: 1;\n min-width: 0;\n}\n\n.icon {\n color: var(--jl-muted);\n}\n\n.chevron {\n color: var(--jl-faint);\n transition: rotate var(--jl-slow) var(--jl-ease);\n}\n\nsummary:hover .chevron {\n color: var(--jl-text);\n}\n\ndetails[open] .chevron {\n rotate: 180deg;\n}\n\nsummary:focus-visible {\n box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--jl-brand) 60%, transparent);\n}\n\n/*\n * The content box is a pseudo-element BASE_STYLES' \"* { box-sizing: inherit }\" does not reach: without this, what\n * it holds (and the slotted fields, which inherit through it) would fall back to content-box.\n */\ndetails::details-content {\n box-sizing: inherit;\n block-size: 0;\n overflow: clip;\n transition:\n block-size var(--jl-slow) var(--jl-ease),\n content-visibility var(--jl-slow) allow-discrete;\n}\n\ndetails[open]::details-content {\n block-size: auto;\n}\n\n.content {\n padding: 0 var(--pad-x) 16px;\n color: var(--jl-text-2);\n font-size: var(--jl-font-md);\n line-height: 1.6;\n}\n\n:host([disabled]) summary {\n color: var(--jl-faint);\n cursor: not-allowed;\n}\n\n:host([disabled]) summary:hover .chevron {\n color: var(--jl-faint);\n}\n";
|
|
3
|
+
export { jl_accordion_item_default as default };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
/**
|
|
3
|
+
* A section that opens and closes under its heading, on the browser's own `<details>`. Alone it is a disclosure;
|
|
4
|
+
* in a `jl-accordion` it is one of a list. `toggle` fires with the new state.
|
|
5
|
+
*/
|
|
6
|
+
export declare class JlAccordionItem extends JlElement {
|
|
7
|
+
static readonly selector = "jl-accordion-item";
|
|
8
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
9
|
+
toggle: boolean;
|
|
10
|
+
}>>;
|
|
11
|
+
readonly heading: Readonly<Readonly<{
|
|
12
|
+
get(): string;
|
|
13
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
14
|
+
}>>;
|
|
15
|
+
readonly open: Readonly<Readonly<{
|
|
16
|
+
get(): boolean;
|
|
17
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
18
|
+
}>>;
|
|
19
|
+
/** An icon before the heading. */
|
|
20
|
+
readonly icon: Readonly<Readonly<{
|
|
21
|
+
get(): string;
|
|
22
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
23
|
+
}>>;
|
|
24
|
+
/** Cannot be opened or closed by people. */
|
|
25
|
+
readonly disabled: Readonly<Readonly<{
|
|
26
|
+
get(): boolean;
|
|
27
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
28
|
+
}>>;
|
|
29
|
+
private readonly _refs;
|
|
30
|
+
templateHtml(): Node;
|
|
31
|
+
templateCss(): string;
|
|
32
|
+
onConnect(): void;
|
|
33
|
+
private renderOpen;
|
|
34
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { registerIcons } from "../lib/icons.js";
|
|
4
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
5
|
+
import jl_accordion_item_default from "./jl-accordion-item.css_raw.js";
|
|
6
|
+
import { css } from "@jadis/core";
|
|
7
|
+
import { ChevronDown } from "lucide";
|
|
8
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
9
|
+
// src/molecules/jl-accordion-item.tsx
|
|
10
|
+
registerIcons({ "chevron-down": ChevronDown });
|
|
11
|
+
var ICON_SIZE = 16;
|
|
12
|
+
/**
|
|
13
|
+
* A section that opens and closes under its heading, on the browser's own `<details>`. Alone it is a disclosure;
|
|
14
|
+
* in a `jl-accordion` it is one of a list. `toggle` fires with the new state.
|
|
15
|
+
*/
|
|
16
|
+
var JlAccordionItem = class extends JlElement {
|
|
17
|
+
static selector = "jl-accordion-item";
|
|
18
|
+
events = this.useEvents();
|
|
19
|
+
heading = this.useChange("", (value) => {
|
|
20
|
+
this._refs.heading.textContent = value;
|
|
21
|
+
}, { attribute: "heading" });
|
|
22
|
+
open = this.useChange(false, (value) => this.renderOpen(value), { attribute: "open" });
|
|
23
|
+
/** An icon before the heading. */
|
|
24
|
+
icon = this.useChange("", (name) => {
|
|
25
|
+
this._refs.icon.name.set(name);
|
|
26
|
+
this._refs.icon.hidden = name === "";
|
|
27
|
+
}, { attribute: "icon" });
|
|
28
|
+
/** Cannot be opened or closed by people. */
|
|
29
|
+
disabled = this.useChange(false, (value) => {
|
|
30
|
+
this.reflect("disabled", value);
|
|
31
|
+
this._refs.summary.toggleAttribute("aria-disabled", value);
|
|
32
|
+
this._refs.summary.tabIndex = value ? -1 : 0;
|
|
33
|
+
}, { attribute: "disabled" });
|
|
34
|
+
_refs = this.useRefs((ref) => ({
|
|
35
|
+
details: ref("details"),
|
|
36
|
+
heading: ref(".heading"),
|
|
37
|
+
icon: ref(".icon"),
|
|
38
|
+
summary: ref("summary")
|
|
39
|
+
}));
|
|
40
|
+
templateHtml() {
|
|
41
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
42
|
+
attrs: { part: "base" },
|
|
43
|
+
children: [/* @__PURE__ */ jsxs("summary", { children: [
|
|
44
|
+
/* @__PURE__ */ jsx(JlIcon, {
|
|
45
|
+
class: "icon",
|
|
46
|
+
size: ICON_SIZE,
|
|
47
|
+
hidden: true
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ jsx("span", { class: "heading" }),
|
|
50
|
+
/* @__PURE__ */ jsx(JlIcon, {
|
|
51
|
+
class: "chevron",
|
|
52
|
+
name: "chevron-down",
|
|
53
|
+
size: ICON_SIZE
|
|
54
|
+
})
|
|
55
|
+
] }), /* @__PURE__ */ jsx("div", {
|
|
56
|
+
class: "content",
|
|
57
|
+
children: /* @__PURE__ */ jsx("slot", {})
|
|
58
|
+
})]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
templateCss() {
|
|
62
|
+
return css`${BASE_STYLES}${jl_accordion_item_default}`;
|
|
63
|
+
}
|
|
64
|
+
onConnect() {
|
|
65
|
+
const details = this._refs.details;
|
|
66
|
+
this.on(this._refs.summary, "click", (event) => {
|
|
67
|
+
if (this.disabled.get()) event.preventDefault();
|
|
68
|
+
});
|
|
69
|
+
this.on(details, "toggle", (event) => {
|
|
70
|
+
const { oldState, newState } = event;
|
|
71
|
+
if (oldState === newState) return;
|
|
72
|
+
if (details.open !== this.open.get()) this.open.set(details.open);
|
|
73
|
+
this.events.emit("toggle", details.open);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
renderOpen(open) {
|
|
77
|
+
this.reflect("open", open);
|
|
78
|
+
this._refs.details.open = open;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
JlAccordionItem.register();
|
|
82
|
+
export { JlAccordionItem };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-accordion.css?raw
|
|
2
|
+
var jl_accordion_default = ":host {\n display: block;\n}\n\n:host([variant=\"bordered\"]) .accordion {\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius);\n background: var(--jl-surface);\n}\n\n:host([variant=\"bordered\"]) ::slotted(*) {\n --pad-x: 16px;\n}\n\n:host([variant=\"bordered\"]) ::slotted(:last-child) {\n border-bottom: none;\n}\n";
|
|
3
|
+
export { jl_accordion_default as default };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { AccordionVariant } from '../types/jl-accordion.types';
|
|
3
|
+
export declare const ACCORDION_VARIANTS: readonly AccordionVariant[];
|
|
4
|
+
/**
|
|
5
|
+
* A list of `jl-accordion-item`s. One item opens at a time (opening one closes the others) unless `multiple` is
|
|
6
|
+
* set. `bordered` puts the items in a box with lines between them.
|
|
7
|
+
*/
|
|
8
|
+
export declare class JlAccordion extends JlElement {
|
|
9
|
+
static readonly selector = "jl-accordion";
|
|
10
|
+
/** Lets several items stay open together. */
|
|
11
|
+
readonly multiple: Readonly<Readonly<{
|
|
12
|
+
get(): boolean;
|
|
13
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
14
|
+
}>>;
|
|
15
|
+
readonly variant: Readonly<Readonly<{
|
|
16
|
+
get(): AccordionVariant;
|
|
17
|
+
set(setter: ((prevState: AccordionVariant) => AccordionVariant) | AccordionVariant): void;
|
|
18
|
+
}>>;
|
|
19
|
+
templateHtml(): Node;
|
|
20
|
+
templateCss(): string;
|
|
21
|
+
onConnect(): void;
|
|
22
|
+
private items;
|
|
23
|
+
private onToggle;
|
|
24
|
+
/** Without `multiple`, only the first open item stays open. */
|
|
25
|
+
private keepOneOpen;
|
|
26
|
+
private closeAllBut;
|
|
27
|
+
}
|