@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,220 @@
|
|
|
1
|
+
import { BASE_STYLES, CONTROL_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlFormControl } from "../base/jl-form-control.js";
|
|
3
|
+
import { oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { CONTROL_SIZES } from "../lib/options.js";
|
|
6
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
7
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
8
|
+
import { clamp, parseNumber, stepValue } from "../lib/numeric.js";
|
|
9
|
+
import jl_number_input_default from "./jl-number-input.css_raw.js";
|
|
10
|
+
import { css } from "@jadis/core";
|
|
11
|
+
import { Minus, Plus } from "lucide";
|
|
12
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
13
|
+
// src/molecules/jl-number-input.tsx
|
|
14
|
+
registerIcons({
|
|
15
|
+
minus: Minus,
|
|
16
|
+
plus: Plus
|
|
17
|
+
});
|
|
18
|
+
var DEFAULT_STEP = 1;
|
|
19
|
+
var PAGE_STEPS = 10;
|
|
20
|
+
var ICON_SIZE = 14;
|
|
21
|
+
var VALUE_MISSING = "Please fill in this field.";
|
|
22
|
+
var positiveStep = (value) => {
|
|
23
|
+
const parsed = parseNumber(value);
|
|
24
|
+
return parsed === null || parsed <= 0 ? DEFAULT_STEP : parsed;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A number field with − and + buttons. The arrow keys step it (Page Up/Down by ten steps, Home and End to the
|
|
28
|
+
* bounds); what is typed is read and kept between `min` and `max` when the field is left. Empty is `null`.
|
|
29
|
+
*/
|
|
30
|
+
var JlNumberInput = class extends JlFormControl {
|
|
31
|
+
static selector = "jl-number-input";
|
|
32
|
+
events = this.useEvents();
|
|
33
|
+
min = this.useChange(null, () => this.renderBounds(), {
|
|
34
|
+
attribute: "min",
|
|
35
|
+
parse: parseNumber
|
|
36
|
+
});
|
|
37
|
+
max = this.useChange(null, () => this.renderBounds(), {
|
|
38
|
+
attribute: "max",
|
|
39
|
+
parse: parseNumber
|
|
40
|
+
});
|
|
41
|
+
step = this.useChange(DEFAULT_STEP, () => void 0, {
|
|
42
|
+
attribute: "step",
|
|
43
|
+
parse: positiveStep
|
|
44
|
+
});
|
|
45
|
+
/** The number, or `null` while the field is empty. */
|
|
46
|
+
value = this.useChange(null, (value) => this.renderValue(value), {
|
|
47
|
+
attribute: "value",
|
|
48
|
+
parse: parseNumber
|
|
49
|
+
});
|
|
50
|
+
placeholder = this.useChange("", (value) => setOrRemove(this._refs.native, "placeholder", value), { attribute: "placeholder" });
|
|
51
|
+
/** The field's name for screen readers, when no visible label names it. */
|
|
52
|
+
label = this.useChange("", (value) => setOrRemove(this._refs.native, "aria-label", value), { attribute: "label" });
|
|
53
|
+
size = this.useChange("md", (value) => this.reflect("size", value), {
|
|
54
|
+
attribute: "size",
|
|
55
|
+
parse: oneOf(CONTROL_SIZES, "md")
|
|
56
|
+
});
|
|
57
|
+
required = this.useChange(false, (value) => {
|
|
58
|
+
this._refs.native.toggleAttribute("aria-required", value);
|
|
59
|
+
this.reflect("required", value);
|
|
60
|
+
this.renderValidity();
|
|
61
|
+
}, { attribute: "required" });
|
|
62
|
+
disabled = this.useChange(false, (value) => this.renderDisabled(value), { attribute: "disabled" });
|
|
63
|
+
/** Draws the field in the danger colour, for an error the page found itself. */
|
|
64
|
+
invalid = this.useChange(false, (value) => {
|
|
65
|
+
this.reflect("invalid", value);
|
|
66
|
+
this._refs.native.toggleAttribute("aria-invalid", value);
|
|
67
|
+
}, { attribute: "invalid" });
|
|
68
|
+
_state = { disabledByForm: false };
|
|
69
|
+
_refs = this.useRefs((ref) => ({
|
|
70
|
+
down: ref(".down"),
|
|
71
|
+
native: ref("input"),
|
|
72
|
+
up: ref(".up")
|
|
73
|
+
}));
|
|
74
|
+
focus(options) {
|
|
75
|
+
this._refs.native.focus(options);
|
|
76
|
+
}
|
|
77
|
+
/** Adds one step (or `count` steps, negative to go down), within the bounds, and fires `change`. */
|
|
78
|
+
stepBy(count) {
|
|
79
|
+
const direction = count < 0 ? -1 : 1;
|
|
80
|
+
const amount = this.step.get() * Math.abs(count);
|
|
81
|
+
this.commitNumber(stepValue(this.typedNumber(), amount, direction, this.min.get(), this.max.get()));
|
|
82
|
+
}
|
|
83
|
+
templateHtml() {
|
|
84
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
85
|
+
class: "control",
|
|
86
|
+
attrs: { part: "base" },
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsx("button", {
|
|
89
|
+
class: "action step down",
|
|
90
|
+
type: "button",
|
|
91
|
+
tabIndex: -1,
|
|
92
|
+
"aria-label": "Decrease",
|
|
93
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
94
|
+
name: "minus",
|
|
95
|
+
size: ICON_SIZE
|
|
96
|
+
})
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ jsx("input", {
|
|
99
|
+
class: "native",
|
|
100
|
+
type: "text",
|
|
101
|
+
inputMode: "decimal",
|
|
102
|
+
role: "spinbutton",
|
|
103
|
+
autocomplete: "off"
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ jsx("button", {
|
|
106
|
+
class: "action step up",
|
|
107
|
+
type: "button",
|
|
108
|
+
tabIndex: -1,
|
|
109
|
+
"aria-label": "Increase",
|
|
110
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
111
|
+
name: "plus",
|
|
112
|
+
size: ICON_SIZE
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
templateCss() {
|
|
119
|
+
return css`${BASE_STYLES}${CONTROL_STYLES}${jl_number_input_default}`;
|
|
120
|
+
}
|
|
121
|
+
onConnect() {
|
|
122
|
+
const native = this._refs.native;
|
|
123
|
+
this.on(native, "input", (event) => event.stopPropagation());
|
|
124
|
+
this.on(native, "blur", () => this.commitText());
|
|
125
|
+
this.on(native, "keydown", (event) => this.onKey(event));
|
|
126
|
+
for (const button of [this._refs.down, this._refs.up]) this.on(button, "mousedown", (event) => event.preventDefault());
|
|
127
|
+
this.on(this._refs.down, "click", () => this.stepBy(-1));
|
|
128
|
+
this.on(this._refs.up, "click", () => this.stepBy(1));
|
|
129
|
+
}
|
|
130
|
+
resetValue() {
|
|
131
|
+
this.value.set(parseNumber(this.getAttribute("value")));
|
|
132
|
+
}
|
|
133
|
+
/** The browser calls it for a disabled fieldset and for the element's own disabled attribute, even before render. */
|
|
134
|
+
setDisabledByForm(disabled) {
|
|
135
|
+
this._state.disabledByForm = disabled;
|
|
136
|
+
this.whenRendered(() => this.renderDisabled(this.disabled.get()));
|
|
137
|
+
}
|
|
138
|
+
onKey(event) {
|
|
139
|
+
const action = (/* @__PURE__ */ new Map([
|
|
140
|
+
["ArrowDown", () => this.stepBy(-1)],
|
|
141
|
+
["ArrowUp", () => this.stepBy(1)],
|
|
142
|
+
["End", () => this.goToBound(this.max.get())],
|
|
143
|
+
["Enter", () => this.commitText()],
|
|
144
|
+
["Home", () => this.goToBound(this.min.get())],
|
|
145
|
+
["PageDown", () => this.stepBy(-10)],
|
|
146
|
+
["PageUp", () => this.stepBy(PAGE_STEPS)]
|
|
147
|
+
])).get(event.key);
|
|
148
|
+
if (action === void 0) return;
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
action();
|
|
151
|
+
}
|
|
152
|
+
/** Home and End go to a bound when there is one, and otherwise move the caret as in any field. */
|
|
153
|
+
goToBound(bound) {
|
|
154
|
+
if (bound === null) return;
|
|
155
|
+
this.commitNumber(bound);
|
|
156
|
+
}
|
|
157
|
+
/** The number typed so far, or the value when the text cannot be read. */
|
|
158
|
+
typedNumber() {
|
|
159
|
+
const text = this._refs.native.value;
|
|
160
|
+
return text.trim() === "" ? null : parseNumber(text) ?? this.value.get();
|
|
161
|
+
}
|
|
162
|
+
/** Reads what was typed: empty gives null, unreadable text goes back to the value, a number is kept in bounds. */
|
|
163
|
+
commitText() {
|
|
164
|
+
const text = this._refs.native.value;
|
|
165
|
+
if (text.trim() === "") {
|
|
166
|
+
this.commit(null);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const typed = parseNumber(text);
|
|
170
|
+
if (typed === null) {
|
|
171
|
+
this.renderValue(this.value.get());
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.commitNumber(typed);
|
|
175
|
+
}
|
|
176
|
+
commitNumber(value) {
|
|
177
|
+
this.commit(clamp(value, this.min.get(), this.max.get()));
|
|
178
|
+
}
|
|
179
|
+
commit(value) {
|
|
180
|
+
const changed = value !== this.value.get();
|
|
181
|
+
this.value.set(value);
|
|
182
|
+
if (changed) this.events.emit("change", value);
|
|
183
|
+
}
|
|
184
|
+
renderValue(value) {
|
|
185
|
+
const native = this._refs.native;
|
|
186
|
+
native.value = value === null ? "" : String(value);
|
|
187
|
+
setOrRemove(native, "aria-valuenow", value === null ? "" : String(value));
|
|
188
|
+
this.internals.setFormValue(value === null ? "" : String(value));
|
|
189
|
+
this.renderButtons();
|
|
190
|
+
this.renderValidity();
|
|
191
|
+
}
|
|
192
|
+
renderBounds() {
|
|
193
|
+
setOrRemove(this._refs.native, "aria-valuemin", String(this.min.get() ?? ""));
|
|
194
|
+
setOrRemove(this._refs.native, "aria-valuemax", String(this.max.get() ?? ""));
|
|
195
|
+
this.renderButtons();
|
|
196
|
+
}
|
|
197
|
+
renderDisabled(disabled) {
|
|
198
|
+
this._refs.native.disabled = disabled || this._state.disabledByForm;
|
|
199
|
+
this.reflect("disabled", disabled);
|
|
200
|
+
this.renderButtons();
|
|
201
|
+
}
|
|
202
|
+
/** A button that cannot move the value further is disabled. */
|
|
203
|
+
renderButtons() {
|
|
204
|
+
const value = this.value.get();
|
|
205
|
+
const max = this.max.get();
|
|
206
|
+
const min = this.min.get();
|
|
207
|
+
const off = this.disabled.get() || this._state.disabledByForm;
|
|
208
|
+
this._refs.down.disabled = off || value !== null && min !== null && value <= min;
|
|
209
|
+
this._refs.up.disabled = off || value !== null && max !== null && value >= max;
|
|
210
|
+
}
|
|
211
|
+
renderValidity() {
|
|
212
|
+
if (!(this.required.get() && this.value.get() === null)) {
|
|
213
|
+
this.internals.setValidity({});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.internals.setValidity({ valueMissing: true }, VALUE_MISSING, this._refs.native);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
JlNumberInput.register();
|
|
220
|
+
export { JlNumberInput };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-pagination.css?raw
|
|
2
|
+
var jl_pagination_default = ":host {\n display: block;\n --height: var(--jl-control-md);\n --font-size: var(--jl-font-sm);\n --entry: var(--height);\n --gap: 4px;\n /* The width of an entry on a phone. */\n --min-button: 26px;\n}\n\n:host([size=\"sm\"]) {\n --height: var(--jl-control-sm);\n --font-size: var(--jl-font-xs);\n --min-button: 24px;\n}\n\n:host([size=\"lg\"]) {\n --height: var(--jl-control-lg);\n --font-size: var(--jl-font-md);\n --min-button: 32px;\n}\n\n/* Each entry is a square the height of the row; on a phone the entries tighten so a row of seven or nine fits. */\n@media (width < 480px) {\n :host {\n --entry: var(--min-button);\n --gap: 2px;\n }\n}\n\nnav {\n display: flex;\n align-items: center;\n gap: var(--gap);\n}\n\n.pages {\n display: flex;\n align-items: center;\n gap: var(--gap);\n padding: 0;\n list-style: none;\n}\n\n.pages li {\n display: flex;\n width: var(--entry);\n}\n\n.button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: var(--height);\n padding: 0 2px;\n border: 1px solid transparent;\n border-radius: var(--jl-radius-sm);\n background: none;\n color: var(--jl-text-2);\n font-size: var(--font-size);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n cursor: pointer;\n transition:\n background var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n.button:hover:not(:disabled, [aria-current]) {\n background: var(--jl-surface-2);\n color: var(--jl-text);\n}\n\n.button:active:not(:disabled, [aria-current]) {\n background: var(--jl-surface-3);\n}\n\n.previous,\n.next {\n flex: none;\n width: calc(var(--entry) + 2px);\n border-color: var(--jl-line-strong);\n color: var(--jl-text);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.page[aria-current] {\n background: var(--jl-accent);\n color: var(--jl-on-accent);\n cursor: default;\n}\n\n.pages .gap {\n display: inline-flex;\n width: calc(var(--entry) - 10px);\n align-items: flex-end;\n justify-content: center;\n color: var(--jl-faint);\n font-size: var(--font-size);\n user-select: none;\n}\n\n.status {\n flex: none;\n min-width: 72px;\n padding: 0 8px;\n color: var(--jl-text-2);\n font-size: var(--font-size);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n text-align: center;\n white-space: nowrap;\n}\n";
|
|
3
|
+
export { jl_pagination_default as default };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { ControlSize } from '../types/options.types';
|
|
3
|
+
/**
|
|
4
|
+
* Moves through pages of results: the first and last pages, the current one with its neighbours, and gaps between;
|
|
5
|
+
* or, `compact`, "3 of 12" between two arrows. `change` fires with the page people go to.
|
|
6
|
+
*/
|
|
7
|
+
export declare class JlPagination extends JlElement {
|
|
8
|
+
static readonly selector = "jl-pagination";
|
|
9
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
10
|
+
change: number;
|
|
11
|
+
}>>;
|
|
12
|
+
/** The current page, from 1. */
|
|
13
|
+
readonly page: Readonly<Readonly<{
|
|
14
|
+
get(): number;
|
|
15
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
16
|
+
}>>;
|
|
17
|
+
/** How many pages there are. */
|
|
18
|
+
readonly pages: Readonly<Readonly<{
|
|
19
|
+
get(): number;
|
|
20
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
21
|
+
}>>;
|
|
22
|
+
/** How many pages show on each side of the current one. */
|
|
23
|
+
readonly siblings: Readonly<Readonly<{
|
|
24
|
+
get(): number;
|
|
25
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
26
|
+
}>>;
|
|
27
|
+
/** How many pages always show at each end. */
|
|
28
|
+
readonly boundaries: Readonly<Readonly<{
|
|
29
|
+
get(): number;
|
|
30
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
31
|
+
}>>;
|
|
32
|
+
/** Only the arrows and "3 of 12", for narrow places. */
|
|
33
|
+
readonly compact: Readonly<Readonly<{
|
|
34
|
+
get(): boolean;
|
|
35
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
36
|
+
}>>;
|
|
37
|
+
readonly size: Readonly<Readonly<{
|
|
38
|
+
get(): ControlSize;
|
|
39
|
+
set(setter: ((prevState: ControlSize) => ControlSize) | ControlSize): void;
|
|
40
|
+
}>>;
|
|
41
|
+
/** The navigation's name for screen readers, when a page has more than one. */
|
|
42
|
+
readonly label: Readonly<Readonly<{
|
|
43
|
+
get(): string;
|
|
44
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
45
|
+
}>>;
|
|
46
|
+
private readonly _refs;
|
|
47
|
+
templateHtml(): Node;
|
|
48
|
+
templateCss(): string;
|
|
49
|
+
onConnect(): void;
|
|
50
|
+
/** The current page, kept within the pages there are. */
|
|
51
|
+
private currentPage;
|
|
52
|
+
private render;
|
|
53
|
+
private entry;
|
|
54
|
+
private renderSize;
|
|
55
|
+
/** Goes one page back or forward from an arrow; at an end, the focus moves to the other arrow. */
|
|
56
|
+
private step;
|
|
57
|
+
/** Goes to a page people picked, and says so. */
|
|
58
|
+
private goTo;
|
|
59
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
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 { registerIcons } from "../lib/icons.js";
|
|
5
|
+
import { CONTROL_SIZES } from "../lib/options.js";
|
|
6
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
7
|
+
import { clampPage, pageItems } from "../lib/pagination.js";
|
|
8
|
+
import jl_pagination_default from "./jl-pagination.css_raw.js";
|
|
9
|
+
import { css } from "@jadis/core";
|
|
10
|
+
import { ChevronLeft, ChevronRight } from "lucide";
|
|
11
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
12
|
+
// src/molecules/jl-pagination.tsx
|
|
13
|
+
registerIcons({
|
|
14
|
+
"chevron-left": ChevronLeft,
|
|
15
|
+
"chevron-right": ChevronRight
|
|
16
|
+
});
|
|
17
|
+
var ICON_SIZES = {
|
|
18
|
+
lg: 18,
|
|
19
|
+
md: 16,
|
|
20
|
+
sm: 14
|
|
21
|
+
};
|
|
22
|
+
var DEFAULT_LABEL = "Pagination";
|
|
23
|
+
var MAX_PAGES = Number.MAX_SAFE_INTEGER;
|
|
24
|
+
var MAX_NEIGHBOURS = 5;
|
|
25
|
+
var STEP_BACK = -1;
|
|
26
|
+
var STEP_FORWARD = 1;
|
|
27
|
+
/**
|
|
28
|
+
* Moves through pages of results: the first and last pages, the current one with its neighbours, and gaps between;
|
|
29
|
+
* or, `compact`, "3 of 12" between two arrows. `change` fires with the page people go to.
|
|
30
|
+
*/
|
|
31
|
+
var JlPagination = class extends JlElement {
|
|
32
|
+
static selector = "jl-pagination";
|
|
33
|
+
events = this.useEvents();
|
|
34
|
+
/** The current page, from 1. */
|
|
35
|
+
page = this.useChange(1, () => this.render(), {
|
|
36
|
+
attribute: "page",
|
|
37
|
+
parse: clampedNumber(1, 1, MAX_PAGES)
|
|
38
|
+
});
|
|
39
|
+
/** How many pages there are. */
|
|
40
|
+
pages = this.useChange(1, () => this.render(), {
|
|
41
|
+
attribute: "pages",
|
|
42
|
+
parse: clampedNumber(1, 0, MAX_PAGES)
|
|
43
|
+
});
|
|
44
|
+
/** How many pages show on each side of the current one. */
|
|
45
|
+
siblings = this.useChange(1, () => this.render(), {
|
|
46
|
+
attribute: "siblings",
|
|
47
|
+
parse: clampedNumber(1, 0, MAX_NEIGHBOURS)
|
|
48
|
+
});
|
|
49
|
+
/** How many pages always show at each end. */
|
|
50
|
+
boundaries = this.useChange(1, () => this.render(), {
|
|
51
|
+
attribute: "boundaries",
|
|
52
|
+
parse: clampedNumber(1, 0, MAX_NEIGHBOURS)
|
|
53
|
+
});
|
|
54
|
+
/** Only the arrows and "3 of 12", for narrow places. */
|
|
55
|
+
compact = this.useChange(false, (value) => {
|
|
56
|
+
this.reflect("compact", value);
|
|
57
|
+
this.render();
|
|
58
|
+
}, { attribute: "compact" });
|
|
59
|
+
size = this.useChange("md", (value) => this.renderSize(value), {
|
|
60
|
+
attribute: "size",
|
|
61
|
+
parse: oneOf(CONTROL_SIZES, "md")
|
|
62
|
+
});
|
|
63
|
+
/** The navigation's name for screen readers, when a page has more than one. */
|
|
64
|
+
label = this.useChange(DEFAULT_LABEL, (value) => {
|
|
65
|
+
this._refs.nav.setAttribute("aria-label", value || DEFAULT_LABEL);
|
|
66
|
+
}, { attribute: "label" });
|
|
67
|
+
_refs = this.useRefs((ref) => ({
|
|
68
|
+
list: ref(".pages"),
|
|
69
|
+
nav: ref("nav"),
|
|
70
|
+
next: ref(".next"),
|
|
71
|
+
nextIcon: ref(".next jl-icon"),
|
|
72
|
+
previous: ref(".previous"),
|
|
73
|
+
previousIcon: ref(".previous jl-icon"),
|
|
74
|
+
status: ref(".status")
|
|
75
|
+
}));
|
|
76
|
+
templateHtml() {
|
|
77
|
+
return /* @__PURE__ */ jsxs("nav", {
|
|
78
|
+
attrs: { part: "base" },
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx("button", {
|
|
81
|
+
type: "button",
|
|
82
|
+
class: "button previous",
|
|
83
|
+
"aria-label": "Previous page",
|
|
84
|
+
children: /* @__PURE__ */ jsx(JlIcon, { name: "chevron-left" })
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ jsx("ol", { class: "pages" }),
|
|
87
|
+
/* @__PURE__ */ jsx("span", {
|
|
88
|
+
class: "status",
|
|
89
|
+
hidden: true
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ jsx("button", {
|
|
92
|
+
type: "button",
|
|
93
|
+
class: "button next",
|
|
94
|
+
"aria-label": "Next page",
|
|
95
|
+
children: /* @__PURE__ */ jsx(JlIcon, { name: "chevron-right" })
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
templateCss() {
|
|
101
|
+
return css`${BASE_STYLES}${jl_pagination_default}`;
|
|
102
|
+
}
|
|
103
|
+
onConnect() {
|
|
104
|
+
this.on(this._refs.previous, "click", () => this.step(STEP_BACK));
|
|
105
|
+
this.on(this._refs.next, "click", () => this.step(STEP_FORWARD));
|
|
106
|
+
this.on(this._refs.list, "click", (event) => {
|
|
107
|
+
const button = event.target.closest("[data-page]");
|
|
108
|
+
if (button !== null) {
|
|
109
|
+
this.goTo(Number(button.dataset.page));
|
|
110
|
+
this._refs.list.querySelector("[aria-current]")?.focus();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** The current page, kept within the pages there are. */
|
|
115
|
+
currentPage() {
|
|
116
|
+
return clampPage(this.page.get(), this.pages.get());
|
|
117
|
+
}
|
|
118
|
+
render() {
|
|
119
|
+
const pages = this.pages.get();
|
|
120
|
+
const page = this.currentPage();
|
|
121
|
+
const compact = this.compact.get();
|
|
122
|
+
this._refs.previous.disabled = page <= 1;
|
|
123
|
+
this._refs.next.disabled = page >= pages;
|
|
124
|
+
this._refs.list.hidden = compact;
|
|
125
|
+
this._refs.status.hidden = !compact;
|
|
126
|
+
this._refs.status.replaceChildren(/* @__PURE__ */ jsx("span", {
|
|
127
|
+
class: "sr-only",
|
|
128
|
+
children: "Page "
|
|
129
|
+
}), `${page} of ${Math.max(pages, 1)}`);
|
|
130
|
+
if (!compact) {
|
|
131
|
+
const items = pageItems(page, pages, this.siblings.get(), this.boundaries.get());
|
|
132
|
+
this._refs.list.replaceChildren(...items.map((item) => this.entry(item, page)));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
entry(item, current) {
|
|
136
|
+
if (item.type === "gap") return /* @__PURE__ */ jsx("li", {
|
|
137
|
+
class: "gap",
|
|
138
|
+
"aria-hidden": "true",
|
|
139
|
+
children: "…"
|
|
140
|
+
});
|
|
141
|
+
const isCurrent = item.page === current;
|
|
142
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
143
|
+
type: "button",
|
|
144
|
+
class: "button page",
|
|
145
|
+
"data-page": item.page,
|
|
146
|
+
"aria-label": `Page ${item.page}`,
|
|
147
|
+
"aria-current": isCurrent ? "page" : void 0,
|
|
148
|
+
children: String(item.page)
|
|
149
|
+
}) });
|
|
150
|
+
}
|
|
151
|
+
renderSize(size) {
|
|
152
|
+
this.reflect("size", size);
|
|
153
|
+
this._refs.previousIcon.size.set(ICON_SIZES[size]);
|
|
154
|
+
this._refs.nextIcon.size.set(ICON_SIZES[size]);
|
|
155
|
+
}
|
|
156
|
+
/** Goes one page back or forward from an arrow; at an end, the focus moves to the other arrow. */
|
|
157
|
+
step(direction) {
|
|
158
|
+
this.goTo(this.currentPage() + direction);
|
|
159
|
+
const arrow = direction === STEP_BACK ? this._refs.previous : this._refs.next;
|
|
160
|
+
const other = direction === STEP_BACK ? this._refs.next : this._refs.previous;
|
|
161
|
+
if (arrow.disabled) other.focus();
|
|
162
|
+
}
|
|
163
|
+
/** Goes to a page people picked, and says so. */
|
|
164
|
+
goTo(page) {
|
|
165
|
+
const target = clampPage(page, this.pages.get());
|
|
166
|
+
if (target === this.currentPage()) return;
|
|
167
|
+
this.page.set(target);
|
|
168
|
+
this.events.emit("change", target);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
JlPagination.register();
|
|
172
|
+
export { JlPagination };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-popover.css?raw
|
|
2
|
+
var jl_popover_default = ":host {\n display: inline-flex;\n vertical-align: middle;\n}\n\n.panel {\n width: max-content;\n min-width: 200px;\n max-width: min(360px, calc(100vw - 16px));\n padding: 14px 16px;\n font-size: var(--jl-font-md);\n line-height: 1.5;\n}\n\n.panel:focus-visible {\n box-shadow: var(--jl-shadow-lg), var(--jl-focus-ring);\n}\n\nheader {\n margin-bottom: 10px;\n}\n\nh2 {\n font-size: var(--jl-font-md);\n font-weight: 700;\n line-height: 1.4;\n}\n";
|
|
3
|
+
export { jl_popover_default as default };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { Placement } from '../types/floating.types';
|
|
3
|
+
/**
|
|
4
|
+
* A panel of content that opens by its trigger (`slot="trigger"`) on a click, over the page: a small form, a few
|
|
5
|
+
* settings, details. A click outside or Escape closes it; the focus moves into it when it opens and back to the
|
|
6
|
+
* trigger when Escape closes it.
|
|
7
|
+
*/
|
|
8
|
+
export declare class JlPopover extends JlElement {
|
|
9
|
+
static readonly selector = "jl-popover";
|
|
10
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
11
|
+
open: undefined;
|
|
12
|
+
close: undefined;
|
|
13
|
+
}>>;
|
|
14
|
+
/** Where the panel goes; it flips to the other side when there is no room. */
|
|
15
|
+
readonly placement: Readonly<Readonly<{
|
|
16
|
+
get(): Placement;
|
|
17
|
+
set(setter: ((prevState: Placement) => Placement) | Placement): void;
|
|
18
|
+
}>>;
|
|
19
|
+
readonly open: Readonly<Readonly<{
|
|
20
|
+
get(): boolean;
|
|
21
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
22
|
+
}>>;
|
|
23
|
+
/** A title over the content; it also names the panel for screen readers. */
|
|
24
|
+
readonly heading: Readonly<Readonly<{
|
|
25
|
+
get(): string;
|
|
26
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
27
|
+
}>>;
|
|
28
|
+
private readonly _state;
|
|
29
|
+
private readonly _refs;
|
|
30
|
+
show(): void;
|
|
31
|
+
hide(): void;
|
|
32
|
+
toggle(): void;
|
|
33
|
+
templateHtml(): Node;
|
|
34
|
+
templateCss(): string;
|
|
35
|
+
onConnect(): void;
|
|
36
|
+
onDisconnect(): void;
|
|
37
|
+
private triggerElement;
|
|
38
|
+
private isOpen;
|
|
39
|
+
/** Whether the focus is in the panel or its content, rather than on the trigger or elsewhere. */
|
|
40
|
+
private focusIsInside;
|
|
41
|
+
private renderOpen;
|
|
42
|
+
/** The panel opened or closed, by a click, by Escape or by code. */
|
|
43
|
+
private onToggle;
|
|
44
|
+
/** Keeps the panel by the trigger while it is open. */
|
|
45
|
+
private follow;
|
|
46
|
+
private focusContent;
|
|
47
|
+
private renderTrigger;
|
|
48
|
+
private renderHeading;
|
|
49
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { BASE_STYLES, FLOATING_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { PLACEMENTS, followAnchor } from "../lib/floating.js";
|
|
5
|
+
import { firstFocusable, setTriggerAria } from "../lib/trigger.js";
|
|
6
|
+
import jl_popover_default from "./jl-popover.css_raw.js";
|
|
7
|
+
import { css } from "@jadis/core";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
9
|
+
// src/molecules/jl-popover.tsx
|
|
10
|
+
var CLOSED = "closed";
|
|
11
|
+
/**
|
|
12
|
+
* A panel of content that opens by its trigger (`slot="trigger"`) on a click, over the page: a small form, a few
|
|
13
|
+
* settings, details. A click outside or Escape closes it; the focus moves into it when it opens and back to the
|
|
14
|
+
* trigger when Escape closes it.
|
|
15
|
+
*/
|
|
16
|
+
var JlPopover = class extends JlElement {
|
|
17
|
+
static selector = "jl-popover";
|
|
18
|
+
events = this.useEvents();
|
|
19
|
+
/** Where the panel goes; it flips to the other side when there is no room. */
|
|
20
|
+
placement = this.useChange("bottom-start", () => this.follow(), {
|
|
21
|
+
attribute: "placement",
|
|
22
|
+
parse: oneOf(PLACEMENTS, "bottom-start")
|
|
23
|
+
});
|
|
24
|
+
open = this.useChange(false, (value) => this.renderOpen(value), { attribute: "open" });
|
|
25
|
+
/** A title over the content; it also names the panel for screen readers. */
|
|
26
|
+
heading = this.useChange("", () => this.renderHeading(), { attribute: "heading" });
|
|
27
|
+
_state = {
|
|
28
|
+
following: new AbortController(),
|
|
29
|
+
openAtPointerDown: false
|
|
30
|
+
};
|
|
31
|
+
_refs = this.useRefs((ref) => ({
|
|
32
|
+
content: ref("slot:not([name])"),
|
|
33
|
+
header: ref("header"),
|
|
34
|
+
heading: ref("h2"),
|
|
35
|
+
panel: ref(".panel"),
|
|
36
|
+
trigger: ref("slot[name=\"trigger\"]")
|
|
37
|
+
}));
|
|
38
|
+
show() {
|
|
39
|
+
this.open.set(true);
|
|
40
|
+
}
|
|
41
|
+
hide() {
|
|
42
|
+
this.open.set(false);
|
|
43
|
+
}
|
|
44
|
+
toggle() {
|
|
45
|
+
this.open.set((open) => !open);
|
|
46
|
+
}
|
|
47
|
+
templateHtml() {
|
|
48
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("slot", { name: "trigger" }), /* @__PURE__ */ jsxs("div", {
|
|
49
|
+
class: "floating panel",
|
|
50
|
+
popover: "auto",
|
|
51
|
+
role: "dialog",
|
|
52
|
+
tabIndex: -1,
|
|
53
|
+
attrs: { part: "base" },
|
|
54
|
+
children: [/* @__PURE__ */ jsx("header", {
|
|
55
|
+
hidden: true,
|
|
56
|
+
children: /* @__PURE__ */ jsx("h2", { id: "heading" })
|
|
57
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
58
|
+
class: "body",
|
|
59
|
+
children: /* @__PURE__ */ jsx("slot", {})
|
|
60
|
+
})]
|
|
61
|
+
})] });
|
|
62
|
+
}
|
|
63
|
+
templateCss() {
|
|
64
|
+
return css`${BASE_STYLES}${FLOATING_STYLES}${jl_popover_default}`;
|
|
65
|
+
}
|
|
66
|
+
onConnect() {
|
|
67
|
+
const panel = this._refs.panel;
|
|
68
|
+
const trigger = this._refs.trigger;
|
|
69
|
+
this.renderTrigger();
|
|
70
|
+
this.on(trigger, "slotchange", () => this.renderTrigger());
|
|
71
|
+
this.on(trigger, "pointerdown", () => {
|
|
72
|
+
this._state.openAtPointerDown = this.isOpen();
|
|
73
|
+
});
|
|
74
|
+
this.on(trigger, "click", () => {
|
|
75
|
+
const closedByPointer = this._state.openAtPointerDown;
|
|
76
|
+
this._state.openAtPointerDown = false;
|
|
77
|
+
if (!closedByPointer) this.toggle();
|
|
78
|
+
});
|
|
79
|
+
this.on(panel, "beforetoggle", (event) => {
|
|
80
|
+
if (event.newState === CLOSED && this.focusIsInside()) this.triggerElement()?.focus();
|
|
81
|
+
});
|
|
82
|
+
this.on(panel, "toggle", (event) => this.onToggle(event.newState !== CLOSED));
|
|
83
|
+
}
|
|
84
|
+
onDisconnect() {
|
|
85
|
+
this._state.following.abort();
|
|
86
|
+
}
|
|
87
|
+
triggerElement() {
|
|
88
|
+
const [element] = this._refs.trigger.assignedElements({ flatten: true });
|
|
89
|
+
return element instanceof HTMLElement ? element : null;
|
|
90
|
+
}
|
|
91
|
+
isOpen() {
|
|
92
|
+
return this._refs.panel.matches(":popover-open");
|
|
93
|
+
}
|
|
94
|
+
/** Whether the focus is in the panel or its content, rather than on the trigger or elsewhere. */
|
|
95
|
+
focusIsInside() {
|
|
96
|
+
const active = this.getRootNode().activeElement;
|
|
97
|
+
const trigger = this.triggerElement();
|
|
98
|
+
if (active === null || trigger?.contains(active) === true) return false;
|
|
99
|
+
return active === this || this.contains(active);
|
|
100
|
+
}
|
|
101
|
+
renderOpen(open) {
|
|
102
|
+
this.reflect("open", open);
|
|
103
|
+
if (!this.isConnected || open === this.isOpen()) return;
|
|
104
|
+
if (!open) {
|
|
105
|
+
this._refs.panel.hidePopover();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this._refs.panel.showPopover();
|
|
109
|
+
this.follow();
|
|
110
|
+
this.focusContent();
|
|
111
|
+
}
|
|
112
|
+
/** The panel opened or closed, by a click, by Escape or by code. */
|
|
113
|
+
onToggle(open) {
|
|
114
|
+
this.renderTrigger();
|
|
115
|
+
if (open !== this.open.get()) this.open.set(open);
|
|
116
|
+
if (!open) this._state.following.abort();
|
|
117
|
+
this.events.emit(open ? "open" : "close");
|
|
118
|
+
}
|
|
119
|
+
/** Keeps the panel by the trigger while it is open. */
|
|
120
|
+
follow() {
|
|
121
|
+
this._state.following.abort();
|
|
122
|
+
const trigger = this.triggerElement();
|
|
123
|
+
if (!this.isOpen() || trigger === null) return;
|
|
124
|
+
this._state.following = new AbortController();
|
|
125
|
+
followAnchor(trigger, this._refs.panel, this.placement.get(), this._state.following.signal);
|
|
126
|
+
}
|
|
127
|
+
focusContent() {
|
|
128
|
+
(firstFocusable(this._refs.content.assignedElements({ flatten: true })) ?? this._refs.panel).focus();
|
|
129
|
+
}
|
|
130
|
+
renderTrigger() {
|
|
131
|
+
const trigger = this.triggerElement();
|
|
132
|
+
if (trigger === null) return;
|
|
133
|
+
setTriggerAria(trigger, "aria-haspopup", "dialog");
|
|
134
|
+
setTriggerAria(trigger, "aria-expanded", String(this.isOpen()));
|
|
135
|
+
this.renderHeading();
|
|
136
|
+
}
|
|
137
|
+
renderHeading() {
|
|
138
|
+
const heading = this.heading.get();
|
|
139
|
+
const panel = this._refs.panel;
|
|
140
|
+
this._refs.heading.textContent = heading;
|
|
141
|
+
this._refs.header.hidden = heading === "";
|
|
142
|
+
if (heading !== "") {
|
|
143
|
+
panel.setAttribute("aria-labelledby", "heading");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
panel.removeAttribute("aria-labelledby");
|
|
147
|
+
const trigger = this.triggerElement();
|
|
148
|
+
panel.ariaLabelledByElements = trigger === null ? null : [trigger];
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
JlPopover.register();
|
|
152
|
+
export { JlPopover };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-radio-group.css?raw
|
|
2
|
+
var jl_radio_group_default = ":host {\n display: block;\n --dot: 18px;\n}\n\n.group {\n display: grid;\n gap: 10px;\n}\n\n.legend:not(.sr-only) {\n color: var(--jl-text);\n font-size: var(--jl-font-sm);\n font-weight: 600;\n line-height: 20px;\n}\n\n.options {\n display: grid;\n gap: 10px;\n}\n\n:host([orientation=\"horizontal\"]) .options {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 24px;\n}\n\n.option {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n line-height: 20px;\n cursor: pointer;\n outline: none;\n}\n\n.option[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* The focus ring goes on the dot, where the eye looks for the choice. */\n.option:focus-visible {\n box-shadow: none;\n}\n\n.dot {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--dot);\n height: var(--dot);\n margin-top: 1px;\n border: 1px solid var(--jl-line-strong);\n border-radius: 50%;\n background: var(--jl-surface);\n transition:\n border-color var(--jl-fast) var(--jl-ease),\n box-shadow var(--jl-fast) var(--jl-ease);\n}\n\n.dot::after {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--jl-accent);\n content: \"\";\n transform: scale(0);\n transition: transform var(--jl-fast) var(--jl-ease);\n}\n\n.option[aria-checked=\"true\"] .dot {\n border-color: var(--jl-accent);\n}\n\n.option[aria-checked=\"true\"] .dot::after {\n transform: scale(1);\n}\n\n.option:focus-visible .dot {\n box-shadow: var(--jl-focus-ring);\n}\n\n.option:not([aria-disabled=\"true\"], [aria-checked=\"true\"]):hover .dot {\n border-color: color-mix(in srgb, var(--jl-line-strong) 60%, var(--jl-text) 18%);\n}\n\n.text {\n display: grid;\n gap: 2px;\n min-width: 0;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n line-height: 18px;\n}\n\n/* Cards: each option is a bordered box; the chosen one takes the accent. */\n:host([variant=\"cards\"]) .options {\n gap: 8px;\n}\n\n:host([variant=\"cards\"][orientation=\"horizontal\"]) .options {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));\n}\n\n:host([variant=\"cards\"]) .option {\n padding: 12px 14px;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius);\n background: var(--jl-surface);\n transition:\n border-color var(--jl-fast) var(--jl-ease),\n background var(--jl-fast) var(--jl-ease),\n box-shadow var(--jl-fast) var(--jl-ease);\n}\n\n:host([variant=\"cards\"]) .option:not([aria-disabled=\"true\"]):hover {\n border-color: var(--jl-line-strong);\n}\n\n:host([variant=\"cards\"]) .option[aria-checked=\"true\"] {\n border-color: var(--jl-accent);\n background: var(--jl-accent-soft);\n}\n\n:host([variant=\"cards\"]) .option:focus-visible {\n box-shadow: var(--jl-focus-ring);\n}\n\n:host([variant=\"cards\"]) .option:focus-visible .dot {\n box-shadow: none;\n}\n\n:host([variant=\"cards\"]) .label {\n font-weight: 600;\n}\n";
|
|
3
|
+
export { jl_radio_group_default as default };
|