@syware/jlib 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +116 -2
- package/dist/all.d.ts +1 -0
- package/dist/all.js +75 -0
- package/dist/atoms/jl-avatar.css_raw.js +3 -0
- package/dist/atoms/jl-avatar.d.ts +52 -0
- package/dist/atoms/jl-avatar.js +167 -0
- package/dist/atoms/jl-badge.css_raw.js +3 -0
- package/dist/atoms/jl-badge.d.ts +32 -0
- package/dist/atoms/jl-badge.js +60 -0
- package/dist/atoms/jl-button.css_raw.js +3 -0
- package/dist/atoms/jl-button.d.ts +70 -0
- package/dist/atoms/jl-button.js +146 -0
- package/dist/atoms/jl-checkbox.css_raw.js +3 -0
- package/dist/atoms/jl-checkbox.d.ts +61 -0
- package/dist/atoms/jl-checkbox.js +149 -0
- package/dist/atoms/jl-chip.css_raw.js +3 -0
- package/dist/atoms/jl-chip.d.ts +59 -0
- package/dist/atoms/jl-chip.js +164 -0
- package/dist/atoms/jl-divider.css_raw.js +3 -0
- package/dist/atoms/jl-divider.d.ts +26 -0
- package/dist/atoms/jl-divider.js +59 -0
- package/dist/atoms/jl-icon.css_raw.js +3 -0
- package/dist/atoms/jl-icon.d.ts +25 -0
- package/dist/atoms/jl-icon.js +41 -0
- package/dist/atoms/jl-input.css_raw.js +3 -0
- package/dist/atoms/jl-input.d.ts +112 -0
- package/dist/atoms/jl-input.js +202 -0
- package/dist/atoms/jl-kbd.css_raw.js +3 -0
- package/dist/atoms/jl-kbd.d.ts +17 -0
- package/dist/atoms/jl-kbd.js +62 -0
- package/dist/atoms/jl-link.css_raw.js +3 -0
- package/dist/atoms/jl-link.d.ts +37 -0
- package/dist/atoms/jl-link.js +86 -0
- package/dist/atoms/jl-progress-ring.css_raw.js +3 -0
- package/dist/atoms/jl-progress-ring.d.ts +49 -0
- package/dist/atoms/jl-progress-ring.js +132 -0
- package/dist/atoms/jl-progress.css_raw.js +3 -0
- package/dist/atoms/jl-progress.d.ts +43 -0
- package/dist/atoms/jl-progress.js +109 -0
- package/dist/atoms/jl-rating.css_raw.js +3 -0
- package/dist/atoms/jl-rating.d.ts +73 -0
- package/dist/atoms/jl-rating.js +184 -0
- package/dist/atoms/jl-select.css_raw.js +3 -0
- package/dist/atoms/jl-select.d.ts +70 -0
- package/dist/atoms/jl-select.js +146 -0
- package/dist/atoms/jl-skeleton.css_raw.js +3 -0
- package/dist/atoms/jl-skeleton.d.ts +34 -0
- package/dist/atoms/jl-skeleton.js +63 -0
- package/dist/atoms/jl-slider.css_raw.js +3 -0
- package/dist/atoms/jl-slider.d.ts +65 -0
- package/dist/atoms/jl-slider.js +143 -0
- package/dist/atoms/jl-spinner.css_raw.js +3 -0
- package/dist/atoms/jl-spinner.d.ts +16 -0
- package/dist/atoms/jl-spinner.js +26 -0
- package/dist/atoms/jl-switch.css_raw.js +3 -0
- package/dist/atoms/jl-switch.d.ts +57 -0
- package/dist/atoms/jl-switch.js +125 -0
- package/dist/atoms/jl-textarea.css_raw.js +3 -0
- package/dist/atoms/jl-textarea.d.ts +81 -0
- package/dist/atoms/jl-textarea.js +148 -0
- package/dist/base/jl-element.d.ts +16 -0
- package/dist/base/jl-element.js +35 -0
- package/dist/base/jl-form-control.d.ts +29 -0
- package/dist/base/jl-form-control.js +44 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +72 -0
- package/dist/lib/arrow.d.ts +6 -0
- package/dist/lib/arrow.js +15 -0
- package/dist/lib/attributes.d.ts +9 -0
- package/dist/lib/attributes.js +22 -0
- package/dist/lib/avatar.d.ts +11 -0
- package/dist/lib/avatar.js +37 -0
- package/dist/lib/calendar.d.ts +48 -0
- package/dist/lib/calendar.js +166 -0
- package/dist/lib/css-length.d.ts +2 -0
- package/dist/lib/css-length.js +8 -0
- package/dist/lib/define.d.ts +9 -0
- package/dist/lib/define.js +9 -0
- package/dist/lib/dom.d.ts +4 -0
- package/dist/lib/dom.js +16 -0
- package/dist/lib/files.d.ts +18 -0
- package/dist/lib/files.js +89 -0
- package/dist/lib/floating.d.ts +13 -0
- package/dist/lib/floating.js +127 -0
- package/dist/lib/fuzzy.d.ts +26 -0
- package/dist/lib/fuzzy.js +149 -0
- package/dist/lib/highlight.d.ts +5 -0
- package/dist/lib/highlight.js +267 -0
- package/dist/lib/hotkey.d.ts +9 -0
- package/dist/lib/hotkey.js +101 -0
- package/dist/lib/icon-set.d.ts +95 -0
- package/dist/lib/icon-set.js +97 -0
- package/dist/lib/icons.d.ts +12 -0
- package/dist/lib/icons.js +39 -0
- package/dist/lib/indicator.d.ts +6 -0
- package/dist/lib/indicator.js +18 -0
- package/dist/lib/keep-in-view.d.ts +5 -0
- package/dist/lib/keep-in-view.js +15 -0
- package/dist/lib/key-shortcut.d.ts +20 -0
- package/dist/lib/key-shortcut.js +100 -0
- package/dist/lib/keycaps.d.ts +8 -0
- package/dist/lib/keycaps.js +162 -0
- package/dist/lib/locale.d.ts +4 -0
- package/dist/lib/locale.js +13 -0
- package/dist/lib/nav-tree.d.ts +9 -0
- package/dist/lib/nav-tree.js +30 -0
- package/dist/lib/numeric.d.ts +20 -0
- package/dist/lib/numeric.js +50 -0
- package/dist/lib/option-filter.d.ts +29 -0
- package/dist/lib/option-filter.js +108 -0
- package/dist/lib/option-groups.d.ts +6 -0
- package/dist/lib/option-groups.js +26 -0
- package/dist/lib/options.d.ts +5 -0
- package/dist/lib/options.js +24 -0
- package/dist/lib/pagination.d.ts +9 -0
- package/dist/lib/pagination.js +52 -0
- package/dist/lib/palette.d.ts +8 -0
- package/dist/lib/palette.js +46 -0
- package/dist/lib/progress.d.ts +20 -0
- package/dist/lib/progress.js +33 -0
- package/dist/lib/rating.d.ts +7 -0
- package/dist/lib/rating.js +26 -0
- package/dist/lib/roving-index.d.ts +10 -0
- package/dist/lib/roving-index.js +32 -0
- package/dist/lib/roving.d.ts +11 -0
- package/dist/lib/roving.js +21 -0
- package/dist/lib/slots.d.ts +2 -0
- package/dist/lib/slots.js +6 -0
- package/dist/lib/sparkline.d.ts +11 -0
- package/dist/lib/sparkline.js +42 -0
- package/dist/lib/split.d.ts +12 -0
- package/dist/lib/split.js +37 -0
- package/dist/lib/surface.d.ts +7 -0
- package/dist/lib/surface.js +28 -0
- package/dist/lib/table.d.ts +30 -0
- package/dist/lib/table.js +110 -0
- package/dist/lib/tags.d.ts +14 -0
- package/dist/lib/tags.js +29 -0
- package/dist/lib/text.d.ts +5 -0
- package/dist/lib/text.js +13 -0
- package/dist/lib/tokens.d.ts +5 -0
- package/dist/lib/tokens.js +15 -0
- package/dist/lib/tree.d.ts +16 -0
- package/dist/lib/tree.js +89 -0
- package/dist/lib/trend.d.ts +3 -0
- package/dist/lib/trend.js +15 -0
- package/dist/lib/trigger.d.ts +11 -0
- package/dist/lib/trigger.js +34 -0
- package/dist/molecules/jl-accordion-item.css_raw.js +3 -0
- package/dist/molecules/jl-accordion-item.d.ts +34 -0
- package/dist/molecules/jl-accordion-item.js +82 -0
- package/dist/molecules/jl-accordion.css_raw.js +3 -0
- package/dist/molecules/jl-accordion.d.ts +27 -0
- package/dist/molecules/jl-accordion.js +61 -0
- package/dist/molecules/jl-alert.css_raw.js +3 -0
- package/dist/molecules/jl-alert.d.ts +38 -0
- package/dist/molecules/jl-alert.js +131 -0
- package/dist/molecules/jl-avatar-group.css_raw.js +3 -0
- package/dist/molecules/jl-avatar-group.d.ts +28 -0
- package/dist/molecules/jl-avatar-group.js +69 -0
- package/dist/molecules/jl-breadcrumb.css_raw.js +3 -0
- package/dist/molecules/jl-breadcrumb.d.ts +35 -0
- package/dist/molecules/jl-breadcrumb.js +126 -0
- package/dist/molecules/jl-calendar.css_raw.js +3 -0
- package/dist/molecules/jl-calendar.d.ts +101 -0
- package/dist/molecules/jl-calendar.js +314 -0
- package/dist/molecules/jl-card.css_raw.js +3 -0
- package/dist/molecules/jl-card.d.ts +46 -0
- package/dist/molecules/jl-card.js +172 -0
- package/dist/molecules/jl-code-block.css_raw.js +3 -0
- package/dist/molecules/jl-code-block.d.ts +44 -0
- package/dist/molecules/jl-code-block.js +106 -0
- package/dist/molecules/jl-combobox.css_raw.js +3 -0
- package/dist/molecules/jl-combobox.d.ts +120 -0
- package/dist/molecules/jl-combobox.js +450 -0
- package/dist/molecules/jl-copy-button.css_raw.js +3 -0
- package/dist/molecules/jl-copy-button.d.ts +31 -0
- package/dist/molecules/jl-copy-button.js +69 -0
- package/dist/molecules/jl-date-picker.css_raw.js +3 -0
- package/dist/molecules/jl-date-picker.d.ts +109 -0
- package/dist/molecules/jl-date-picker.js +322 -0
- package/dist/molecules/jl-empty.css_raw.js +3 -0
- package/dist/molecules/jl-empty.d.ts +32 -0
- package/dist/molecules/jl-empty.js +94 -0
- package/dist/molecules/jl-field.css_raw.js +3 -0
- package/dist/molecules/jl-field.d.ts +49 -0
- package/dist/molecules/jl-field.js +147 -0
- package/dist/molecules/jl-file-drop.css_raw.js +3 -0
- package/dist/molecules/jl-file-drop.d.ts +93 -0
- package/dist/molecules/jl-file-drop.js +366 -0
- package/dist/molecules/jl-list-item.css_raw.js +3 -0
- package/dist/molecules/jl-list-item.d.ts +56 -0
- package/dist/molecules/jl-list-item.js +176 -0
- package/dist/molecules/jl-menu.css_raw.js +3 -0
- package/dist/molecules/jl-menu.d.ts +62 -0
- package/dist/molecules/jl-menu.js +288 -0
- package/dist/molecules/jl-number-input.css_raw.js +3 -0
- package/dist/molecules/jl-number-input.d.ts +81 -0
- package/dist/molecules/jl-number-input.js +220 -0
- package/dist/molecules/jl-pagination.css_raw.js +3 -0
- package/dist/molecules/jl-pagination.d.ts +59 -0
- package/dist/molecules/jl-pagination.js +172 -0
- package/dist/molecules/jl-popover.css_raw.js +3 -0
- package/dist/molecules/jl-popover.d.ts +49 -0
- package/dist/molecules/jl-popover.js +152 -0
- package/dist/molecules/jl-radio-group.css_raw.js +3 -0
- package/dist/molecules/jl-radio-group.d.ts +71 -0
- package/dist/molecules/jl-radio-group.js +202 -0
- package/dist/molecules/jl-search.css_raw.js +3 -0
- package/dist/molecules/jl-search.d.ts +71 -0
- package/dist/molecules/jl-search.js +210 -0
- package/dist/molecules/jl-segmented.css_raw.js +3 -0
- package/dist/molecules/jl-segmented.d.ts +65 -0
- package/dist/molecules/jl-segmented.js +187 -0
- package/dist/molecules/jl-stat.css_raw.js +3 -0
- package/dist/molecules/jl-stat.d.ts +55 -0
- package/dist/molecules/jl-stat.js +169 -0
- package/dist/molecules/jl-steps.css_raw.js +3 -0
- package/dist/molecules/jl-steps.d.ts +44 -0
- package/dist/molecules/jl-steps.js +118 -0
- package/dist/molecules/jl-tab-panel.css_raw.js +3 -0
- package/dist/molecules/jl-tab-panel.d.ts +35 -0
- package/dist/molecules/jl-tab-panel.js +35 -0
- package/dist/molecules/jl-tabs.css_raw.js +3 -0
- package/dist/molecules/jl-tabs.d.ts +51 -0
- package/dist/molecules/jl-tabs.js +198 -0
- package/dist/molecules/jl-tag-input.css_raw.js +3 -0
- package/dist/molecules/jl-tag-input.d.ts +103 -0
- package/dist/molecules/jl-tag-input.js +429 -0
- package/dist/molecules/jl-timeline.css_raw.js +3 -0
- package/dist/molecules/jl-timeline.d.ts +28 -0
- package/dist/molecules/jl-timeline.js +81 -0
- package/dist/molecules/jl-toast.css_raw.js +3 -0
- package/dist/molecules/jl-toast.d.ts +75 -0
- package/dist/molecules/jl-toast.js +274 -0
- package/dist/molecules/jl-tooltip.css_raw.js +3 -0
- package/dist/molecules/jl-tooltip.d.ts +44 -0
- package/dist/molecules/jl-tooltip.js +160 -0
- package/dist/organisms/jl-command-palette.css_raw.js +3 -0
- package/dist/organisms/jl-command-palette.d.ts +60 -0
- package/dist/organisms/jl-command-palette.js +285 -0
- package/dist/organisms/jl-confirm.css_raw.js +3 -0
- package/dist/organisms/jl-confirm.d.ts +56 -0
- package/dist/organisms/jl-confirm.js +132 -0
- package/dist/organisms/jl-dialog.css_raw.js +3 -0
- package/dist/organisms/jl-dialog.d.ts +43 -0
- package/dist/organisms/jl-dialog.js +120 -0
- package/dist/organisms/jl-drawer.css_raw.js +3 -0
- package/dist/organisms/jl-drawer.d.ts +50 -0
- package/dist/organisms/jl-drawer.js +141 -0
- package/dist/organisms/jl-navbar.css_raw.js +3 -0
- package/dist/organisms/jl-navbar.d.ts +32 -0
- package/dist/organisms/jl-navbar.js +121 -0
- package/dist/organisms/jl-page-header.css_raw.js +3 -0
- package/dist/organisms/jl-page-header.d.ts +26 -0
- package/dist/organisms/jl-page-header.js +85 -0
- package/dist/organisms/jl-side-nav.css_raw.js +3 -0
- package/dist/organisms/jl-side-nav.d.ts +60 -0
- package/dist/organisms/jl-side-nav.js +269 -0
- package/dist/organisms/jl-table.css_raw.js +3 -0
- package/dist/organisms/jl-table.d.ts +109 -0
- package/dist/organisms/jl-table.js +318 -0
- package/dist/organisms/jl-toaster.css_raw.js +3 -0
- package/dist/organisms/jl-toaster.d.ts +48 -0
- package/dist/organisms/jl-toaster.js +142 -0
- package/dist/organisms/jl-tree.css_raw.js +3 -0
- package/dist/organisms/jl-tree.d.ts +59 -0
- package/dist/organisms/jl-tree.js +198 -0
- package/dist/styles/base.css_raw.js +3 -0
- package/dist/styles/control.css_raw.js +3 -0
- package/dist/styles/floating.css_raw.js +3 -0
- package/dist/styles/shared.d.ts +8 -0
- package/dist/styles/shared.js +14 -0
- package/dist/styles/tokens.css_raw.js +3 -0
- package/dist/templates/jl-app-shell.css_raw.js +3 -0
- package/dist/templates/jl-app-shell.d.ts +37 -0
- package/dist/templates/jl-app-shell.js +150 -0
- package/dist/templates/jl-split-view.css_raw.js +3 -0
- package/dist/templates/jl-split-view.d.ts +49 -0
- package/dist/templates/jl-split-view.js +149 -0
- package/dist/tokens.css +188 -0
- package/dist/types/calendar.types.d.ts +11 -0
- package/dist/types/files.types.d.ts +23 -0
- package/dist/types/floating.types.d.ts +13 -0
- package/dist/types/fuzzy.types.d.ts +10 -0
- package/dist/types/highlight.types.d.ts +6 -0
- package/dist/types/hotkey.types.d.ts +19 -0
- package/dist/types/jl-accordion.types.d.ts +2 -0
- package/dist/types/jl-avatar.types.d.ts +10 -0
- package/dist/types/jl-breadcrumb.types.d.ts +6 -0
- package/dist/types/jl-button.types.d.ts +3 -0
- package/dist/types/jl-card.types.d.ts +2 -0
- package/dist/types/jl-chip.types.d.ts +1 -0
- package/dist/types/jl-combobox.types.d.ts +8 -0
- package/dist/types/jl-command-palette.types.d.ts +22 -0
- package/dist/types/jl-confirm.types.d.ts +10 -0
- package/dist/types/jl-dialog.types.d.ts +1 -0
- package/dist/types/jl-divider.types.d.ts +2 -0
- package/dist/types/jl-drawer.types.d.ts +4 -0
- package/dist/types/jl-empty.types.d.ts +2 -0
- package/dist/types/jl-field.types.d.ts +2 -0
- package/dist/types/jl-input.types.d.ts +1 -0
- package/dist/types/jl-link.types.d.ts +2 -0
- package/dist/types/jl-menu.types.d.ts +22 -0
- package/dist/types/jl-navbar.types.d.ts +6 -0
- package/dist/types/jl-progress.types.d.ts +3 -0
- package/dist/types/jl-radio-group.types.d.ts +10 -0
- package/dist/types/jl-segmented.types.d.ts +7 -0
- package/dist/types/jl-select.types.d.ts +12 -0
- package/dist/types/jl-side-nav.types.d.ts +17 -0
- package/dist/types/jl-skeleton.types.d.ts +2 -0
- package/dist/types/jl-slider.types.d.ts +5 -0
- package/dist/types/jl-split-view.types.d.ts +2 -0
- package/dist/types/jl-stat.types.d.ts +2 -0
- package/dist/types/jl-steps.types.d.ts +8 -0
- package/dist/types/jl-switch.types.d.ts +1 -0
- package/dist/types/jl-table.types.d.ts +30 -0
- package/dist/types/jl-tabs.types.d.ts +2 -0
- package/dist/types/jl-timeline.types.d.ts +10 -0
- package/dist/types/jl-toast.types.d.ts +2 -0
- package/dist/types/jl-toaster.types.d.ts +23 -0
- package/dist/types/jl-tree.types.d.ts +26 -0
- package/dist/types/key-shortcut.types.d.ts +9 -0
- package/dist/types/keycaps.types.d.ts +7 -0
- package/dist/types/option-filter.types.d.ts +10 -0
- package/dist/types/options.types.d.ts +4 -0
- package/dist/types/pagination.types.d.ts +8 -0
- package/dist/types/roving-index.types.d.ts +2 -0
- package/dist/types/sparkline.types.d.ts +11 -0
- package/package.json +96 -4
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { highlightedNodes } from "../lib/highlight.js";
|
|
5
|
+
import { dedent } from "../lib/text.js";
|
|
6
|
+
import jl_code_block_default from "./jl-code-block.css_raw.js";
|
|
7
|
+
import { JlCopyButton } from "./jl-copy-button.js";
|
|
8
|
+
import { css } from "@jadis/core";
|
|
9
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
10
|
+
// src/molecules/jl-code-block.tsx
|
|
11
|
+
var CODE_LANGUAGES = [
|
|
12
|
+
"text",
|
|
13
|
+
"html",
|
|
14
|
+
"css",
|
|
15
|
+
"ts",
|
|
16
|
+
"bash"
|
|
17
|
+
];
|
|
18
|
+
var LANGUAGE_LABELS = {
|
|
19
|
+
bash: "Shell",
|
|
20
|
+
css: "CSS",
|
|
21
|
+
html: "HTML",
|
|
22
|
+
text: "",
|
|
23
|
+
ts: "TypeScript"
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Source code, coloured, with a button to copy it. The code is the `code` property, or the element's own text
|
|
27
|
+
* (a `<template>` inside keeps HTML as text): `<jl-code-block language="ts">const a = 1;</jl-code-block>`.
|
|
28
|
+
*/
|
|
29
|
+
var JlCodeBlock = class extends JlElement {
|
|
30
|
+
static selector = "jl-code-block";
|
|
31
|
+
code = this.useChange("", () => this.render(), { attribute: "code" });
|
|
32
|
+
language = this.useChange("text", () => this.render(), {
|
|
33
|
+
attribute: "language",
|
|
34
|
+
parse: oneOf(CODE_LANGUAGES, "text")
|
|
35
|
+
});
|
|
36
|
+
/** A file name or a title over the code. */
|
|
37
|
+
heading = this.useChange("", () => this.renderHeader(), { attribute: "heading" });
|
|
38
|
+
lineNumbers = this.useChange(false, () => this.render(), { attribute: "line-numbers" });
|
|
39
|
+
hideCopy = this.useChange(false, (value) => {
|
|
40
|
+
this._refs.copy.hidden = value;
|
|
41
|
+
}, { attribute: "hide-copy" });
|
|
42
|
+
/** Wraps long lines instead of scrolling sideways. */
|
|
43
|
+
wrap = this.useChange(false, (value) => this.reflect("wrap", value), { attribute: "wrap" });
|
|
44
|
+
_refs = this.useRefs((ref) => ({
|
|
45
|
+
code: ref("code"),
|
|
46
|
+
copy: ref("jl-copy-button"),
|
|
47
|
+
gutter: ref(".gutter"),
|
|
48
|
+
header: ref("header"),
|
|
49
|
+
heading: ref(".heading"),
|
|
50
|
+
language: ref(".language")
|
|
51
|
+
}));
|
|
52
|
+
templateHtml() {
|
|
53
|
+
return /* @__PURE__ */ jsxs("figure", {
|
|
54
|
+
attrs: { part: "base" },
|
|
55
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
56
|
+
hidden: true,
|
|
57
|
+
children: [/* @__PURE__ */ jsx("span", { class: "heading" }), /* @__PURE__ */ jsx("span", { class: "language" })]
|
|
58
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
59
|
+
class: "body",
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ jsx("div", {
|
|
62
|
+
class: "gutter",
|
|
63
|
+
"aria-hidden": "true",
|
|
64
|
+
hidden: true
|
|
65
|
+
}),
|
|
66
|
+
/* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", {}) }),
|
|
67
|
+
/* @__PURE__ */ jsx(JlCopyButton, { class: "copy" })
|
|
68
|
+
]
|
|
69
|
+
})]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
templateCss() {
|
|
73
|
+
return css`${BASE_STYLES}${jl_code_block_default}`;
|
|
74
|
+
}
|
|
75
|
+
/** The code shown: the `code` input, or else the text the page wrote inside the element. */
|
|
76
|
+
source() {
|
|
77
|
+
const code = this.code.get();
|
|
78
|
+
if (code !== "") return code;
|
|
79
|
+
const template = this.querySelector("template");
|
|
80
|
+
return dedent(template === null ? this.textContent ?? "" : template.innerHTML);
|
|
81
|
+
}
|
|
82
|
+
render() {
|
|
83
|
+
const source = this.source();
|
|
84
|
+
const language = this.language.get();
|
|
85
|
+
this._refs.code.replaceChildren(...highlightedNodes(source, language));
|
|
86
|
+
this._refs.copy.value.set(source);
|
|
87
|
+
this.renderGutter(source);
|
|
88
|
+
this.renderHeader();
|
|
89
|
+
}
|
|
90
|
+
renderGutter(source) {
|
|
91
|
+
const gutter = this._refs.gutter;
|
|
92
|
+
gutter.hidden = !this.lineNumbers.get();
|
|
93
|
+
const count = source.split("\n").length;
|
|
94
|
+
gutter.textContent = Array.from({ length: count }, (_, index) => String(index + 1)).join("\n");
|
|
95
|
+
}
|
|
96
|
+
renderHeader() {
|
|
97
|
+
const heading = this.heading.get();
|
|
98
|
+
const label = LANGUAGE_LABELS[this.language.get()];
|
|
99
|
+
this._refs.heading.textContent = heading;
|
|
100
|
+
this._refs.language.textContent = label;
|
|
101
|
+
this._refs.header.hidden = heading === "";
|
|
102
|
+
this.reflect("has-header", heading !== "");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
JlCodeBlock.register();
|
|
106
|
+
export { CODE_LANGUAGES, JlCodeBlock };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-combobox.css?raw
|
|
2
|
+
var jl_combobox_default = ":host {\n display: block;\n /* As a grid or flex item, the field may be narrower than its text field would like. */\n min-width: 0;\n}\n\n.toggle jl-icon {\n transition: transform var(--jl-fast) var(--jl-ease);\n}\n\n:host([open]) .toggle jl-icon {\n transform: rotate(180deg);\n}\n\n.toggle:disabled {\n cursor: not-allowed;\n}\n\n.popup {\n padding: 4px;\n}\n\n.listbox {\n position: relative;\n max-height: 264px;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n list-style: none;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 34px;\n padding: 6px 10px;\n border-radius: var(--jl-radius-xs);\n color: var(--jl-text);\n font-size: var(--jl-font-md);\n line-height: 1.35;\n cursor: pointer;\n user-select: none;\n}\n\n.option.active {\n background: var(--jl-surface-3);\n}\n\n.option-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.option.chosen .option-label {\n font-weight: 600;\n}\n\n.option[aria-disabled=\"true\"] {\n color: var(--jl-faint);\n cursor: not-allowed;\n}\n\n.text {\n display: grid;\n flex: 1;\n gap: 1px;\n min-width: 0;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n}\n\n.check {\n color: var(--jl-accent);\n visibility: hidden;\n}\n\n.option.chosen .check {\n visibility: visible;\n}\n\nmark {\n background: none;\n color: var(--jl-accent);\n font-weight: 700;\n}\n\n.empty {\n padding: 8px 10px;\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n}\n";
|
|
3
|
+
export { jl_combobox_default as default };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { JlFormControl } from '../base/jl-form-control';
|
|
2
|
+
import type { ComboboxOption } from '../types/jl-combobox.types';
|
|
3
|
+
import type { ControlSize } from '../types/options.types';
|
|
4
|
+
/**
|
|
5
|
+
* A text field with a list of options that narrows as people type; they choose with the arrows and Enter, or with
|
|
6
|
+
* the pointer. The field shows the option's label, `value` and the form hold its value. With `allow-custom`, the
|
|
7
|
+
* text people type becomes the value when no option matches it.
|
|
8
|
+
*/
|
|
9
|
+
export declare class JlCombobox extends JlFormControl {
|
|
10
|
+
static readonly selector = "jl-combobox";
|
|
11
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
12
|
+
change: string;
|
|
13
|
+
input: string;
|
|
14
|
+
}>>;
|
|
15
|
+
readonly options: Readonly<Readonly<{
|
|
16
|
+
get(): ComboboxOption[];
|
|
17
|
+
set(setter: ComboboxOption[] | ((prevState: ComboboxOption[]) => ComboboxOption[])): void;
|
|
18
|
+
}>>;
|
|
19
|
+
/** The chosen option's value (with `allow-custom`, any text). */
|
|
20
|
+
readonly value: Readonly<Readonly<{
|
|
21
|
+
get(): string;
|
|
22
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
23
|
+
}>>;
|
|
24
|
+
readonly placeholder: Readonly<Readonly<{
|
|
25
|
+
get(): string;
|
|
26
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
27
|
+
}>>;
|
|
28
|
+
/** The field's name for screen readers, when no visible label names it. */
|
|
29
|
+
readonly label: Readonly<Readonly<{
|
|
30
|
+
get(): string;
|
|
31
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
32
|
+
}>>;
|
|
33
|
+
/** Takes the typed text as the value when no option has it as its label. */
|
|
34
|
+
readonly allowCustom: Readonly<Readonly<{
|
|
35
|
+
get(): boolean;
|
|
36
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
37
|
+
}>>;
|
|
38
|
+
/** Shows a button that empties the field while it holds text. */
|
|
39
|
+
readonly clearable: Readonly<Readonly<{
|
|
40
|
+
get(): boolean;
|
|
41
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
42
|
+
}>>;
|
|
43
|
+
/** What the list says when no option matches. */
|
|
44
|
+
readonly emptyText: Readonly<Readonly<{
|
|
45
|
+
get(): string;
|
|
46
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
47
|
+
}>>;
|
|
48
|
+
readonly required: Readonly<Readonly<{
|
|
49
|
+
get(): boolean;
|
|
50
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
51
|
+
}>>;
|
|
52
|
+
readonly disabled: Readonly<Readonly<{
|
|
53
|
+
get(): boolean;
|
|
54
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
55
|
+
}>>;
|
|
56
|
+
/** Draws the field in the danger colour, for an error the page found itself. */
|
|
57
|
+
readonly invalid: Readonly<Readonly<{
|
|
58
|
+
get(): boolean;
|
|
59
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
60
|
+
}>>;
|
|
61
|
+
readonly size: Readonly<Readonly<{
|
|
62
|
+
get(): ControlSize;
|
|
63
|
+
set(setter: ((prevState: ControlSize) => ControlSize) | ControlSize): void;
|
|
64
|
+
}>>;
|
|
65
|
+
private readonly _listId;
|
|
66
|
+
private readonly _state;
|
|
67
|
+
private readonly _refs;
|
|
68
|
+
focus(options?: FocusOptions): void;
|
|
69
|
+
templateHtml(): Node;
|
|
70
|
+
templateCss(): string;
|
|
71
|
+
onConnect(): void;
|
|
72
|
+
onDisconnect(): void;
|
|
73
|
+
protected resetValue(): void;
|
|
74
|
+
protected setDisabledByForm(disabled: boolean): void;
|
|
75
|
+
private isOpen;
|
|
76
|
+
private onType;
|
|
77
|
+
private onKey;
|
|
78
|
+
/** Down opens the list or moves down it; Alt + Down opens it as it is, Alt + Up closes it. */
|
|
79
|
+
private onArrow;
|
|
80
|
+
private toggleWithAlt;
|
|
81
|
+
/** Enter chooses the active option; with free text allowed, it takes the text. It submits the form otherwise. */
|
|
82
|
+
private onEnter;
|
|
83
|
+
/** Escape closes the list, then a second one brings back the chosen option's label over unfinished typing. */
|
|
84
|
+
private onEscape;
|
|
85
|
+
/** Tab takes the option the arrows reached, and lets the focus move on. */
|
|
86
|
+
private onTab;
|
|
87
|
+
private toggleList;
|
|
88
|
+
/**
|
|
89
|
+
* Opens the list, or refreshes it, for what was typed. While typing, the first match is active, so Enter takes
|
|
90
|
+
* it; otherwise the chosen option is. With free text allowed and nothing matching, the list stays closed.
|
|
91
|
+
*/
|
|
92
|
+
private openList;
|
|
93
|
+
private showPopup;
|
|
94
|
+
private closeList;
|
|
95
|
+
private chosenIndex;
|
|
96
|
+
private activateEdgeIfNone;
|
|
97
|
+
private moveActive;
|
|
98
|
+
private optionAt;
|
|
99
|
+
private hover;
|
|
100
|
+
private choose;
|
|
101
|
+
/** Leaving the field, or Enter on typed text: an option with that label, free text, nothing, or back as it was. */
|
|
102
|
+
private commitText;
|
|
103
|
+
private optionWithLabel;
|
|
104
|
+
/** Sets the value as people chose it, and says so when it changed. */
|
|
105
|
+
private commit;
|
|
106
|
+
private clearValue;
|
|
107
|
+
/** New options: the field may now show the value's label, and an open list (or one being typed in) refreshes. */
|
|
108
|
+
private renderOptionsChange;
|
|
109
|
+
private renderOptions;
|
|
110
|
+
private optionNode;
|
|
111
|
+
/** The active option is the one the arrows reached: aria-selected and aria-activedescendant follow it. */
|
|
112
|
+
private renderActive;
|
|
113
|
+
private revealActive;
|
|
114
|
+
/** The label of the value's option (or the value itself, as free text), unless people are typing. */
|
|
115
|
+
private renderValue;
|
|
116
|
+
private displayOf;
|
|
117
|
+
private renderValidity;
|
|
118
|
+
private renderClear;
|
|
119
|
+
private renderDisabled;
|
|
120
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlFormControl } from "../base/jl-form-control.js";
|
|
3
|
+
import { jsonOf, oneOf } from "../lib/attributes.js";
|
|
4
|
+
import { followAnchor } from "../lib/floating.js";
|
|
5
|
+
import { registerIcons } from "../lib/icons.js";
|
|
6
|
+
import { CONTROL_SIZES } from "../lib/options.js";
|
|
7
|
+
import { setOrRemove, uniqueId } from "../lib/dom.js";
|
|
8
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
9
|
+
import { keepInView } from "../lib/keep-in-view.js";
|
|
10
|
+
import { edgeEnabledIndex, filterOptions, foldText, matchParts, nextEnabledIndex } from "../lib/option-filter.js";
|
|
11
|
+
import jl_combobox_default from "./jl-combobox.css_raw.js";
|
|
12
|
+
import { css } from "@jadis/core";
|
|
13
|
+
import { Check, ChevronDown, X } from "lucide";
|
|
14
|
+
import { Fragment, jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
15
|
+
// src/molecules/jl-combobox.tsx
|
|
16
|
+
registerIcons({
|
|
17
|
+
check: Check,
|
|
18
|
+
"chevron-down": ChevronDown,
|
|
19
|
+
x: X
|
|
20
|
+
});
|
|
21
|
+
var ICON_SIZE = 16;
|
|
22
|
+
var ACTION_ICON_SIZE = 14;
|
|
23
|
+
var CHECK_SIZE = 14;
|
|
24
|
+
var NONE = -1;
|
|
25
|
+
var PLACEMENT = "bottom-start";
|
|
26
|
+
var VALUE_MISSING = "Choose an option.";
|
|
27
|
+
var DEFAULT_EMPTY_TEXT = "No results";
|
|
28
|
+
/** A JSON attribute that should hold a list of options; anything else gives none. */
|
|
29
|
+
var parseOptions = (value) => {
|
|
30
|
+
const parsed = jsonOf([])(value);
|
|
31
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
32
|
+
};
|
|
33
|
+
/** A label with the searched text in a `<mark>`. */
|
|
34
|
+
var highlighted = (label, query) => matchParts(label, query).map((part) => part.matched ? /* @__PURE__ */ jsx("mark", { children: part.text }) : part.text);
|
|
35
|
+
/**
|
|
36
|
+
* A text field with a list of options that narrows as people type; they choose with the arrows and Enter, or with
|
|
37
|
+
* the pointer. The field shows the option's label, `value` and the form hold its value. With `allow-custom`, the
|
|
38
|
+
* text people type becomes the value when no option matches it.
|
|
39
|
+
*/
|
|
40
|
+
var JlCombobox = class extends JlFormControl {
|
|
41
|
+
static selector = "jl-combobox";
|
|
42
|
+
events = this.useEvents();
|
|
43
|
+
options = this.useChange([], () => this.renderOptionsChange(), {
|
|
44
|
+
attribute: "options",
|
|
45
|
+
parse: parseOptions
|
|
46
|
+
});
|
|
47
|
+
/** The chosen option's value (with `allow-custom`, any text). */
|
|
48
|
+
value = this.useChange("", () => this.renderValue(), { attribute: "value" });
|
|
49
|
+
placeholder = this.useChange("", (value) => setOrRemove(this._refs.native, "placeholder", value), { attribute: "placeholder" });
|
|
50
|
+
/** The field's name for screen readers, when no visible label names it. */
|
|
51
|
+
label = this.useChange("", (value) => {
|
|
52
|
+
setOrRemove(this._refs.native, "aria-label", value);
|
|
53
|
+
setOrRemove(this._refs.listbox, "aria-label", value);
|
|
54
|
+
}, { attribute: "label" });
|
|
55
|
+
/** Takes the typed text as the value when no option has it as its label. */
|
|
56
|
+
allowCustom = this.useChange(false, () => this.renderValue(), { attribute: "allow-custom" });
|
|
57
|
+
/** Shows a button that empties the field while it holds text. */
|
|
58
|
+
clearable = this.useChange(false, () => this.renderClear(), { attribute: "clearable" });
|
|
59
|
+
/** What the list says when no option matches. */
|
|
60
|
+
emptyText = this.useChange(DEFAULT_EMPTY_TEXT, (value) => {
|
|
61
|
+
this._refs.empty.textContent = value;
|
|
62
|
+
}, { attribute: "empty-text" });
|
|
63
|
+
required = this.useChange(false, (value) => {
|
|
64
|
+
this._refs.native.toggleAttribute("aria-required", value);
|
|
65
|
+
this.reflect("required", value);
|
|
66
|
+
this.renderValidity();
|
|
67
|
+
}, { attribute: "required" });
|
|
68
|
+
disabled = this.useChange(false, (value) => this.renderDisabled(value), { attribute: "disabled" });
|
|
69
|
+
/** Draws the field in the danger colour, for an error the page found itself. */
|
|
70
|
+
invalid = this.useChange(false, (value) => {
|
|
71
|
+
this.reflect("invalid", value);
|
|
72
|
+
this._refs.native.toggleAttribute("aria-invalid", value);
|
|
73
|
+
}, { attribute: "invalid" });
|
|
74
|
+
size = this.useChange("md", (value) => this.reflect("size", value), {
|
|
75
|
+
attribute: "size",
|
|
76
|
+
parse: oneOf(CONTROL_SIZES, "md")
|
|
77
|
+
});
|
|
78
|
+
_listId = uniqueId("jl-combobox-list");
|
|
79
|
+
_state = {
|
|
80
|
+
active: NONE,
|
|
81
|
+
matches: [],
|
|
82
|
+
opening: new AbortController(),
|
|
83
|
+
/** What people typed since the last choice; empty shows every option. */
|
|
84
|
+
query: ""
|
|
85
|
+
};
|
|
86
|
+
_refs = this.useRefs((ref) => ({
|
|
87
|
+
clear: ref(".clear"),
|
|
88
|
+
control: ref(".control"),
|
|
89
|
+
empty: ref(".empty"),
|
|
90
|
+
listbox: ref(".listbox"),
|
|
91
|
+
native: ref("input"),
|
|
92
|
+
popup: ref(".popup"),
|
|
93
|
+
toggle: ref(".toggle")
|
|
94
|
+
}));
|
|
95
|
+
focus(options) {
|
|
96
|
+
this._refs.native.focus(options);
|
|
97
|
+
}
|
|
98
|
+
templateHtml() {
|
|
99
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
100
|
+
class: "control",
|
|
101
|
+
attrs: { part: "base" },
|
|
102
|
+
children: [
|
|
103
|
+
/* @__PURE__ */ jsx("input", {
|
|
104
|
+
class: "native",
|
|
105
|
+
role: "combobox",
|
|
106
|
+
"aria-autocomplete": "list",
|
|
107
|
+
"aria-expanded": "false",
|
|
108
|
+
"aria-controls": this._listId,
|
|
109
|
+
autocomplete: "off",
|
|
110
|
+
spellcheck: false
|
|
111
|
+
}),
|
|
112
|
+
/* @__PURE__ */ jsx("button", {
|
|
113
|
+
class: "action clear",
|
|
114
|
+
type: "button",
|
|
115
|
+
"aria-label": "Clear",
|
|
116
|
+
tabIndex: -1,
|
|
117
|
+
hidden: true,
|
|
118
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
119
|
+
name: "x",
|
|
120
|
+
size: ACTION_ICON_SIZE
|
|
121
|
+
})
|
|
122
|
+
}),
|
|
123
|
+
/* @__PURE__ */ jsx("button", {
|
|
124
|
+
class: "action toggle",
|
|
125
|
+
type: "button",
|
|
126
|
+
"aria-label": "Show the options",
|
|
127
|
+
tabIndex: -1,
|
|
128
|
+
children: /* @__PURE__ */ jsx(JlIcon, {
|
|
129
|
+
name: "chevron-down",
|
|
130
|
+
size: ICON_SIZE
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
]
|
|
134
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
135
|
+
class: "floating panel popup",
|
|
136
|
+
popover: "manual",
|
|
137
|
+
attrs: { part: "list" },
|
|
138
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
139
|
+
class: "listbox",
|
|
140
|
+
role: "listbox",
|
|
141
|
+
id: this._listId
|
|
142
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
143
|
+
class: "empty",
|
|
144
|
+
hidden: true
|
|
145
|
+
})]
|
|
146
|
+
})] });
|
|
147
|
+
}
|
|
148
|
+
templateCss() {
|
|
149
|
+
return css`${BASE_STYLES}${CONTROL_STYLES}${FLOATING_STYLES}${jl_combobox_default}`;
|
|
150
|
+
}
|
|
151
|
+
onConnect() {
|
|
152
|
+
const native = this._refs.native;
|
|
153
|
+
this.on(native, "input", (event) => {
|
|
154
|
+
event.stopPropagation();
|
|
155
|
+
this.onType();
|
|
156
|
+
});
|
|
157
|
+
this.on(native, "keydown", (event) => this.onKey(event));
|
|
158
|
+
this.on(native, "blur", () => this.commitText());
|
|
159
|
+
this.on(native, "click", () => this.openList(false));
|
|
160
|
+
this.on(this._refs.toggle, "click", () => this.toggleList());
|
|
161
|
+
this.on(this._refs.clear, "click", () => this.clearValue());
|
|
162
|
+
for (const element of [
|
|
163
|
+
this._refs.popup,
|
|
164
|
+
this._refs.toggle,
|
|
165
|
+
this._refs.clear
|
|
166
|
+
]) this.on(element, "mousedown", (event) => event.preventDefault());
|
|
167
|
+
this.on(this._refs.listbox, "click", (event) => this.choose(this.optionAt(event)));
|
|
168
|
+
this.on(this._refs.listbox, "pointermove", (event) => this.hover(event));
|
|
169
|
+
}
|
|
170
|
+
onDisconnect() {
|
|
171
|
+
this._state.opening.abort();
|
|
172
|
+
}
|
|
173
|
+
resetValue() {
|
|
174
|
+
this._state.query = "";
|
|
175
|
+
this.value.set(this.getAttribute("value") ?? "");
|
|
176
|
+
}
|
|
177
|
+
setDisabledByForm(disabled) {
|
|
178
|
+
this.whenRendered(() => {
|
|
179
|
+
this._refs.native.disabled = disabled || this.disabled.get();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
isOpen() {
|
|
183
|
+
return this._refs.popup.matches(":popover-open");
|
|
184
|
+
}
|
|
185
|
+
onType() {
|
|
186
|
+
const text = this._refs.native.value;
|
|
187
|
+
this._state.query = text;
|
|
188
|
+
this.events.emit("input", text);
|
|
189
|
+
this.renderClear();
|
|
190
|
+
this.openList(true);
|
|
191
|
+
}
|
|
192
|
+
onKey(event) {
|
|
193
|
+
if ((/* @__PURE__ */ new Map([
|
|
194
|
+
["ArrowDown", () => this.onArrow(1, event.altKey)],
|
|
195
|
+
["ArrowUp", () => this.onArrow(-1, event.altKey)],
|
|
196
|
+
["Enter", () => this.onEnter()],
|
|
197
|
+
["Escape", () => this.onEscape()],
|
|
198
|
+
["Tab", () => this.onTab()]
|
|
199
|
+
])).get(event.key)?.() === true) event.preventDefault();
|
|
200
|
+
}
|
|
201
|
+
/** Down opens the list or moves down it; Alt + Down opens it as it is, Alt + Up closes it. */
|
|
202
|
+
onArrow(direction, alt) {
|
|
203
|
+
if (alt) {
|
|
204
|
+
this.toggleWithAlt(direction);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (!this.isOpen()) {
|
|
208
|
+
this.openList(false);
|
|
209
|
+
this.activateEdgeIfNone(direction);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
this.moveActive(direction);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
toggleWithAlt(direction) {
|
|
216
|
+
if (direction > 0) {
|
|
217
|
+
this.openList(false);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.closeList();
|
|
221
|
+
}
|
|
222
|
+
/** Enter chooses the active option; with free text allowed, it takes the text. It submits the form otherwise. */
|
|
223
|
+
onEnter() {
|
|
224
|
+
const active = this._state.matches[this._state.active];
|
|
225
|
+
if (this.isOpen() && active !== void 0) {
|
|
226
|
+
this.choose(active);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (this._state.query !== "") {
|
|
230
|
+
this.commitText();
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (this.isOpen()) {
|
|
234
|
+
this.closeList();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
/** Escape closes the list, then a second one brings back the chosen option's label over unfinished typing. */
|
|
240
|
+
onEscape() {
|
|
241
|
+
if (this.isOpen()) {
|
|
242
|
+
this.closeList();
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (this._state.query === "") return false;
|
|
246
|
+
this._state.query = "";
|
|
247
|
+
this.renderValue();
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
/** Tab takes the option the arrows reached, and lets the focus move on. */
|
|
251
|
+
onTab() {
|
|
252
|
+
const active = this._state.matches[this._state.active];
|
|
253
|
+
if (this.isOpen() && active !== void 0) this.choose(active);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
toggleList() {
|
|
257
|
+
if (this.isOpen()) {
|
|
258
|
+
this.closeList();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.openList(false);
|
|
262
|
+
this.focus();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Opens the list, or refreshes it, for what was typed. While typing, the first match is active, so Enter takes
|
|
266
|
+
* it; otherwise the chosen option is. With free text allowed and nothing matching, the list stays closed.
|
|
267
|
+
*/
|
|
268
|
+
openList(typing) {
|
|
269
|
+
if (this.disabled.get() || this.options.get().length === 0 && this._state.query === "") return;
|
|
270
|
+
this.renderOptions();
|
|
271
|
+
const matches = this._state.matches;
|
|
272
|
+
const autoActive = typing && this._state.query.trim() !== "" && !this.allowCustom.get();
|
|
273
|
+
this._state.active = autoActive ? edgeEnabledIndex(matches, 1) : this.chosenIndex();
|
|
274
|
+
this.renderActive();
|
|
275
|
+
if (matches.length === 0 && this.allowCustom.get()) {
|
|
276
|
+
this.closeList();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!this.isOpen()) this.showPopup();
|
|
280
|
+
this.revealActive();
|
|
281
|
+
}
|
|
282
|
+
showPopup() {
|
|
283
|
+
const popup = this._refs.popup;
|
|
284
|
+
popup.style.width = `${this._refs.control.offsetWidth}px`;
|
|
285
|
+
popup.showPopover();
|
|
286
|
+
this._state.opening.abort();
|
|
287
|
+
this._state.opening = new AbortController();
|
|
288
|
+
followAnchor(this._refs.control, popup, PLACEMENT, this._state.opening.signal);
|
|
289
|
+
this._refs.native.setAttribute("aria-expanded", "true");
|
|
290
|
+
this.reflect("open", true);
|
|
291
|
+
}
|
|
292
|
+
closeList() {
|
|
293
|
+
this._state.active = NONE;
|
|
294
|
+
this.renderActive();
|
|
295
|
+
if (!this.isOpen()) return;
|
|
296
|
+
this._refs.popup.hidePopover();
|
|
297
|
+
this._state.opening.abort();
|
|
298
|
+
this._refs.native.setAttribute("aria-expanded", "false");
|
|
299
|
+
this.reflect("open", false);
|
|
300
|
+
}
|
|
301
|
+
chosenIndex() {
|
|
302
|
+
const value = this.value.get();
|
|
303
|
+
return this._state.matches.findIndex((option) => option.value === value && option.disabled !== true);
|
|
304
|
+
}
|
|
305
|
+
activateEdgeIfNone(direction) {
|
|
306
|
+
if (this._state.active !== NONE) return;
|
|
307
|
+
this._state.active = edgeEnabledIndex(this._state.matches, direction);
|
|
308
|
+
this.renderActive();
|
|
309
|
+
this.revealActive();
|
|
310
|
+
}
|
|
311
|
+
moveActive(direction) {
|
|
312
|
+
this._state.active = nextEnabledIndex(this._state.matches, this._state.active, direction);
|
|
313
|
+
this.renderActive();
|
|
314
|
+
this.revealActive();
|
|
315
|
+
}
|
|
316
|
+
optionAt(event) {
|
|
317
|
+
const item = event.target.closest("[data-index]");
|
|
318
|
+
return item === null ? void 0 : this._state.matches[Number(item.dataset.index)];
|
|
319
|
+
}
|
|
320
|
+
hover(event) {
|
|
321
|
+
const option = this.optionAt(event);
|
|
322
|
+
const index = option === void 0 ? NONE : this._state.matches.indexOf(option);
|
|
323
|
+
if (index === this._state.active || option?.disabled === true) return;
|
|
324
|
+
this._state.active = index;
|
|
325
|
+
this.renderActive();
|
|
326
|
+
}
|
|
327
|
+
choose(option) {
|
|
328
|
+
if (option === void 0 || option.disabled === true) return;
|
|
329
|
+
this._state.query = "";
|
|
330
|
+
this.closeList();
|
|
331
|
+
this.commit(option.value);
|
|
332
|
+
}
|
|
333
|
+
/** Leaving the field, or Enter on typed text: an option with that label, free text, nothing, or back as it was. */
|
|
334
|
+
commitText() {
|
|
335
|
+
this.closeList();
|
|
336
|
+
const text = this._refs.native.value.trim();
|
|
337
|
+
const exact = this.optionWithLabel(text);
|
|
338
|
+
this._state.query = "";
|
|
339
|
+
if (exact !== void 0) {
|
|
340
|
+
this.commit(exact.value);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (text === "" || this.allowCustom.get()) {
|
|
344
|
+
this.commit(text);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this.renderValue();
|
|
348
|
+
}
|
|
349
|
+
optionWithLabel(text) {
|
|
350
|
+
const folded = foldText(text);
|
|
351
|
+
return this.options.get().find((option) => option.disabled !== true && foldText(option.label) === folded);
|
|
352
|
+
}
|
|
353
|
+
/** Sets the value as people chose it, and says so when it changed. */
|
|
354
|
+
commit(value) {
|
|
355
|
+
const changed = value !== this.value.get();
|
|
356
|
+
this.value.set(value);
|
|
357
|
+
if (changed) this.events.emit("change", value);
|
|
358
|
+
}
|
|
359
|
+
clearValue() {
|
|
360
|
+
this._state.query = "";
|
|
361
|
+
this.closeList();
|
|
362
|
+
this.commit("");
|
|
363
|
+
this.focus();
|
|
364
|
+
}
|
|
365
|
+
/** New options: the field may now show the value's label, and an open list (or one being typed in) refreshes. */
|
|
366
|
+
renderOptionsChange() {
|
|
367
|
+
this.renderValue();
|
|
368
|
+
const typing = this._state.query !== "";
|
|
369
|
+
if (this.isOpen() || typing && this.matches(":focus-within")) this.openList(typing);
|
|
370
|
+
}
|
|
371
|
+
renderOptions() {
|
|
372
|
+
const matches = filterOptions(this.options.get(), this._state.query);
|
|
373
|
+
const value = this.value.get();
|
|
374
|
+
this._state.matches = matches;
|
|
375
|
+
this._refs.listbox.replaceChildren(...matches.map((option, index) => this.optionNode(option, index, value)));
|
|
376
|
+
this._refs.listbox.hidden = matches.length === 0;
|
|
377
|
+
this._refs.empty.hidden = matches.length > 0;
|
|
378
|
+
}
|
|
379
|
+
optionNode(option, index, value) {
|
|
380
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
381
|
+
class: option.value === value ? "option chosen" : "option",
|
|
382
|
+
role: "option",
|
|
383
|
+
id: `${this._listId}-${index}`,
|
|
384
|
+
"data-index": String(index),
|
|
385
|
+
"aria-selected": "false",
|
|
386
|
+
"aria-disabled": option.disabled === true ? "true" : void 0,
|
|
387
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
388
|
+
class: "text",
|
|
389
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
390
|
+
class: "option-label",
|
|
391
|
+
children: highlighted(option.label, this._state.query)
|
|
392
|
+
}), option.description === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
393
|
+
class: "description",
|
|
394
|
+
children: option.description
|
|
395
|
+
})]
|
|
396
|
+
}), /* @__PURE__ */ jsx(JlIcon, {
|
|
397
|
+
class: "check",
|
|
398
|
+
name: "check",
|
|
399
|
+
size: CHECK_SIZE
|
|
400
|
+
})]
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/** The active option is the one the arrows reached: aria-selected and aria-activedescendant follow it. */
|
|
404
|
+
renderActive() {
|
|
405
|
+
const active = this._state.active;
|
|
406
|
+
for (const item of this._refs.listbox.querySelectorAll("[data-index]")) {
|
|
407
|
+
const isActive = Number(item.dataset.index) === active;
|
|
408
|
+
item.classList.toggle("active", isActive);
|
|
409
|
+
item.setAttribute("aria-selected", String(isActive));
|
|
410
|
+
}
|
|
411
|
+
setOrRemove(this._refs.native, "aria-activedescendant", active === NONE ? "" : `${this._listId}-${active}`);
|
|
412
|
+
}
|
|
413
|
+
revealActive() {
|
|
414
|
+
const item = this._refs.listbox.children[this._state.active];
|
|
415
|
+
if (item instanceof HTMLElement) keepInView(this._refs.listbox, item);
|
|
416
|
+
}
|
|
417
|
+
/** The label of the value's option (or the value itself, as free text), unless people are typing. */
|
|
418
|
+
renderValue() {
|
|
419
|
+
const value = this.value.get();
|
|
420
|
+
if (this._state.query === "") this._refs.native.value = this.displayOf(value);
|
|
421
|
+
this.internals.setFormValue(value);
|
|
422
|
+
this.renderValidity();
|
|
423
|
+
this.renderClear();
|
|
424
|
+
}
|
|
425
|
+
displayOf(value) {
|
|
426
|
+
const option = this.options.get().find((candidate) => candidate.value === value);
|
|
427
|
+
if (option !== void 0) return option.label;
|
|
428
|
+
return this.allowCustom.get() ? value : "";
|
|
429
|
+
}
|
|
430
|
+
renderValidity() {
|
|
431
|
+
if (this.required.get() && this.value.get() === "") {
|
|
432
|
+
this.internals.setValidity({ valueMissing: true }, VALUE_MISSING, this._refs.native);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
this.internals.setValidity({});
|
|
436
|
+
}
|
|
437
|
+
renderClear() {
|
|
438
|
+
const native = this._refs.native;
|
|
439
|
+
this._refs.clear.hidden = !(this.clearable.get() && !this.disabled.get() && native.value !== "");
|
|
440
|
+
}
|
|
441
|
+
renderDisabled(disabled) {
|
|
442
|
+
this._refs.native.disabled = disabled;
|
|
443
|
+
this._refs.toggle.disabled = disabled;
|
|
444
|
+
this.reflect("disabled", disabled);
|
|
445
|
+
this.closeList();
|
|
446
|
+
this.renderClear();
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
JlCombobox.register();
|
|
450
|
+
export { JlCombobox };
|