@syware/jlib 0.3.0 → 0.5.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/dist/all.js +10 -3
- package/dist/atoms/jl-badge.d.ts +12 -1
- package/dist/atoms/jl-badge.js +15 -1
- package/dist/atoms/jl-button.css_raw.js +1 -1
- package/dist/atoms/jl-button.d.ts +19 -0
- package/dist/atoms/jl-button.js +75 -23
- package/dist/atoms/jl-chip.css_raw.js +1 -1
- package/dist/atoms/jl-input.d.ts +5 -0
- package/dist/atoms/jl-input.js +2 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -2
- package/dist/lib/attributes.d.ts +5 -0
- package/dist/lib/attributes.js +13 -1
- package/dist/lib/time.d.ts +10 -0
- package/dist/lib/time.js +64 -0
- package/dist/lib/tokens.d.ts +2 -1
- package/dist/lib/tokens.js +13 -4
- package/dist/molecules/jl-account-button.css_raw.js +3 -0
- package/dist/molecules/jl-account-button.d.ts +46 -0
- package/dist/molecules/jl-account-button.js +97 -0
- package/dist/molecules/jl-alert.d.ts +10 -3
- package/dist/molecules/jl-alert.js +23 -6
- package/dist/molecules/jl-chip-group.css_raw.js +3 -0
- package/dist/molecules/jl-chip-group.d.ts +81 -0
- package/dist/molecules/jl-chip-group.js +259 -0
- package/dist/molecules/jl-load-state.css_raw.js +3 -0
- package/dist/molecules/jl-load-state.d.ts +61 -0
- package/dist/molecules/jl-load-state.js +200 -0
- package/dist/molecules/jl-search-button.css_raw.js +3 -0
- package/dist/molecules/jl-search-button.d.ts +34 -0
- package/dist/molecules/jl-search-button.js +80 -0
- package/dist/molecules/jl-tag-input.css_raw.js +1 -1
- package/dist/organisms/jl-confirm.d.ts +7 -1
- package/dist/organisms/jl-confirm.js +22 -1
- package/dist/organisms/jl-tab-bar.css_raw.js +3 -0
- package/dist/organisms/jl-tab-bar.d.ts +43 -0
- package/dist/organisms/jl-tab-bar.js +123 -0
- package/dist/organisms/jl-toaster.d.ts +4 -1
- package/dist/organisms/jl-toaster.js +9 -2
- package/dist/styles/control.css_raw.js +1 -1
- package/dist/styles/tokens.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.d.ts +8 -2
- package/dist/templates/jl-app-shell.js +32 -5
- package/dist/templates/jl-page.css_raw.js +3 -0
- package/dist/templates/jl-page.d.ts +49 -0
- package/dist/templates/jl-page.js +102 -0
- package/dist/tokens.css +22 -0
- package/dist/types/jl-badge.types.d.ts +11 -0
- package/dist/types/jl-chip-group.types.d.ts +10 -0
- package/dist/types/jl-confirm.types.d.ts +15 -0
- package/dist/types/jl-load-state.types.d.ts +10 -0
- package/dist/types/jl-page.types.d.ts +2 -0
- package/dist/types/jl-tab-bar.types.d.ts +11 -0
- package/dist/types/jl-toaster.types.d.ts +1 -1
- package/package.json +1 -1
package/dist/lib/time.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/lib/time.ts
|
|
2
|
+
var SECOND_MS = 1e3;
|
|
3
|
+
var MINUTES_PER_HOUR = 60;
|
|
4
|
+
var HOURS_PER_DAY = 24;
|
|
5
|
+
var MINUTE_S = 60;
|
|
6
|
+
var HOUR_S = 3600;
|
|
7
|
+
var DAY_S = HOURS_PER_DAY * HOUR_S;
|
|
8
|
+
var NOW_UNDER_S = 45;
|
|
9
|
+
var MINUTES_UNDER_S = 2700;
|
|
10
|
+
var HOURS_UNDER_S = 22 * HOUR_S;
|
|
11
|
+
var DAYS_UNDER_S = 7 * DAY_S;
|
|
12
|
+
/** A number of one unit, as the language writes it short: "33 secs", "3 min", "2 Std.". */
|
|
13
|
+
var unitText = (value, unit, locale) => new Intl.NumberFormat(locale, {
|
|
14
|
+
maximumFractionDigits: 0,
|
|
15
|
+
style: "unit",
|
|
16
|
+
unit,
|
|
17
|
+
unitDisplay: "short"
|
|
18
|
+
}).format(value);
|
|
19
|
+
/** Two units side by side, the second left out at zero: "2 hrs 10 mins", "3 days". */
|
|
20
|
+
function pair(first, second, locale) {
|
|
21
|
+
const parts = [unitText(first[0], first[1], locale)];
|
|
22
|
+
if (second[0] > 0) parts.push(unitText(second[0], second[1], locale));
|
|
23
|
+
return new Intl.ListFormat(locale, {
|
|
24
|
+
style: "narrow",
|
|
25
|
+
type: "unit"
|
|
26
|
+
}).format(parts);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A duration in the units that read best: "< 1 sec" under a second (never "0.0 s"), then seconds, minutes, hours
|
|
30
|
+
* and minutes, days and hours.
|
|
31
|
+
*/
|
|
32
|
+
function formatDuration(milliseconds, locale) {
|
|
33
|
+
const seconds = Math.max(0, milliseconds) / SECOND_MS;
|
|
34
|
+
if (seconds < 1) return `< ${unitText(1, "second", locale)}`;
|
|
35
|
+
if (seconds < MINUTE_S) return unitText(Math.round(seconds), "second", locale);
|
|
36
|
+
if (seconds < HOUR_S) return unitText(Math.round(seconds / MINUTE_S), "minute", locale);
|
|
37
|
+
if (seconds < DAY_S) {
|
|
38
|
+
const minutes = Math.round(seconds / MINUTE_S);
|
|
39
|
+
return pair([Math.floor(minutes / MINUTES_PER_HOUR), "hour"], [minutes % MINUTES_PER_HOUR, "minute"], locale);
|
|
40
|
+
}
|
|
41
|
+
const hours = Math.round(seconds / HOUR_S);
|
|
42
|
+
return pair([Math.floor(hours / HOURS_PER_DAY), "day"], [hours % HOURS_PER_DAY, "hour"], locale);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* When something happened, from `now`: "now", "5 minutes ago", "3 hours ago", "yesterday" within a week, then the
|
|
46
|
+
* date ("12 Mar", with the year when it is not this year's). Times to come read the same way: "in 5 minutes".
|
|
47
|
+
*/
|
|
48
|
+
function formatRelativeTime(date, now, locale) {
|
|
49
|
+
const then = new Date(date);
|
|
50
|
+
const seconds = (then.getTime() - new Date(now).getTime()) / SECOND_MS;
|
|
51
|
+
const distance = Math.abs(seconds);
|
|
52
|
+
const relative = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
53
|
+
if (distance < NOW_UNDER_S) return relative.format(0, "second");
|
|
54
|
+
if (distance < MINUTES_UNDER_S) return relative.format(Math.round(seconds / MINUTE_S), "minute");
|
|
55
|
+
if (distance < HOURS_UNDER_S) return relative.format(Math.round(seconds / HOUR_S), "hour");
|
|
56
|
+
if (distance < DAYS_UNDER_S) return relative.format(Math.round(seconds / DAY_S), "day");
|
|
57
|
+
const sameYear = then.getFullYear() === new Date(now).getFullYear();
|
|
58
|
+
return new Intl.DateTimeFormat(locale, {
|
|
59
|
+
day: "numeric",
|
|
60
|
+
month: "short",
|
|
61
|
+
...sameYear ? {} : { year: "numeric" }
|
|
62
|
+
}).format(then);
|
|
63
|
+
}
|
|
64
|
+
export { formatDuration, formatRelativeTime };
|
package/dist/lib/tokens.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gives the document the design tokens, once. They live in the `jlib` cascade layer, so the page overrides any of
|
|
3
|
-
* them with a plain rule: `:root { --jl-accent: #7c3aed; }`.
|
|
3
|
+
* them with a plain rule: `:root { --jl-accent: #7c3aed; }`. Where the document takes no constructed stylesheet, a
|
|
4
|
+
* style element in its head holds them instead.
|
|
4
5
|
*/
|
|
5
6
|
export declare function adoptTokens(): void;
|
package/dist/lib/tokens.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { TOKEN_STYLES } from "../styles/shared.js";
|
|
2
2
|
// src/lib/tokens.ts
|
|
3
|
-
var adopted = {
|
|
3
|
+
var adopted = { done: false };
|
|
4
|
+
/** Whether the document takes constructed stylesheets: jsdom, which runs many apps' unit tests, does not. */
|
|
5
|
+
var takesSheets = () => typeof CSSStyleSheet !== "undefined" && typeof CSSStyleSheet.prototype.replaceSync === "function" && Array.isArray(document.adoptedStyleSheets);
|
|
4
6
|
/**
|
|
5
7
|
* Gives the document the design tokens, once. They live in the `jlib` cascade layer, so the page overrides any of
|
|
6
|
-
* them with a plain rule: `:root { --jl-accent: #7c3aed; }`.
|
|
8
|
+
* them with a plain rule: `:root { --jl-accent: #7c3aed; }`. Where the document takes no constructed stylesheet, a
|
|
9
|
+
* style element in its head holds them instead.
|
|
7
10
|
*/
|
|
8
11
|
function adoptTokens() {
|
|
9
|
-
if (adopted.
|
|
12
|
+
if (adopted.done || typeof document === "undefined") return;
|
|
13
|
+
adopted.done = true;
|
|
14
|
+
if (!takesSheets()) {
|
|
15
|
+
const style = document.createElement("style");
|
|
16
|
+
style.textContent = TOKEN_STYLES;
|
|
17
|
+
document.head.append(style);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
10
20
|
const sheet = new CSSStyleSheet();
|
|
11
21
|
sheet.replaceSync(TOKEN_STYLES);
|
|
12
22
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
13
|
-
adopted.sheet = sheet;
|
|
14
23
|
}
|
|
15
24
|
export { adoptTokens };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-account-button.css?raw
|
|
2
|
+
var jl_account_button_default = ":host {\n display: block;\n}\n\n.base {\n display: flex;\n width: 100%;\n padding: 6px 8px;\n border: 0;\n border-radius: var(--jl-radius-sm);\n background: transparent;\n color: var(--jl-text);\n text-align: left;\n text-decoration: none;\n cursor: pointer;\n transition: background var(--jl-fast) var(--jl-ease);\n}\n\n.base:hover {\n background: var(--jl-surface-2);\n}\n\n.content {\n display: flex;\n flex: 1;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n\njl-avatar {\n flex: none;\n}\n\n.text {\n display: grid;\n min-width: 0;\n line-height: 1.3;\n}\n\n.name,\n.description {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.name {\n font-size: var(--jl-font-sm);\n font-weight: 650;\n}\n\n.description {\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n}\n\n/* The avatar alone, round. */\n:host([compact]) {\n display: inline-block;\n}\n\n:host([compact]) .base {\n width: auto;\n padding: 2px;\n border-radius: var(--jl-radius-pill);\n}\n\n:host([compact]) .text {\n display: none;\n}\n";
|
|
3
|
+
export { jl_account_button_default as default };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
/**
|
|
3
|
+
* The person signed in, where the frame of an app shows them (the foot of the side nav, a phone's top bar): their
|
|
4
|
+
* avatar, their name, and a line under it such as their role. With an `href` it is a link to their account, else a
|
|
5
|
+
* button that fires `press` (to open a menu). `compact` keeps the avatar alone, the name as its tooltip.
|
|
6
|
+
*/
|
|
7
|
+
export declare class JlAccountButton extends JlElement {
|
|
8
|
+
static readonly selector = "jl-account-button";
|
|
9
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
10
|
+
press: undefined;
|
|
11
|
+
}>>;
|
|
12
|
+
readonly name: Readonly<Readonly<{
|
|
13
|
+
get(): string;
|
|
14
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
15
|
+
}>>;
|
|
16
|
+
/** The line under the name: their role, their company. */
|
|
17
|
+
readonly description: Readonly<Readonly<{
|
|
18
|
+
get(): string;
|
|
19
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
20
|
+
}>>;
|
|
21
|
+
/** Their picture; their initials without it. */
|
|
22
|
+
readonly src: Readonly<Readonly<{
|
|
23
|
+
get(): string;
|
|
24
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
25
|
+
}>>;
|
|
26
|
+
/** Their account's address: the button becomes a link. */
|
|
27
|
+
readonly href: Readonly<Readonly<{
|
|
28
|
+
get(): string;
|
|
29
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
30
|
+
}>>;
|
|
31
|
+
/** The avatar alone. */
|
|
32
|
+
readonly compact: Readonly<Readonly<{
|
|
33
|
+
get(): boolean;
|
|
34
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
35
|
+
}>>;
|
|
36
|
+
private readonly _refs;
|
|
37
|
+
focus(options?: FocusOptions): void;
|
|
38
|
+
templateHtml(): Node;
|
|
39
|
+
templateCss(): string;
|
|
40
|
+
onConnect(): void;
|
|
41
|
+
private control;
|
|
42
|
+
/** Moves the content into the link or back into the button. */
|
|
43
|
+
private renderLink;
|
|
44
|
+
/** Its name for screen readers and its tooltip: the name, then the line under it. */
|
|
45
|
+
private renderLabel;
|
|
46
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
4
|
+
import { JlAvatar } from "../atoms/jl-avatar.js";
|
|
5
|
+
import jl_account_button_default from "./jl-account-button.css_raw.js";
|
|
6
|
+
import { css } from "@jadis/core";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
8
|
+
// src/molecules/jl-account-button.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The person signed in, where the frame of an app shows them (the foot of the side nav, a phone's top bar): their
|
|
11
|
+
* avatar, their name, and a line under it such as their role. With an `href` it is a link to their account, else a
|
|
12
|
+
* button that fires `press` (to open a menu). `compact` keeps the avatar alone, the name as its tooltip.
|
|
13
|
+
*/
|
|
14
|
+
var JlAccountButton = class extends JlElement {
|
|
15
|
+
static selector = "jl-account-button";
|
|
16
|
+
events = this.useEvents();
|
|
17
|
+
name = this.useChange("", (value) => {
|
|
18
|
+
this._refs.avatar.name.set(value);
|
|
19
|
+
this._refs.name.textContent = value;
|
|
20
|
+
this.renderLabel();
|
|
21
|
+
}, { attribute: "name" });
|
|
22
|
+
/** The line under the name: their role, their company. */
|
|
23
|
+
description = this.useChange("", (value) => {
|
|
24
|
+
this._refs.description.textContent = value;
|
|
25
|
+
this._refs.description.hidden = value === "";
|
|
26
|
+
this.renderLabel();
|
|
27
|
+
}, { attribute: "description" });
|
|
28
|
+
/** Their picture; their initials without it. */
|
|
29
|
+
src = this.useChange("", (value) => this._refs.avatar.src.set(value), { attribute: "src" });
|
|
30
|
+
/** Their account's address: the button becomes a link. */
|
|
31
|
+
href = this.useChange("", () => this.renderLink(), { attribute: "href" });
|
|
32
|
+
/** The avatar alone. */
|
|
33
|
+
compact = this.useChange(false, (value) => this.reflect("compact", value), { attribute: "compact" });
|
|
34
|
+
_refs = this.useRefs((ref) => ({
|
|
35
|
+
avatar: ref("jl-avatar"),
|
|
36
|
+
button: ref("button"),
|
|
37
|
+
content: ref(".content"),
|
|
38
|
+
description: ref(".description"),
|
|
39
|
+
link: ref("a"),
|
|
40
|
+
name: ref(".name")
|
|
41
|
+
}));
|
|
42
|
+
focus(options) {
|
|
43
|
+
this.control().focus(options);
|
|
44
|
+
}
|
|
45
|
+
templateHtml() {
|
|
46
|
+
const avatar = /* @__PURE__ */ jsx(JlAvatar, { size: "md" });
|
|
47
|
+
avatar.setAttribute("aria-hidden", "true");
|
|
48
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
49
|
+
type: "button",
|
|
50
|
+
class: "base",
|
|
51
|
+
attrs: { part: "base" },
|
|
52
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
53
|
+
class: "content",
|
|
54
|
+
children: [avatar, /* @__PURE__ */ jsxs("span", {
|
|
55
|
+
class: "text",
|
|
56
|
+
children: [/* @__PURE__ */ jsx("span", { class: "name" }), /* @__PURE__ */ jsx("span", {
|
|
57
|
+
class: "description",
|
|
58
|
+
hidden: true
|
|
59
|
+
})]
|
|
60
|
+
})]
|
|
61
|
+
})
|
|
62
|
+
}), /* @__PURE__ */ jsx("a", {
|
|
63
|
+
class: "base",
|
|
64
|
+
hidden: true,
|
|
65
|
+
attrs: { part: "base" }
|
|
66
|
+
})] });
|
|
67
|
+
}
|
|
68
|
+
templateCss() {
|
|
69
|
+
return css`${BASE_STYLES}${jl_account_button_default}`;
|
|
70
|
+
}
|
|
71
|
+
onConnect() {
|
|
72
|
+
this.on(this._refs.button, "click", () => this.events.emit("press"));
|
|
73
|
+
this.on(this._refs.link, "click", () => this.events.emit("press"));
|
|
74
|
+
}
|
|
75
|
+
control() {
|
|
76
|
+
return this.href.get() === "" ? this._refs.button : this._refs.link;
|
|
77
|
+
}
|
|
78
|
+
/** Moves the content into the link or back into the button. */
|
|
79
|
+
renderLink() {
|
|
80
|
+
const { button, content, link } = this._refs;
|
|
81
|
+
const href = this.href.get();
|
|
82
|
+
this.control().append(content);
|
|
83
|
+
button.hidden = href !== "";
|
|
84
|
+
link.hidden = href === "";
|
|
85
|
+
setOrRemove(link, "href", href);
|
|
86
|
+
}
|
|
87
|
+
/** Its name for screen readers and its tooltip: the name, then the line under it. */
|
|
88
|
+
renderLabel() {
|
|
89
|
+
const label = [this.name.get(), this.description.get()].filter((part) => part !== "").join(", ");
|
|
90
|
+
for (const control of [this._refs.button, this._refs.link]) {
|
|
91
|
+
setOrRemove(control, "title", this.name.get());
|
|
92
|
+
setOrRemove(control, "aria-label", label);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
JlAccountButton.register();
|
|
97
|
+
export { JlAccountButton };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { JlElement } from '../base/jl-element';
|
|
2
2
|
import type { Tone } from '../types/options.types';
|
|
3
3
|
/**
|
|
4
|
-
* A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text
|
|
5
|
-
* in the default slot, buttons in `slot="actions"`. A `dismissible`
|
|
4
|
+
* A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text is the
|
|
5
|
+
* `description` or goes in the default slot (for links and markup), buttons in `slot="actions"`. A `dismissible`
|
|
6
|
+
* alert has an × that hides it.
|
|
6
7
|
*/
|
|
7
8
|
export declare class JlAlert extends JlElement {
|
|
8
9
|
static readonly selector = "jl-alert";
|
|
@@ -17,6 +18,11 @@ export declare class JlAlert extends JlElement {
|
|
|
17
18
|
get(): string;
|
|
18
19
|
set(setter: string | ((prevState: string) => string)): void;
|
|
19
20
|
}>>;
|
|
21
|
+
/** The message as plain text, for pages that set it from code; richer content goes in the default slot. */
|
|
22
|
+
readonly description: Readonly<Readonly<{
|
|
23
|
+
get(): string;
|
|
24
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
25
|
+
}>>;
|
|
20
26
|
/** An icon name in place of the tone's own; `none` shows no icon. */
|
|
21
27
|
readonly icon: Readonly<Readonly<{
|
|
22
28
|
get(): string;
|
|
@@ -33,7 +39,8 @@ export declare class JlAlert extends JlElement {
|
|
|
33
39
|
templateHtml(): Node;
|
|
34
40
|
templateCss(): string;
|
|
35
41
|
onConnect(): void;
|
|
36
|
-
|
|
42
|
+
/** Empty parts take no room: an alert with only a description has no blank line under it. */
|
|
43
|
+
private renderSlots;
|
|
37
44
|
/** The tone's own icon is filled, as a toast's is; an icon of the page's stays as it is drawn. */
|
|
38
45
|
private renderIcon;
|
|
39
46
|
}
|
|
@@ -31,8 +31,9 @@ var TONE_ROLES = {
|
|
|
31
31
|
warn: "alert"
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
* A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text
|
|
35
|
-
* in the default slot, buttons in `slot="actions"`. A `dismissible`
|
|
34
|
+
* A message in the flow of the page, coloured by its tone: a tip, a success, a warning, an error. The text is the
|
|
35
|
+
* `description` or goes in the default slot (for links and markup), buttons in `slot="actions"`. A `dismissible`
|
|
36
|
+
* alert has an × that hides it.
|
|
36
37
|
*/
|
|
37
38
|
var JlAlert = class extends JlElement {
|
|
38
39
|
static selector = "jl-alert";
|
|
@@ -49,6 +50,11 @@ var JlAlert = class extends JlElement {
|
|
|
49
50
|
this._refs.heading.textContent = value;
|
|
50
51
|
this._refs.heading.hidden = value === "";
|
|
51
52
|
}, { attribute: "heading" });
|
|
53
|
+
/** The message as plain text, for pages that set it from code; richer content goes in the default slot. */
|
|
54
|
+
description = this.useChange("", (value) => {
|
|
55
|
+
this._refs.description.textContent = value;
|
|
56
|
+
this._refs.description.hidden = value === "";
|
|
57
|
+
}, { attribute: "description" });
|
|
52
58
|
/** An icon name in place of the tone's own; `none` shows no icon. */
|
|
53
59
|
icon = this.useChange("", () => this.renderIcon(), { attribute: "icon" });
|
|
54
60
|
/** Shows an × that hides the alert and fires `dismiss`. */
|
|
@@ -61,8 +67,11 @@ var JlAlert = class extends JlElement {
|
|
|
61
67
|
actionsSlot: ref("slot[name=\"actions\"]"),
|
|
62
68
|
alert: ref(".alert"),
|
|
63
69
|
close: ref(".close"),
|
|
70
|
+
description: ref(".description"),
|
|
64
71
|
heading: ref(".heading"),
|
|
65
|
-
icon: ref(".icon")
|
|
72
|
+
icon: ref(".icon"),
|
|
73
|
+
text: ref(".text"),
|
|
74
|
+
textSlot: ref("slot:not([name])")
|
|
66
75
|
}));
|
|
67
76
|
/** Hides the alert as its × does, and fires `dismiss`. */
|
|
68
77
|
dismiss() {
|
|
@@ -86,8 +95,13 @@ var JlAlert = class extends JlElement {
|
|
|
86
95
|
class: "heading",
|
|
87
96
|
hidden: true
|
|
88
97
|
}),
|
|
98
|
+
/* @__PURE__ */ jsx("p", {
|
|
99
|
+
class: "description",
|
|
100
|
+
hidden: true
|
|
101
|
+
}),
|
|
89
102
|
/* @__PURE__ */ jsx("div", {
|
|
90
103
|
class: "text",
|
|
104
|
+
hidden: true,
|
|
91
105
|
children: /* @__PURE__ */ jsx("slot", {})
|
|
92
106
|
}),
|
|
93
107
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -113,10 +127,13 @@ var JlAlert = class extends JlElement {
|
|
|
113
127
|
}
|
|
114
128
|
onConnect() {
|
|
115
129
|
this._refs.close.events.register("press", () => this.dismiss());
|
|
116
|
-
this.
|
|
117
|
-
this.on(this._refs.actionsSlot, "slotchange", () => this.
|
|
130
|
+
this.renderSlots();
|
|
131
|
+
this.on(this._refs.actionsSlot, "slotchange", () => this.renderSlots());
|
|
132
|
+
this.on(this._refs.textSlot, "slotchange", () => this.renderSlots());
|
|
118
133
|
}
|
|
119
|
-
|
|
134
|
+
/** Empty parts take no room: an alert with only a description has no blank line under it. */
|
|
135
|
+
renderSlots() {
|
|
136
|
+
this._refs.text.hidden = !hasSlotted(this._refs.textSlot);
|
|
120
137
|
this._refs.actions.hidden = !hasSlotted(this._refs.actionsSlot);
|
|
121
138
|
}
|
|
122
139
|
/** The tone's own icon is filled, as a toast's is; an icon of the page's stays as it is drawn. */
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-chip-group.css?raw
|
|
2
|
+
var jl_chip_group_default = ":host {\n display: block;\n min-width: 0;\n --height: 28px;\n --pad: 10px;\n --font-size: var(--jl-font-sm);\n}\n\n:host([size=\"sm\"]) {\n --height: 24px;\n --pad: 8px;\n --font-size: var(--jl-font-xs);\n}\n\n/* Where a thumb presses it. */\n:host([size=\"lg\"]) {\n --height: 36px;\n --pad: 14px;\n --font-size: var(--jl-font-md);\n}\n\n/*\n * One row that scrolls sideways on a narrow screen. The padding keeps the focus ring inside what the scroller\n * clips; the negative margin keeps the chips aligned with the content around.\n */\n.group {\n display: flex;\n gap: 8px;\n min-width: 0;\n margin: -3px;\n padding: 3px;\n overflow-x: auto;\n scrollbar-width: none;\n contain: inline-size;\n}\n\n.group::-webkit-scrollbar {\n display: none;\n}\n\n:host([wrap]) .group {\n flex-wrap: wrap;\n overflow: visible;\n contain: none;\n}\n\n.chip {\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n height: var(--height);\n padding: 0 var(--pad);\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface);\n color: var(--jl-text-2);\n font-size: var(--font-size);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background var(--jl-fast) var(--jl-ease),\n border-color var(--jl-fast) var(--jl-ease),\n color var(--jl-fast) var(--jl-ease);\n}\n\n.chip:hover:not(:disabled) {\n background: var(--jl-surface-2);\n}\n\n.chip.selected,\n.chip.selected:hover:not(:disabled) {\n border-color: var(--jl-accent-line);\n background: var(--jl-accent-soft);\n color: var(--jl-accent);\n}\n\n.chip:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* A line box taller than the chip's own line-height of 1: the accents of É, Â, Ö stay inside what overflow clips. */\n.label {\n overflow: hidden;\n line-height: 1.4;\n text-overflow: ellipsis;\n}\n\n.badge {\n min-width: 18px;\n margin-right: -4px;\n padding: 0 6px;\n border-radius: var(--jl-radius-pill);\n background: var(--jl-surface-3);\n color: var(--jl-muted);\n font-size: var(--jl-font-xs);\n font-variant-numeric: tabular-nums;\n font-weight: 650;\n line-height: 18px;\n text-align: center;\n}\n\n.chip.selected .badge {\n background: color-mix(in srgb, var(--jl-accent) 14%, transparent);\n color: var(--jl-accent);\n}\n";
|
|
3
|
+
export { jl_chip_group_default as default };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { JlFormControl } from '../base/jl-form-control';
|
|
2
|
+
import type { ChipGroupOption } from '../types/jl-chip-group.types';
|
|
3
|
+
import type { ControlSize } from '../types/options.types';
|
|
4
|
+
/**
|
|
5
|
+
* A row of chips to choose from: a status to filter by, a category, a reason. One choice by default, as radios
|
|
6
|
+
* (choosing the chosen chip again keeps it); `multiple` lets people turn each chip on and off. On a narrow screen
|
|
7
|
+
* the row scrolls sideways; `wrap` lets it go to the next line instead. `change` fires with the chosen values.
|
|
8
|
+
*/
|
|
9
|
+
export declare class JlChipGroup extends JlFormControl {
|
|
10
|
+
static readonly selector = "jl-chip-group";
|
|
11
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
12
|
+
change: string[];
|
|
13
|
+
}>>;
|
|
14
|
+
readonly options: Readonly<Readonly<{
|
|
15
|
+
get(): ChipGroupOption[];
|
|
16
|
+
set(setter: ChipGroupOption[] | ((prevState: ChipGroupOption[]) => ChipGroupOption[])): void;
|
|
17
|
+
}>>;
|
|
18
|
+
/** The chosen values, in the options' order; with one choice, a list of at most one. */
|
|
19
|
+
readonly value: Readonly<Readonly<{
|
|
20
|
+
get(): string[];
|
|
21
|
+
set(setter: string[] | ((prevState: string[]) => string[])): void;
|
|
22
|
+
}>>;
|
|
23
|
+
/** Several chips can be chosen, each turned on and off on its own. */
|
|
24
|
+
readonly multiple: Readonly<Readonly<{
|
|
25
|
+
get(): boolean;
|
|
26
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
27
|
+
}>>;
|
|
28
|
+
/** Goes to the next line when the chips do not fit, instead of scrolling sideways. */
|
|
29
|
+
readonly wrap: Readonly<Readonly<{
|
|
30
|
+
get(): boolean;
|
|
31
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
32
|
+
}>>;
|
|
33
|
+
readonly size: Readonly<Readonly<{
|
|
34
|
+
get(): ControlSize;
|
|
35
|
+
set(setter: ((prevState: ControlSize) => ControlSize) | ControlSize): void;
|
|
36
|
+
}>>;
|
|
37
|
+
/** The group's name for screen readers, when no visible label names it. */
|
|
38
|
+
readonly label: Readonly<Readonly<{
|
|
39
|
+
get(): string;
|
|
40
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
41
|
+
}>>;
|
|
42
|
+
/** A form refuses to submit while nothing is chosen. */
|
|
43
|
+
readonly required: Readonly<Readonly<{
|
|
44
|
+
get(): boolean;
|
|
45
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
46
|
+
}>>;
|
|
47
|
+
readonly disabled: Readonly<Readonly<{
|
|
48
|
+
get(): boolean;
|
|
49
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
50
|
+
}>>;
|
|
51
|
+
/** The chip that holds the Tab stop with several choices: the last one people reached. */
|
|
52
|
+
private readonly _state;
|
|
53
|
+
private readonly _nameWatch;
|
|
54
|
+
private readonly _refs;
|
|
55
|
+
/** Focuses the chip that holds the Tab stop. */
|
|
56
|
+
focus(options?: FocusOptions): void;
|
|
57
|
+
templateHtml(): Node;
|
|
58
|
+
templateCss(): string;
|
|
59
|
+
onConnect(): void;
|
|
60
|
+
onDisconnect(): void;
|
|
61
|
+
protected resetValue(): void;
|
|
62
|
+
/** The browser calls it for a disabled fieldset and for the element's own `disabled`, even before it renders. */
|
|
63
|
+
protected setDisabledByForm(disabled: boolean): void;
|
|
64
|
+
private chips;
|
|
65
|
+
/** What is chosen: with one choice, only the first value counts. */
|
|
66
|
+
private chosen;
|
|
67
|
+
private isDisabled;
|
|
68
|
+
/** One choice: the chip becomes it. Several: the chip turns on or off, the values keeping the options' order. */
|
|
69
|
+
private press;
|
|
70
|
+
private commit;
|
|
71
|
+
private onKeydown;
|
|
72
|
+
private renderOptions;
|
|
73
|
+
private chip;
|
|
74
|
+
/** Radios with one choice, toggle buttons with several; each chosen chip shows its icon or a check. */
|
|
75
|
+
private renderValue;
|
|
76
|
+
/** Tab reaches one chip: the chosen one with one choice, the last one reached with several, else the first. */
|
|
77
|
+
private renderTabStop;
|
|
78
|
+
/** One choice gives one entry; several give one entry each under the field's name, as checkboxes would. */
|
|
79
|
+
private renderFormValue;
|
|
80
|
+
private renderValidity;
|
|
81
|
+
}
|