@syware/jlib 0.4.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 +8 -3
- package/dist/atoms/jl-badge.d.ts +12 -1
- package/dist/atoms/jl-badge.js +15 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -2
- package/dist/lib/time.d.ts +10 -0
- package/dist/lib/time.js +64 -0
- 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-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/organisms/jl-confirm.d.ts +7 -1
- package/dist/organisms/jl-confirm.js +22 -1
- 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-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-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-toaster.types.d.ts +1 -1
- package/package.json +1 -1
package/dist/all.js
CHANGED
|
@@ -8,6 +8,7 @@ import { PLACEMENTS, computePosition, followAnchor, placeFloating } from "./lib/
|
|
|
8
8
|
import { COMMON_ICONS } from "./lib/icon-set.js";
|
|
9
9
|
import { hasIcon, iconElement, iconNames, registerIcons } from "./lib/icons.js";
|
|
10
10
|
import { CONTROL_SIZES, TONES, TONE_ICONS, TONE_ICON_FILLED } from "./lib/options.js";
|
|
11
|
+
import { formatDuration, formatRelativeTime } from "./lib/time.js";
|
|
11
12
|
import { JlIcon } from "./atoms/jl-icon.js";
|
|
12
13
|
import { AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, JlAvatar } from "./atoms/jl-avatar.js";
|
|
13
14
|
import { JlBadge } from "./atoms/jl-badge.js";
|
|
@@ -29,6 +30,7 @@ import { JlSwitch, SWITCH_SIZES } from "./atoms/jl-switch.js";
|
|
|
29
30
|
import { JlTextarea } from "./atoms/jl-textarea.js";
|
|
30
31
|
import { JlAccordionItem } from "./molecules/jl-accordion-item.js";
|
|
31
32
|
import { ACCORDION_VARIANTS, JlAccordion } from "./molecules/jl-accordion.js";
|
|
33
|
+
import { JlAccountButton } from "./molecules/jl-account-button.js";
|
|
32
34
|
import { JlAlert } from "./molecules/jl-alert.js";
|
|
33
35
|
import { JlAvatarGroup } from "./molecules/jl-avatar-group.js";
|
|
34
36
|
import { JlBreadcrumb } from "./molecules/jl-breadcrumb.js";
|
|
@@ -43,11 +45,13 @@ import { EMPTY_SIZES, JlEmpty } from "./molecules/jl-empty.js";
|
|
|
43
45
|
import { FIELD_ORIENTATIONS, JlField } from "./molecules/jl-field.js";
|
|
44
46
|
import { JlFileDrop } from "./molecules/jl-file-drop.js";
|
|
45
47
|
import { JlListItem } from "./molecules/jl-list-item.js";
|
|
48
|
+
import { JlLoadState, LOAD_SKELETONS, LOAD_STATES } from "./molecules/jl-load-state.js";
|
|
46
49
|
import { JlMenu } from "./molecules/jl-menu.js";
|
|
47
50
|
import { JlNumberInput } from "./molecules/jl-number-input.js";
|
|
48
51
|
import { JlPagination } from "./molecules/jl-pagination.js";
|
|
49
52
|
import { JlPopover } from "./molecules/jl-popover.js";
|
|
50
53
|
import { JlRadioGroup, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS } from "./molecules/jl-radio-group.js";
|
|
54
|
+
import { JlSearchButton } from "./molecules/jl-search-button.js";
|
|
51
55
|
import { JlSearch } from "./molecules/jl-search.js";
|
|
52
56
|
import { JlSegmented } from "./molecules/jl-segmented.js";
|
|
53
57
|
import { JlStat, STAT_TRENDS } from "./molecules/jl-stat.js";
|
|
@@ -60,7 +64,7 @@ import { JlToast, TOAST_TONES } from "./molecules/jl-toast.js";
|
|
|
60
64
|
import { JlTooltip } from "./molecules/jl-tooltip.js";
|
|
61
65
|
import { JlCommandPalette } from "./organisms/jl-command-palette.js";
|
|
62
66
|
import { DIALOG_SIZES, JlDialog } from "./organisms/jl-dialog.js";
|
|
63
|
-
import { CONFIRM_TONES, JlConfirm, confirmDialog } from "./organisms/jl-confirm.js";
|
|
67
|
+
import { CONFIRM_TONES, JlConfirm, confirmDialog, guardLeave } from "./organisms/jl-confirm.js";
|
|
64
68
|
import { DRAWER_PLACEMENTS, DRAWER_SIZES, JlDrawer } from "./organisms/jl-drawer.js";
|
|
65
69
|
import { JlFlow } from "./organisms/jl-flow.js";
|
|
66
70
|
import { JlNavbar } from "./organisms/jl-navbar.js";
|
|
@@ -71,8 +75,9 @@ import { JlTable, TABLE_SORT_DIRECTIONS } from "./organisms/jl-table.js";
|
|
|
71
75
|
import { JlToaster, TOASTER_PLACEMENTS, toast } from "./organisms/jl-toaster.js";
|
|
72
76
|
import { JlTree } from "./organisms/jl-tree.js";
|
|
73
77
|
import { JlAppShell } from "./templates/jl-app-shell.js";
|
|
78
|
+
import { JlPage, PAGE_WIDTHS } from "./templates/jl-page.js";
|
|
74
79
|
import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
|
|
75
80
|
// src/all.ts
|
|
76
81
|
registerIcons(COMMON_ICONS);
|
|
77
|
-
define(JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlEmpty, JlField, JlFileDrop, JlFlow, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabBar, JlTable, JlTabPanel, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree);
|
|
78
|
-
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabBar, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
|
82
|
+
define(JlAccordion, JlAccordionItem, JlAccountButton, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlEmpty, JlField, JlFileDrop, JlFlow, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlLoadState, JlMenu, JlNavbar, JlNumberInput, JlPage, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSearchButton, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabBar, JlTable, JlTabPanel, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree);
|
|
83
|
+
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAccountButton, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlLoadState, JlMenu, JlNavbar, JlNumberInput, JlPage, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSearchButton, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabBar, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, LOAD_SKELETONS, LOAD_STATES, PAGE_WIDTHS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, formatDuration, formatRelativeTime, guardLeave, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
package/dist/atoms/jl-badge.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { StatusLook } from '../types/jl-badge.types';
|
|
2
3
|
import type { Tone } from '../types/options.types';
|
|
3
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A short status, coloured by its tone: "Active", "3 new", "Failed". A `dot` marks a live state; a `hint` says what
|
|
6
|
+
* the state means, as a tooltip. `showStatus()` takes the look of a state from an app's map of them.
|
|
7
|
+
*/
|
|
4
8
|
export declare class JlBadge extends JlElement {
|
|
5
9
|
static readonly selector = "jl-badge";
|
|
6
10
|
readonly label: Readonly<Readonly<{
|
|
@@ -24,7 +28,14 @@ export declare class JlBadge extends JlElement {
|
|
|
24
28
|
get(): boolean;
|
|
25
29
|
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
26
30
|
}>>;
|
|
31
|
+
/** What the state means, as a tooltip: "Waiting for logistics to prepare it." */
|
|
32
|
+
readonly hint: Readonly<Readonly<{
|
|
33
|
+
get(): string;
|
|
34
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
35
|
+
}>>;
|
|
27
36
|
private readonly _refs;
|
|
37
|
+
/** Shows a state as an app's map of states describes it: its label, tone, icon and hint. */
|
|
38
|
+
showStatus(look: StatusLook): void;
|
|
28
39
|
templateHtml(): Node;
|
|
29
40
|
templateCss(): string;
|
|
30
41
|
/** An icon wins over the dot: a badge shows one marker at most. */
|
package/dist/atoms/jl-badge.js
CHANGED
|
@@ -2,13 +2,17 @@ import { BASE_STYLES } from "../styles/shared.js";
|
|
|
2
2
|
import { JlElement } from "../base/jl-element.js";
|
|
3
3
|
import { oneOf } from "../lib/attributes.js";
|
|
4
4
|
import { TONES } from "../lib/options.js";
|
|
5
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
5
6
|
import { JlIcon } from "./jl-icon.js";
|
|
6
7
|
import jl_badge_default from "./jl-badge.css_raw.js";
|
|
7
8
|
import { css } from "@jadis/core";
|
|
8
9
|
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
9
10
|
// src/atoms/jl-badge.tsx
|
|
10
11
|
var ICON_SIZE = 13;
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A short status, coloured by its tone: "Active", "3 new", "Failed". A `dot` marks a live state; a `hint` says what
|
|
14
|
+
* the state means, as a tooltip. `showStatus()` takes the look of a state from an app's map of them.
|
|
15
|
+
*/
|
|
12
16
|
var JlBadge = class extends JlElement {
|
|
13
17
|
static selector = "jl-badge";
|
|
14
18
|
label = this.useChange("", (value) => {
|
|
@@ -22,11 +26,21 @@ var JlBadge = class extends JlElement {
|
|
|
22
26
|
dot = this.useChange(false, () => this.renderMarker(), { attribute: "dot" });
|
|
23
27
|
/** Filled with the tone's colour, for what must stand out. */
|
|
24
28
|
solid = this.useChange(false, (value) => this.reflect("solid", value), { attribute: "solid" });
|
|
29
|
+
/** What the state means, as a tooltip: "Waiting for logistics to prepare it." */
|
|
30
|
+
hint = this.useChange("", (value) => setOrRemove(this._refs.badge, "title", value), { attribute: "hint" });
|
|
25
31
|
_refs = this.useRefs((ref) => ({
|
|
32
|
+
badge: ref(".badge"),
|
|
26
33
|
dot: ref(".dot"),
|
|
27
34
|
icon: ref("jl-icon"),
|
|
28
35
|
label: ref(".label")
|
|
29
36
|
}));
|
|
37
|
+
/** Shows a state as an app's map of states describes it: its label, tone, icon and hint. */
|
|
38
|
+
showStatus(look) {
|
|
39
|
+
this.label.set(look.label);
|
|
40
|
+
this.tone.set(look.tone);
|
|
41
|
+
this.icon.set(look.icon ?? "");
|
|
42
|
+
this.hint.set(look.hint ?? "");
|
|
43
|
+
}
|
|
30
44
|
templateHtml() {
|
|
31
45
|
return /* @__PURE__ */ jsxs("span", {
|
|
32
46
|
class: "badge",
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { computePosition, followAnchor, PLACEMENTS, placeFloating } from './lib/
|
|
|
6
6
|
export { COMMON_ICONS } from './lib/icon-set';
|
|
7
7
|
export { hasIcon, iconElement, iconNames, registerIcons } from './lib/icons';
|
|
8
8
|
export { CONTROL_SIZES, TONE_ICON_FILLED, TONE_ICONS, TONES } from './lib/options';
|
|
9
|
+
export { formatDuration, formatRelativeTime } from './lib/time';
|
|
9
10
|
export { adoptTokens } from './lib/tokens';
|
|
10
11
|
export { BASE_STYLES, CONTROL_STYLES, FLOATING_STYLES, TOKEN_STYLES } from './styles/shared';
|
|
11
12
|
export type * from './types/calendar.types';
|
|
@@ -16,6 +17,7 @@ export type * from './types/highlight.types';
|
|
|
16
17
|
export type * from './types/hotkey.types';
|
|
17
18
|
export type * from './types/jl-accordion.types';
|
|
18
19
|
export type * from './types/jl-avatar.types';
|
|
20
|
+
export type * from './types/jl-badge.types';
|
|
19
21
|
export type * from './types/jl-breadcrumb.types';
|
|
20
22
|
export type * from './types/jl-button.types';
|
|
21
23
|
export type * from './types/jl-card.types';
|
|
@@ -32,8 +34,10 @@ export type * from './types/jl-field.types';
|
|
|
32
34
|
export type * from './types/jl-flow.types';
|
|
33
35
|
export type * from './types/jl-input.types';
|
|
34
36
|
export type * from './types/jl-link.types';
|
|
37
|
+
export type * from './types/jl-load-state.types';
|
|
35
38
|
export type * from './types/jl-menu.types';
|
|
36
39
|
export type * from './types/jl-navbar.types';
|
|
40
|
+
export type * from './types/jl-page.types';
|
|
37
41
|
export type * from './types/jl-progress.types';
|
|
38
42
|
export type * from './types/jl-radio-group.types';
|
|
39
43
|
export type * from './types/jl-segmented.types';
|
|
@@ -80,6 +84,7 @@ export * from './atoms/jl-switch';
|
|
|
80
84
|
export * from './atoms/jl-textarea';
|
|
81
85
|
export * from './molecules/jl-accordion-item';
|
|
82
86
|
export * from './molecules/jl-accordion';
|
|
87
|
+
export * from './molecules/jl-account-button';
|
|
83
88
|
export * from './molecules/jl-alert';
|
|
84
89
|
export * from './molecules/jl-avatar-group';
|
|
85
90
|
export * from './molecules/jl-breadcrumb';
|
|
@@ -94,11 +99,13 @@ export * from './molecules/jl-empty';
|
|
|
94
99
|
export * from './molecules/jl-field';
|
|
95
100
|
export * from './molecules/jl-file-drop';
|
|
96
101
|
export * from './molecules/jl-list-item';
|
|
102
|
+
export * from './molecules/jl-load-state';
|
|
97
103
|
export * from './molecules/jl-menu';
|
|
98
104
|
export * from './molecules/jl-number-input';
|
|
99
105
|
export * from './molecules/jl-pagination';
|
|
100
106
|
export * from './molecules/jl-popover';
|
|
101
107
|
export * from './molecules/jl-radio-group';
|
|
108
|
+
export * from './molecules/jl-search-button';
|
|
102
109
|
export * from './molecules/jl-search';
|
|
103
110
|
export * from './molecules/jl-segmented';
|
|
104
111
|
export * from './molecules/jl-stat';
|
|
@@ -122,4 +129,5 @@ export * from './organisms/jl-table';
|
|
|
122
129
|
export * from './organisms/jl-toaster';
|
|
123
130
|
export * from './organisms/jl-tree';
|
|
124
131
|
export * from './templates/jl-app-shell';
|
|
132
|
+
export * from './templates/jl-page';
|
|
125
133
|
export * from './templates/jl-split-view';
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { PLACEMENTS, computePosition, followAnchor, placeFloating } from "./lib/
|
|
|
8
8
|
import { COMMON_ICONS } from "./lib/icon-set.js";
|
|
9
9
|
import { hasIcon, iconElement, iconNames, registerIcons } from "./lib/icons.js";
|
|
10
10
|
import { CONTROL_SIZES, TONES, TONE_ICONS, TONE_ICON_FILLED } from "./lib/options.js";
|
|
11
|
+
import { formatDuration, formatRelativeTime } from "./lib/time.js";
|
|
11
12
|
import { JlIcon } from "./atoms/jl-icon.js";
|
|
12
13
|
import { AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, JlAvatar } from "./atoms/jl-avatar.js";
|
|
13
14
|
import { JlBadge } from "./atoms/jl-badge.js";
|
|
@@ -29,6 +30,7 @@ import { JlSwitch, SWITCH_SIZES } from "./atoms/jl-switch.js";
|
|
|
29
30
|
import { JlTextarea } from "./atoms/jl-textarea.js";
|
|
30
31
|
import { JlAccordionItem } from "./molecules/jl-accordion-item.js";
|
|
31
32
|
import { ACCORDION_VARIANTS, JlAccordion } from "./molecules/jl-accordion.js";
|
|
33
|
+
import { JlAccountButton } from "./molecules/jl-account-button.js";
|
|
32
34
|
import { JlAlert } from "./molecules/jl-alert.js";
|
|
33
35
|
import { JlAvatarGroup } from "./molecules/jl-avatar-group.js";
|
|
34
36
|
import { JlBreadcrumb } from "./molecules/jl-breadcrumb.js";
|
|
@@ -43,11 +45,13 @@ import { EMPTY_SIZES, JlEmpty } from "./molecules/jl-empty.js";
|
|
|
43
45
|
import { FIELD_ORIENTATIONS, JlField } from "./molecules/jl-field.js";
|
|
44
46
|
import { JlFileDrop } from "./molecules/jl-file-drop.js";
|
|
45
47
|
import { JlListItem } from "./molecules/jl-list-item.js";
|
|
48
|
+
import { JlLoadState, LOAD_SKELETONS, LOAD_STATES } from "./molecules/jl-load-state.js";
|
|
46
49
|
import { JlMenu } from "./molecules/jl-menu.js";
|
|
47
50
|
import { JlNumberInput } from "./molecules/jl-number-input.js";
|
|
48
51
|
import { JlPagination } from "./molecules/jl-pagination.js";
|
|
49
52
|
import { JlPopover } from "./molecules/jl-popover.js";
|
|
50
53
|
import { JlRadioGroup, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS } from "./molecules/jl-radio-group.js";
|
|
54
|
+
import { JlSearchButton } from "./molecules/jl-search-button.js";
|
|
51
55
|
import { JlSearch } from "./molecules/jl-search.js";
|
|
52
56
|
import { JlSegmented } from "./molecules/jl-segmented.js";
|
|
53
57
|
import { JlStat, STAT_TRENDS } from "./molecules/jl-stat.js";
|
|
@@ -60,7 +64,7 @@ import { JlToast, TOAST_TONES } from "./molecules/jl-toast.js";
|
|
|
60
64
|
import { JlTooltip } from "./molecules/jl-tooltip.js";
|
|
61
65
|
import { JlCommandPalette } from "./organisms/jl-command-palette.js";
|
|
62
66
|
import { DIALOG_SIZES, JlDialog } from "./organisms/jl-dialog.js";
|
|
63
|
-
import { CONFIRM_TONES, JlConfirm, confirmDialog } from "./organisms/jl-confirm.js";
|
|
67
|
+
import { CONFIRM_TONES, JlConfirm, confirmDialog, guardLeave } from "./organisms/jl-confirm.js";
|
|
64
68
|
import { DRAWER_PLACEMENTS, DRAWER_SIZES, JlDrawer } from "./organisms/jl-drawer.js";
|
|
65
69
|
import { JlFlow } from "./organisms/jl-flow.js";
|
|
66
70
|
import { JlNavbar } from "./organisms/jl-navbar.js";
|
|
@@ -71,5 +75,6 @@ import { JlTable, TABLE_SORT_DIRECTIONS } from "./organisms/jl-table.js";
|
|
|
71
75
|
import { JlToaster, TOASTER_PLACEMENTS, toast } from "./organisms/jl-toaster.js";
|
|
72
76
|
import { JlTree } from "./organisms/jl-tree.js";
|
|
73
77
|
import { JlAppShell } from "./templates/jl-app-shell.js";
|
|
78
|
+
import { JlPage, PAGE_WIDTHS } from "./templates/jl-page.js";
|
|
74
79
|
import { JlSplitView, SPLIT_ORIENTATIONS } from "./templates/jl-split-view.js";
|
|
75
|
-
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlMenu, JlNavbar, JlNumberInput, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabBar, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
|
80
|
+
export { ACCORDION_VARIANTS, AVATAR_SHAPES, AVATAR_SIZES, AVATAR_STATUSES, BASE_STYLES, BUTTON_TYPES, BUTTON_VARIANTS, CARD_PADDINGS, CHIP_SIZES, CODE_LANGUAGES, COMMON_ICONS, CONFIRM_TONES, CONTROL_SIZES, CONTROL_STYLES, DIALOG_SIZES, DIVIDER_ALIGNS, DRAWER_PLACEMENTS, DRAWER_SIZES, EMPTY_SIZES, FIELD_ORIENTATIONS, FLOATING_STYLES, INPUT_TYPES, JlAccordion, JlAccordionItem, JlAccountButton, JlAlert, JlAppShell, JlAvatar, JlAvatarGroup, JlBadge, JlBreadcrumb, JlButton, JlCalendar, JlCard, JlCheckbox, JlChip, JlChipGroup, JlCodeBlock, JlCombobox, JlCommandPalette, JlConfirm, JlCopyButton, JlDatePicker, JlDialog, JlDivider, JlDrawer, JlElement, JlEmpty, JlField, JlFileDrop, JlFlow, JlFormControl, JlIcon, JlInput, JlKbd, JlLink, JlListItem, JlLoadState, JlMenu, JlNavbar, JlNumberInput, JlPage, JlPageHeader, JlPagination, JlPopover, JlProgress, JlProgressRing, JlRadioGroup, JlRating, JlSearch, JlSearchButton, JlSegmented, JlSelect, JlSideNav, JlSkeleton, JlSlider, JlSpinner, JlSplitView, JlStat, JlSteps, JlSwitch, JlTabBar, JlTabPanel, JlTable, JlTabs, JlTagInput, JlTextarea, JlTimeline, JlToast, JlToaster, JlTooltip, JlTree, LINK_VARIANTS, LOAD_SKELETONS, LOAD_STATES, PAGE_WIDTHS, PLACEMENTS, PROGRESS_SIZES, PROGRESS_TONES, RADIO_GROUP_ORIENTATIONS, RADIO_GROUP_VARIANTS, SKELETON_SHAPES, SPLIT_ORIENTATIONS, STAT_TRENDS, STEPS_ORIENTATIONS, SWITCH_SIZES, TABLE_SORT_DIRECTIONS, TAB_VARIANTS, TOASTER_PLACEMENTS, TOAST_TONES, TOKEN_STYLES, TONES, TONE_ICONS, TONE_ICON_FILLED, adoptTokens, clampedNumber, computePosition, confirmDialog, define, followAnchor, formatDuration, formatRelativeTime, guardLeave, hasIcon, iconElement, iconNames, jsonOf, oneOf, placeFloating, registerIcons, toast };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A duration in the units that read best: "< 1 sec" under a second (never "0.0 s"), then seconds, minutes, hours
|
|
3
|
+
* and minutes, days and hours.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatDuration(milliseconds: number, locale?: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* When something happened, from `now`: "now", "5 minutes ago", "3 hours ago", "yesterday" within a week, then the
|
|
8
|
+
* date ("12 Mar", with the year when it is not this year's). Times to come read the same way: "in 5 minutes".
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatRelativeTime(date: Date | number | string, now: Date | number, locale?: string): string;
|
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 };
|
|
@@ -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 };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-load-state.css?raw
|
|
2
|
+
var jl_load_state_default = ":host {\n display: block;\n}\n\n.bones {\n display: grid;\n gap: 16px;\n padding: 16px;\n}\n\n.bones.lines {\n gap: 10px;\n}\n\n.bones.cards {\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: 12px;\n padding: 0;\n}\n\n.bone-row {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.bone-lines {\n display: grid;\n flex: 1;\n gap: 6px;\n}\n\n.bone-card {\n display: grid;\n gap: 8px;\n padding: 12px;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-lg);\n}\n";
|
|
3
|
+
export { jl_load_state_default as default };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { LoadSkeleton, LoadState, LoadTask } from '../types/jl-load-state.types';
|
|
3
|
+
export declare const LOAD_STATES: readonly LoadState[];
|
|
4
|
+
export declare const LOAD_SKELETONS: readonly LoadSkeleton[];
|
|
5
|
+
/**
|
|
6
|
+
* What a list or a page shows while its data comes: nothing for a moment, then a skeleton shaped like it; then the
|
|
7
|
+
* data (the default slot), what to say when there is none (slot `empty`, a `jl-empty`), or, when it failed, what
|
|
8
|
+
* failed and Try again. `load()` fetches, draws and moves between the states; a later `load()` keeps what is shown
|
|
9
|
+
* until the new data is there, so live updates do not flash. Or set `state` yourself.
|
|
10
|
+
*/
|
|
11
|
+
export declare class JlLoadState extends JlElement {
|
|
12
|
+
static readonly selector = "jl-load-state";
|
|
13
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
14
|
+
retry: undefined;
|
|
15
|
+
}>>;
|
|
16
|
+
readonly state: Readonly<Readonly<{
|
|
17
|
+
get(): LoadState;
|
|
18
|
+
set(setter: ((prevState: LoadState) => LoadState) | LoadState): void;
|
|
19
|
+
}>>;
|
|
20
|
+
/** The shape of the skeleton; slot `loading` takes one of the page's own instead. */
|
|
21
|
+
readonly skeleton: Readonly<Readonly<{
|
|
22
|
+
get(): LoadSkeleton;
|
|
23
|
+
set(setter: ((prevState: LoadSkeleton) => LoadSkeleton) | LoadSkeleton): void;
|
|
24
|
+
}>>;
|
|
25
|
+
/** How many rows, cards or lines the skeleton has. */
|
|
26
|
+
readonly count: Readonly<Readonly<{
|
|
27
|
+
get(): number;
|
|
28
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
29
|
+
}>>;
|
|
30
|
+
/** Milliseconds before the skeleton shows. */
|
|
31
|
+
readonly delay: Readonly<Readonly<{
|
|
32
|
+
get(): number;
|
|
33
|
+
set(setter: number | ((prevState: number) => number)): void;
|
|
34
|
+
}>>;
|
|
35
|
+
readonly errorHeading: Readonly<Readonly<{
|
|
36
|
+
get(): string;
|
|
37
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
38
|
+
}>>;
|
|
39
|
+
readonly errorText: Readonly<Readonly<{
|
|
40
|
+
get(): string;
|
|
41
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
42
|
+
}>>;
|
|
43
|
+
readonly retryLabel: Readonly<Readonly<{
|
|
44
|
+
get(): string;
|
|
45
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
46
|
+
}>>;
|
|
47
|
+
private readonly _work;
|
|
48
|
+
private readonly _refs;
|
|
49
|
+
/**
|
|
50
|
+
* Fetches the data and draws it. The first time, and after a failure, the skeleton shows while it comes; later
|
|
51
|
+
* (a refresh), what is shown stays until the new data is there, and stays too if it fails. Resolves to whether it
|
|
52
|
+
* worked: a refresh that fails says so only here, for the page to tell people.
|
|
53
|
+
*/
|
|
54
|
+
load<Value>(task: LoadTask<Value>): Promise<boolean>;
|
|
55
|
+
templateHtml(): Node;
|
|
56
|
+
templateCss(): string;
|
|
57
|
+
onConnect(): void;
|
|
58
|
+
onDisconnect(): void;
|
|
59
|
+
private renderState;
|
|
60
|
+
private renderSkeleton;
|
|
61
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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 { define } from "../lib/define.js";
|
|
5
|
+
import { registerIcons } from "../lib/icons.js";
|
|
6
|
+
import { JlButton } from "../atoms/jl-button.js";
|
|
7
|
+
import { JlSkeleton } from "../atoms/jl-skeleton.js";
|
|
8
|
+
import { JlEmpty } from "./jl-empty.js";
|
|
9
|
+
import jl_load_state_default from "./jl-load-state.css_raw.js";
|
|
10
|
+
import { css } from "@jadis/core";
|
|
11
|
+
import { CircleAlert, RefreshCw } from "lucide";
|
|
12
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
13
|
+
// src/molecules/jl-load-state.tsx
|
|
14
|
+
registerIcons({
|
|
15
|
+
"circle-alert": CircleAlert,
|
|
16
|
+
refresh: RefreshCw
|
|
17
|
+
});
|
|
18
|
+
define(JlEmpty);
|
|
19
|
+
var LOAD_STATES = [
|
|
20
|
+
"loading",
|
|
21
|
+
"ready",
|
|
22
|
+
"empty",
|
|
23
|
+
"error"
|
|
24
|
+
];
|
|
25
|
+
var LOAD_SKELETONS = [
|
|
26
|
+
"rows",
|
|
27
|
+
"cards",
|
|
28
|
+
"lines"
|
|
29
|
+
];
|
|
30
|
+
var DEFAULT_ERROR_HEADING = "This could not be loaded";
|
|
31
|
+
var DEFAULT_ERROR_TEXT = "Check the connection, then try again. Nothing was changed.";
|
|
32
|
+
var DEFAULT_RETRY_LABEL = "Try again";
|
|
33
|
+
var DEFAULT_DELAY_MS = 300;
|
|
34
|
+
var MAX_DELAY_MS = 5e3;
|
|
35
|
+
var DEFAULT_COUNT = 3;
|
|
36
|
+
var MAX_COUNT = 24;
|
|
37
|
+
var LINE_WIDTHS = [
|
|
38
|
+
"100%",
|
|
39
|
+
"92%",
|
|
40
|
+
"96%",
|
|
41
|
+
"70%"
|
|
42
|
+
];
|
|
43
|
+
/** The skeleton of each shape, `count` times. */
|
|
44
|
+
var SKELETONS = {
|
|
45
|
+
cards: (count) => Array.from({ length: count }, () => /* @__PURE__ */ jsxs("div", {
|
|
46
|
+
class: "bone-card",
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx(JlSkeleton, {
|
|
49
|
+
shape: "rect",
|
|
50
|
+
height: "120px"
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ jsx(JlSkeleton, { width: "70%" }),
|
|
53
|
+
/* @__PURE__ */ jsx(JlSkeleton, { width: "45%" })
|
|
54
|
+
]
|
|
55
|
+
})),
|
|
56
|
+
lines: (count) => Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsx(JlSkeleton, { width: LINE_WIDTHS[index % LINE_WIDTHS.length] })),
|
|
57
|
+
rows: (count) => Array.from({ length: count }, () => /* @__PURE__ */ jsxs("div", {
|
|
58
|
+
class: "bone-row",
|
|
59
|
+
children: [/* @__PURE__ */ jsx(JlSkeleton, {
|
|
60
|
+
shape: "circle",
|
|
61
|
+
width: "32px",
|
|
62
|
+
height: "32px"
|
|
63
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
64
|
+
class: "bone-lines",
|
|
65
|
+
children: [/* @__PURE__ */ jsx(JlSkeleton, { width: "60%" }), /* @__PURE__ */ jsx(JlSkeleton, { width: "35%" })]
|
|
66
|
+
})]
|
|
67
|
+
}))
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* What a list or a page shows while its data comes: nothing for a moment, then a skeleton shaped like it; then the
|
|
71
|
+
* data (the default slot), what to say when there is none (slot `empty`, a `jl-empty`), or, when it failed, what
|
|
72
|
+
* failed and Try again. `load()` fetches, draws and moves between the states; a later `load()` keeps what is shown
|
|
73
|
+
* until the new data is there, so live updates do not flash. Or set `state` yourself.
|
|
74
|
+
*/
|
|
75
|
+
var JlLoadState = class extends JlElement {
|
|
76
|
+
static selector = "jl-load-state";
|
|
77
|
+
events = this.useEvents();
|
|
78
|
+
state = this.useChange("loading", (value) => this.renderState(value), {
|
|
79
|
+
attribute: "state",
|
|
80
|
+
parse: oneOf(LOAD_STATES, "loading")
|
|
81
|
+
});
|
|
82
|
+
/** The shape of the skeleton; slot `loading` takes one of the page's own instead. */
|
|
83
|
+
skeleton = this.useChange("rows", () => this.renderSkeleton(), {
|
|
84
|
+
attribute: "skeleton",
|
|
85
|
+
parse: oneOf(LOAD_SKELETONS, "rows")
|
|
86
|
+
});
|
|
87
|
+
/** How many rows, cards or lines the skeleton has. */
|
|
88
|
+
count = this.useChange(DEFAULT_COUNT, () => this.renderSkeleton(), {
|
|
89
|
+
attribute: "count",
|
|
90
|
+
parse: clampedNumber(DEFAULT_COUNT, 1, MAX_COUNT)
|
|
91
|
+
});
|
|
92
|
+
/** Milliseconds before the skeleton shows. */
|
|
93
|
+
delay = this.useChange(DEFAULT_DELAY_MS, () => void 0, {
|
|
94
|
+
attribute: "delay",
|
|
95
|
+
parse: clampedNumber(DEFAULT_DELAY_MS, 0, MAX_DELAY_MS)
|
|
96
|
+
});
|
|
97
|
+
errorHeading = this.useChange(DEFAULT_ERROR_HEADING, (value) => this._refs.error.heading.set(value), { attribute: "error-heading" });
|
|
98
|
+
errorText = this.useChange(DEFAULT_ERROR_TEXT, (value) => this._refs.error.description.set(value), { attribute: "error-text" });
|
|
99
|
+
retryLabel = this.useChange(DEFAULT_RETRY_LABEL, (value) => this._refs.retry.label.set(value), { attribute: "retry-label" });
|
|
100
|
+
_work = {};
|
|
101
|
+
_refs = this.useRefs((ref) => ({
|
|
102
|
+
bones: ref(".bones"),
|
|
103
|
+
content: ref(".content"),
|
|
104
|
+
empty: ref(".empty"),
|
|
105
|
+
error: ref("jl-empty"),
|
|
106
|
+
loading: ref(".loading"),
|
|
107
|
+
retry: ref(".retry")
|
|
108
|
+
}));
|
|
109
|
+
/**
|
|
110
|
+
* Fetches the data and draws it. The first time, and after a failure, the skeleton shows while it comes; later
|
|
111
|
+
* (a refresh), what is shown stays until the new data is there, and stays too if it fails. Resolves to whether it
|
|
112
|
+
* worked: a refresh that fails says so only here, for the page to tell people.
|
|
113
|
+
*/
|
|
114
|
+
async load(task) {
|
|
115
|
+
this._work.retry = () => this.load(task);
|
|
116
|
+
const showing = this.state.get() === "ready" || this.state.get() === "empty";
|
|
117
|
+
if (!showing) this.state.set("loading");
|
|
118
|
+
try {
|
|
119
|
+
const value = await task.fetch();
|
|
120
|
+
task.draw(value);
|
|
121
|
+
this.state.set(task.isEmpty?.(value) === true ? "empty" : "ready");
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
if (!showing) this.state.set("error");
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
templateHtml() {
|
|
129
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
130
|
+
class: "load-state",
|
|
131
|
+
attrs: { part: "base" },
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ jsx("div", {
|
|
134
|
+
class: "loading",
|
|
135
|
+
hidden: true,
|
|
136
|
+
"aria-busy": "true",
|
|
137
|
+
children: /* @__PURE__ */ jsx("slot", {
|
|
138
|
+
name: "loading",
|
|
139
|
+
children: /* @__PURE__ */ jsx("div", { class: "bones" })
|
|
140
|
+
})
|
|
141
|
+
}),
|
|
142
|
+
/* @__PURE__ */ jsx("jl-empty", {
|
|
143
|
+
class: "error",
|
|
144
|
+
icon: "circle-alert",
|
|
145
|
+
heading: DEFAULT_ERROR_HEADING,
|
|
146
|
+
description: DEFAULT_ERROR_TEXT,
|
|
147
|
+
hidden: true,
|
|
148
|
+
children: /* @__PURE__ */ jsx(JlButton, {
|
|
149
|
+
class: "retry",
|
|
150
|
+
icon: "refresh",
|
|
151
|
+
label: DEFAULT_RETRY_LABEL
|
|
152
|
+
})
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ jsx("div", {
|
|
155
|
+
class: "empty",
|
|
156
|
+
hidden: true,
|
|
157
|
+
children: /* @__PURE__ */ jsx("slot", { name: "empty" })
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ jsx("div", {
|
|
160
|
+
class: "content",
|
|
161
|
+
hidden: true,
|
|
162
|
+
children: /* @__PURE__ */ jsx("slot", {})
|
|
163
|
+
})
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
templateCss() {
|
|
168
|
+
return css`${BASE_STYLES}${jl_load_state_default}`;
|
|
169
|
+
}
|
|
170
|
+
onConnect() {
|
|
171
|
+
this.renderSkeleton();
|
|
172
|
+
this.renderState(this.state.get());
|
|
173
|
+
this._refs.retry.events.register("press", () => {
|
|
174
|
+
this.events.emit("retry");
|
|
175
|
+
this._work.retry?.();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
onDisconnect() {
|
|
179
|
+
clearTimeout(this._work.timer);
|
|
180
|
+
}
|
|
181
|
+
renderState(state) {
|
|
182
|
+
const refs = this._refs;
|
|
183
|
+
clearTimeout(this._work.timer);
|
|
184
|
+
refs.loading.hidden = true;
|
|
185
|
+
refs.error.hidden = state !== "error";
|
|
186
|
+
refs.empty.hidden = state !== "empty";
|
|
187
|
+
refs.content.hidden = state !== "ready";
|
|
188
|
+
this.reflect("state", state);
|
|
189
|
+
if (state === "loading") this._work.timer = setTimeout(() => {
|
|
190
|
+
refs.loading.hidden = false;
|
|
191
|
+
}, this.delay.get());
|
|
192
|
+
}
|
|
193
|
+
renderSkeleton() {
|
|
194
|
+
const kind = this.skeleton.get();
|
|
195
|
+
this._refs.bones.className = `bones ${kind}`;
|
|
196
|
+
this._refs.bones.replaceChildren(...SKELETONS[kind](this.count.get()));
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
JlLoadState.register();
|
|
200
|
+
export { JlLoadState, LOAD_SKELETONS, LOAD_STATES };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/molecules/jl-search-button.css?raw
|
|
2
|
+
var jl_search_button_default = ":host {\n display: block;\n}\n\n.search {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: var(--jl-control-sm);\n padding: 0 6px 0 10px;\n border: 1px solid var(--jl-line);\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface);\n color: var(--jl-muted);\n font-size: var(--jl-font-sm);\n cursor: pointer;\n transition: border-color var(--jl-fast) var(--jl-ease);\n}\n\n.search:hover {\n border-color: var(--jl-line-strong);\n color: var(--jl-text-2);\n}\n\n.text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-align: left;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Folded: the icon alone, as square as a control. */\n:host([compact]) {\n display: inline-block;\n}\n\n:host([compact]) .search {\n justify-content: center;\n width: var(--jl-control-sm);\n padding: 0;\n}\n\n:host([compact]) .text,\n:host([compact]) jl-kbd {\n display: none;\n}\n\n/* Touch screens have no keys to press. */\n@media (pointer: coarse) {\n jl-kbd {\n display: none;\n }\n}\n";
|
|
3
|
+
export { jl_search_button_default as default };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
/**
|
|
3
|
+
* What opens an app's command palette: it looks like a search field, with the palette's keys (Ctrl K, ⌘K on a
|
|
4
|
+
* Mac). `compact` keeps the icon alone, for a folded side nav or a phone's top bar. Listen to `press`; the keys are
|
|
5
|
+
* the palette's own (`jl-command-palette` listens to them), the button only shows them.
|
|
6
|
+
*/
|
|
7
|
+
export declare class JlSearchButton extends JlElement {
|
|
8
|
+
static readonly selector = "jl-search-button";
|
|
9
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
10
|
+
press: undefined;
|
|
11
|
+
}>>;
|
|
12
|
+
/** The text, and the button's name: "Search". */
|
|
13
|
+
readonly label: Readonly<Readonly<{
|
|
14
|
+
get(): string;
|
|
15
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
16
|
+
}>>;
|
|
17
|
+
/** The keys that open the palette, as `jl-kbd` writes them: `mod+k`; `none` for no keys. */
|
|
18
|
+
readonly hotkey: Readonly<Readonly<{
|
|
19
|
+
get(): string;
|
|
20
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
21
|
+
}>>;
|
|
22
|
+
/** The icon alone, the label as its tooltip. */
|
|
23
|
+
readonly compact: Readonly<Readonly<{
|
|
24
|
+
get(): boolean;
|
|
25
|
+
set(setter: boolean | ((prevState: boolean) => boolean)): void;
|
|
26
|
+
}>>;
|
|
27
|
+
private readonly _refs;
|
|
28
|
+
focus(options?: FocusOptions): void;
|
|
29
|
+
templateHtml(): Node;
|
|
30
|
+
templateCss(): string;
|
|
31
|
+
onConnect(): void;
|
|
32
|
+
private renderLabel;
|
|
33
|
+
private renderHotkey;
|
|
34
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../styles/shared.js";
|
|
2
|
+
import { JlElement } from "../base/jl-element.js";
|
|
3
|
+
import { registerIcons } from "../lib/icons.js";
|
|
4
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
5
|
+
import { JlIcon } from "../atoms/jl-icon.js";
|
|
6
|
+
import { JlKbd } from "../atoms/jl-kbd.js";
|
|
7
|
+
import { parseShortcut, shortcutAria } from "../lib/key-shortcut.js";
|
|
8
|
+
import jl_search_button_default from "./jl-search-button.css_raw.js";
|
|
9
|
+
import { css } from "@jadis/core";
|
|
10
|
+
import { Search } from "lucide";
|
|
11
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
12
|
+
// src/molecules/jl-search-button.tsx
|
|
13
|
+
registerIcons({ search: Search });
|
|
14
|
+
var DEFAULT_LABEL = "Search";
|
|
15
|
+
var DEFAULT_HOTKEY = "mod+k";
|
|
16
|
+
var NO_HOTKEY = "none";
|
|
17
|
+
var ICON_SIZE = 15;
|
|
18
|
+
var MAC_PATTERN = /Mac|iPhone|iPad|iPod/;
|
|
19
|
+
var isMac = () => MAC_PATTERN.test(navigator.userAgent);
|
|
20
|
+
/**
|
|
21
|
+
* What opens an app's command palette: it looks like a search field, with the palette's keys (Ctrl K, ⌘K on a
|
|
22
|
+
* Mac). `compact` keeps the icon alone, for a folded side nav or a phone's top bar. Listen to `press`; the keys are
|
|
23
|
+
* the palette's own (`jl-command-palette` listens to them), the button only shows them.
|
|
24
|
+
*/
|
|
25
|
+
var JlSearchButton = class extends JlElement {
|
|
26
|
+
static selector = "jl-search-button";
|
|
27
|
+
events = this.useEvents();
|
|
28
|
+
/** The text, and the button's name: "Search". */
|
|
29
|
+
label = this.useChange(DEFAULT_LABEL, () => this.renderLabel(), { attribute: "label" });
|
|
30
|
+
/** The keys that open the palette, as `jl-kbd` writes them: `mod+k`; `none` for no keys. */
|
|
31
|
+
hotkey = this.useChange(DEFAULT_HOTKEY, () => this.renderHotkey(), { attribute: "hotkey" });
|
|
32
|
+
/** The icon alone, the label as its tooltip. */
|
|
33
|
+
compact = this.useChange(false, (value) => this.reflect("compact", value), { attribute: "compact" });
|
|
34
|
+
_refs = this.useRefs((ref) => ({
|
|
35
|
+
button: ref("button"),
|
|
36
|
+
keys: ref("jl-kbd"),
|
|
37
|
+
text: ref(".text")
|
|
38
|
+
}));
|
|
39
|
+
focus(options) {
|
|
40
|
+
this._refs.button.focus(options);
|
|
41
|
+
}
|
|
42
|
+
templateHtml() {
|
|
43
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
44
|
+
type: "button",
|
|
45
|
+
class: "search",
|
|
46
|
+
attrs: { part: "base" },
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx(JlIcon, {
|
|
49
|
+
name: "search",
|
|
50
|
+
size: ICON_SIZE
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { class: "text" }),
|
|
53
|
+
/* @__PURE__ */ jsx(JlKbd, {})
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
templateCss() {
|
|
58
|
+
return css`${BASE_STYLES}${jl_search_button_default}`;
|
|
59
|
+
}
|
|
60
|
+
onConnect() {
|
|
61
|
+
this.renderLabel();
|
|
62
|
+
this.renderHotkey();
|
|
63
|
+
this.on(this._refs.button, "click", () => this.events.emit("press"));
|
|
64
|
+
}
|
|
65
|
+
renderLabel() {
|
|
66
|
+
const label = this.label.get() || DEFAULT_LABEL;
|
|
67
|
+
this._refs.text.textContent = label;
|
|
68
|
+
this._refs.button.setAttribute("aria-label", label);
|
|
69
|
+
this._refs.button.title = label;
|
|
70
|
+
}
|
|
71
|
+
renderHotkey() {
|
|
72
|
+
const text = this.hotkey.get();
|
|
73
|
+
const shortcut = text === NO_HOTKEY ? null : parseShortcut(text);
|
|
74
|
+
this._refs.keys.keys.set(shortcut === null ? "" : text);
|
|
75
|
+
this._refs.keys.hidden = shortcut === null;
|
|
76
|
+
setOrRemove(this._refs.button, "aria-keyshortcuts", shortcut === null ? "" : shortcutAria(shortcut, isMac()));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
JlSearchButton.register();
|
|
80
|
+
export { JlSearchButton };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JlElement } from '../base/jl-element';
|
|
2
|
-
import type { ConfirmOptions, ConfirmTone } from '../types/jl-confirm.types';
|
|
2
|
+
import type { ConfirmOptions, ConfirmTone, LeaveGuardOptions } from '../types/jl-confirm.types';
|
|
3
3
|
export declare const CONFIRM_TONES: readonly ConfirmTone[];
|
|
4
4
|
/**
|
|
5
5
|
* A yes-or-no question in a small modal dialog. `ask()` opens it and resolves `true` on confirm, `false` on cancel,
|
|
@@ -54,3 +54,9 @@ export declare class JlConfirm extends JlElement {
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
export declare function confirmDialog(options: ConfirmOptions): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Guards a form or an editor against leaving with changes not saved. Closing or reloading the tab asks the
|
|
59
|
+
* browser's own question; the function it returns asks in the page, for the app's router to call before it moves
|
|
60
|
+
* on: `if (!(await canLeave())) return;`. It resolves `true` at once when nothing would be lost.
|
|
61
|
+
*/
|
|
62
|
+
export declare function guardLeave(options: LeaveGuardOptions): () => Promise<boolean>;
|
|
@@ -129,4 +129,25 @@ function confirmDialog(options) {
|
|
|
129
129
|
document.body.append(element);
|
|
130
130
|
return element.ask().finally(() => element.remove());
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
var LEAVE_HEADING = "Leave without saving?";
|
|
133
|
+
var LEAVE_TEXT = "Your changes are lost.";
|
|
134
|
+
var LEAVE_LABEL = "Leave";
|
|
135
|
+
var STAY_LABEL = "Keep editing";
|
|
136
|
+
/**
|
|
137
|
+
* Guards a form or an editor against leaving with changes not saved. Closing or reloading the tab asks the
|
|
138
|
+
* browser's own question; the function it returns asks in the page, for the app's router to call before it moves
|
|
139
|
+
* on: `if (!(await canLeave())) return;`. It resolves `true` at once when nothing would be lost.
|
|
140
|
+
*/
|
|
141
|
+
function guardLeave(options) {
|
|
142
|
+
window.addEventListener("beforeunload", (event) => {
|
|
143
|
+
if (options.isDirty()) event.preventDefault();
|
|
144
|
+
}, { signal: options.signal });
|
|
145
|
+
return async () => !options.isDirty() || confirmDialog({
|
|
146
|
+
cancelLabel: options.stayLabel ?? STAY_LABEL,
|
|
147
|
+
confirmLabel: options.leaveLabel ?? LEAVE_LABEL,
|
|
148
|
+
heading: options.heading ?? LEAVE_HEADING,
|
|
149
|
+
text: options.text ?? LEAVE_TEXT,
|
|
150
|
+
tone: "danger"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
export { CONFIRM_TONES, JlConfirm, confirmDialog, guardLeave };
|
|
@@ -24,7 +24,10 @@ export declare class JlToaster extends JlElement {
|
|
|
24
24
|
}>>;
|
|
25
25
|
private readonly _watched;
|
|
26
26
|
private readonly _refs;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Shows a notification. It stays 5 seconds unless `duration` says otherwise, 8 for an error or with an action;
|
|
29
|
+
* it has a close button unless told not to.
|
|
30
|
+
*/
|
|
28
31
|
notify(options: ToastOptions): ToastHandle;
|
|
29
32
|
templateHtml(): Node;
|
|
30
33
|
templateCss(): string;
|
|
@@ -16,8 +16,12 @@ var TOASTER_PLACEMENTS = [
|
|
|
16
16
|
var DEFAULT_MAX = 3;
|
|
17
17
|
var MAX_LIMIT = 20;
|
|
18
18
|
var DEFAULT_DURATION = 5e3;
|
|
19
|
+
var LONG_DURATION = 8e3;
|
|
20
|
+
var LONG_TONE = "danger";
|
|
19
21
|
var DEFAULT_LABEL = "Notifications";
|
|
20
22
|
var STACKED = "data-stacked";
|
|
23
|
+
/** How long a toast stays when its options do not say: longer for an error, or to give time to press its action. */
|
|
24
|
+
var defaultDuration = (options) => options.tone === LONG_TONE || (options.actionLabel ?? "") !== "" ? LONG_DURATION : DEFAULT_DURATION;
|
|
21
25
|
var OPEN = ":popover-open";
|
|
22
26
|
/**
|
|
23
27
|
* The corner of the window where notifications stack, newest nearest the edge, at most `max` at a time. It lives in
|
|
@@ -42,14 +46,17 @@ var JlToaster = class extends JlElement {
|
|
|
42
46
|
region: ref(".region"),
|
|
43
47
|
slot: ref("slot")
|
|
44
48
|
}));
|
|
45
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Shows a notification. It stays 5 seconds unless `duration` says otherwise, 8 for an error or with an action;
|
|
51
|
+
* it has a close button unless told not to.
|
|
52
|
+
*/
|
|
46
53
|
notify(options) {
|
|
47
54
|
const toast = /* @__PURE__ */ jsx(JlToast, {
|
|
48
55
|
heading: options.heading,
|
|
49
56
|
text: options.text,
|
|
50
57
|
tone: options.tone,
|
|
51
58
|
icon: options.icon,
|
|
52
|
-
duration: options.duration ??
|
|
59
|
+
duration: options.duration ?? defaultDuration(options),
|
|
53
60
|
closable: options.closable ?? true,
|
|
54
61
|
actionLabel: options.actionLabel
|
|
55
62
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/styles/control.css?raw
|
|
2
|
-
var control_default = "/* The look of the fields people type in: text fields, selects and areas share it. */\n.control {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n min-height: var(--height, var(--jl-control-md));\n padding: 0 12px;\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface);\n color: var(--jl-text);\n font-size: var(--jl-font
|
|
2
|
+
var control_default = "/* The look of the fields people type in: text fields, selects and areas share it. */\n.control {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n min-height: var(--height, var(--jl-control-md));\n padding: 0 12px;\n border: 1px solid var(--jl-line-strong);\n border-radius: var(--jl-radius-sm);\n background: var(--jl-surface);\n color: var(--jl-text);\n font-size: var(--jl-control-font);\n line-height: 20px;\n transition:\n border-color var(--jl-fast) var(--jl-ease),\n box-shadow var(--jl-fast) var(--jl-ease);\n}\n\n.control:hover {\n border-color: color-mix(in srgb, var(--jl-line-strong) 60%, var(--jl-text) 18%);\n}\n\n.control:focus-within {\n border-color: var(--jl-accent);\n box-shadow: var(--jl-focus-ring);\n}\n\n:host([invalid]) .control {\n border-color: var(--jl-danger);\n}\n\n:host([invalid]) .control:focus-within {\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--jl-danger) 25%, transparent);\n}\n\n:host([disabled]) .control,\n:host(:disabled) .control {\n background: var(--jl-surface-2);\n color: var(--jl-muted);\n cursor: not-allowed;\n}\n\n:host([size=\"sm\"]) {\n --height: var(--jl-control-sm);\n}\n\n:host([size=\"lg\"]) {\n --height: var(--jl-control-lg);\n}\n\n/* The native element inside the frame: it takes the room and leaves the frame to draw the border. */\n.native {\n flex: 1;\n min-width: 0;\n height: calc(var(--height, var(--jl-control-md)) - 2px);\n padding: 0;\n border: none;\n outline: none;\n background: transparent;\n color: inherit;\n font: inherit;\n}\n\n.native::placeholder {\n color: var(--jl-faint);\n}\n\n.native:focus-visible {\n box-shadow: none;\n}\n\n.native:disabled {\n cursor: not-allowed;\n}\n\n.adornment {\n display: inline-flex;\n flex: none;\n align-items: center;\n color: var(--jl-faint);\n}\n\n/* A small button inside the frame: clear, reveal the password, step a number. */\n.action {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n margin-right: -4px;\n padding: 0;\n border: none;\n border-radius: var(--jl-radius-xs);\n background: none;\n color: var(--jl-faint);\n cursor: pointer;\n}\n\n.action:hover {\n background: var(--jl-surface-3);\n color: var(--jl-text);\n}\n";
|
|
3
3
|
export { control_default as default };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/styles/tokens.css?raw
|
|
2
|
-
var tokens_default = "/*\n * The design tokens, as swai and swcode have them: ink and white, flat surfaces separated by lines, and one blue\n * that marks what is live, selected or waiting for a person. Custom properties cross shadow roots, so every\n * component reads them. They sit in the `jlib` layer: any unlayered rule of the page overrides them.\n */\n@layer jlib {\n :root,\n [data-theme=\"light\"] {\n color-scheme: light;\n --jl-font-sans: \"Manrope Variable\", \"Manrope\", system-ui, -apple-system, \"Segoe UI\", sans-serif;\n --jl-font-mono: \"JetBrains Mono Variable\", \"JetBrains Mono\", ui-monospace, \"SF Mono\", Menlo, monospace;\n\n --jl-font-xs: 12px;\n --jl-font-sm: 13px;\n --jl-font-md: 14px;\n --jl-font-lg: 16px;\n --jl-font-xl: 20px;\n --jl-font-2xl: 28px;\n\n --jl-text: #0b0b0c;\n --jl-text-2: #3a3f47;\n /* Secondary text: 6.2:1 on white. */\n --jl-muted: #5b616b;\n /* Icons and decorations only: 3.2:1 on white is enough for them, not for text. */\n --jl-faint: #8a9099;\n\n --jl-bg: #ffffff;\n --jl-surface: #ffffff;\n --jl-surface-2: #f7f8f9;\n --jl-surface-3: #eef0f2;\n --jl-code-bg: #f0f2f4;\n --jl-line: #e6e8eb;\n --jl-line-soft: #eef0f2;\n --jl-line-strong: #d5d9de;\n\n /* The SyWare logo's blue: marks, focus rings, progress. 3.8:1 on white: enough for shapes, never for text. */\n --jl-brand: #0e88d3;\n /* The same hue, deep enough to carry text: buttons, links, the selection. 5.2:1 on white. */\n --jl-accent: #0c72b0;\n --jl-accent-strong: #095a8b;\n --jl-accent-soft: #e9f4fb;\n --jl-accent-line: #b7dbf2;\n --jl-on-accent: #ffffff;\n\n --jl-ok: #17784a;\n --jl-ok-soft: #ecf6f0;\n --jl-warn: #9a5b00;\n --jl-warn-soft: #fcf4e6;\n --jl-danger: #b42318;\n --jl-danger-soft: #fdf0ef;\n --jl-on-danger: #ffffff;\n --jl-info: #0c72b0;\n --jl-info-soft: #e9f4fb;\n\n /* Code: the accent for keywords, quiet colours from the same family for the rest. */\n --jl-code-keyword: #0c72b0;\n --jl-code-tag: #095a8b;\n --jl-code-attribute: #5b48c8;\n --jl-code-string: #17784a;\n --jl-code-number: #9a5b00;\n --jl-code-function: #3d5a80;\n --jl-code-property: #127a72;\n --jl-code-comment: #8a9099;\n\n --jl-radius-xs: 6px;\n --jl-radius-sm: 8px;\n --jl-radius: 10px;\n --jl-radius-lg: 12px;\n --jl-radius-xl: 20px;\n --jl-radius-pill: 999px;\n\n /* Spacing: a 4px grid, for the layouts pages build around the components. */\n --jl-space-1: 4px;\n --jl-space-2: 8px;\n --jl-space-3: 12px;\n --jl-space-4: 16px;\n --jl-space-5: 20px;\n --jl-space-6: 24px;\n --jl-space-8: 32px;\n --jl-space-10: 40px;\n --jl-space-12: 48px;\n --jl-space-16: 64px;\n\n --jl-control-sm: 30px;\n --jl-control-md: 36px;\n --jl-control-lg: 44px;\n\n /* Flat: lines separate; only what floats (menus, dialogs, toasts) casts a shadow. */\n --jl-shadow-sm: 0 1px 2px rgb(11 11 12 / 0.05);\n --jl-shadow: 0 6px 16px -6px rgb(11 11 12 / 0.12);\n --jl-shadow-lg: 0 18px 44px -14px rgb(11 11 12 / 0.22);\n --jl-focus-ring: 0 0 0 3px color-mix(in srgb, var(--jl-brand) 35%, transparent);\n --jl-scrim: rgb(11 11 12 / 0.38);\n\n --jl-ease: cubic-bezier(0.2, 0.7, 0.2, 1);\n --jl-fast: 120ms;\n --jl-slow: 240ms;\n }\n\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n color-scheme: dark;\n --jl-text: #eceef1;\n --jl-text-2: #c3c8cf;\n --jl-muted: #959ca6;\n --jl-faint: #6b727c;\n --jl-bg: #0f1113;\n --jl-surface: #0f1113;\n --jl-surface-2: #16191c;\n --jl-surface-3: #1d2125;\n --jl-code-bg: #1d2125;\n --jl-line: #262a2f;\n --jl-line-soft: #1f2327;\n --jl-line-strong: #343a41;\n --jl-brand: #1a9be0;\n --jl-accent: #3ea6e8;\n --jl-accent-strong: #6cc0f2;\n --jl-accent-soft: #0f2a3b;\n --jl-accent-line: #1e4a66;\n --jl-on-accent: #04131c;\n --jl-ok: #4cc38a;\n --jl-ok-soft: #10261c;\n --jl-warn: #e5a54b;\n --jl-warn-soft: #2a2012;\n --jl-danger: #f0786c;\n --jl-danger-soft: #3a1a17;\n --jl-on-danger: #1c0604;\n --jl-info: #3ea6e8;\n --jl-info-soft: #0f2a3b;\n --jl-code-keyword: #3ea6e8;\n --jl-code-tag: #6cc0f2;\n --jl-code-attribute: #a99bf0;\n --jl-code-string: #4cc38a;\n --jl-code-number: #e5a54b;\n --jl-code-function: #8fb0d6;\n --jl-code-property: #4fc1b5;\n --jl-code-comment: #6b727c;\n --jl-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);\n --jl-shadow: 0 6px 18px -6px rgb(0 0 0 / 0.6);\n --jl-shadow-lg: 0 18px 44px -12px rgb(0 0 0 / 0.7);\n --jl-scrim: rgb(0 0 0 / 0.55);\n }\n }\n\n /* Dark: the same blue, lifted to read on a dark ground, with dark text on the buttons. */\n [data-theme=\"dark\"] {\n color-scheme: dark;\n --jl-text: #eceef1;\n --jl-text-2: #c3c8cf;\n --jl-muted: #959ca6;\n --jl-faint: #6b727c;\n --jl-bg: #0f1113;\n --jl-surface: #0f1113;\n --jl-surface-2: #16191c;\n --jl-surface-3: #1d2125;\n --jl-code-bg: #1d2125;\n --jl-line: #262a2f;\n --jl-line-soft: #1f2327;\n --jl-line-strong: #343a41;\n --jl-brand: #1a9be0;\n --jl-accent: #3ea6e8;\n --jl-accent-strong: #6cc0f2;\n --jl-accent-soft: #0f2a3b;\n --jl-accent-line: #1e4a66;\n --jl-on-accent: #04131c;\n --jl-ok: #4cc38a;\n --jl-ok-soft: #10261c;\n --jl-warn: #e5a54b;\n --jl-warn-soft: #2a2012;\n --jl-danger: #f0786c;\n --jl-danger-soft: #3a1a17;\n --jl-on-danger: #1c0604;\n --jl-info: #3ea6e8;\n --jl-info-soft: #0f2a3b;\n --jl-code-keyword: #3ea6e8;\n --jl-code-tag: #6cc0f2;\n --jl-code-attribute: #a99bf0;\n --jl-code-string: #4cc38a;\n --jl-code-number: #e5a54b;\n --jl-code-function: #8fb0d6;\n --jl-code-property: #4fc1b5;\n --jl-code-comment: #6b727c;\n --jl-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);\n --jl-shadow: 0 6px 18px -6px rgb(0 0 0 / 0.6);\n --jl-shadow-lg: 0 18px 44px -12px rgb(0 0 0 / 0.7);\n --jl-scrim: rgb(0 0 0 / 0.55);\n }\n}\n";
|
|
2
|
+
var tokens_default = "/*\n * The design tokens, as swai and swcode have them: ink and white, flat surfaces separated by lines, and one blue\n * that marks what is live, selected or waiting for a person. Custom properties cross shadow roots, so every\n * component reads them. They sit in the `jlib` layer: any unlayered rule of the page overrides them.\n */\n@layer jlib {\n :root,\n [data-theme=\"light\"] {\n color-scheme: light;\n --jl-font-sans: \"Manrope Variable\", \"Manrope\", system-ui, -apple-system, \"Segoe UI\", sans-serif;\n --jl-font-mono: \"JetBrains Mono Variable\", \"JetBrains Mono\", ui-monospace, \"SF Mono\", Menlo, monospace;\n\n --jl-font-xs: 12px;\n --jl-font-sm: 13px;\n --jl-font-md: 14px;\n --jl-font-lg: 16px;\n --jl-font-xl: 20px;\n --jl-font-2xl: 28px;\n\n --jl-text: #0b0b0c;\n --jl-text-2: #3a3f47;\n /* Secondary text: 6.2:1 on white. */\n --jl-muted: #5b616b;\n /* Icons and decorations only: 3.2:1 on white is enough for them, not for text. */\n --jl-faint: #8a9099;\n\n --jl-bg: #ffffff;\n --jl-surface: #ffffff;\n --jl-surface-2: #f7f8f9;\n --jl-surface-3: #eef0f2;\n --jl-code-bg: #f0f2f4;\n --jl-line: #e6e8eb;\n --jl-line-soft: #eef0f2;\n --jl-line-strong: #d5d9de;\n\n /* The SyWare logo's blue: marks, focus rings, progress. 3.8:1 on white: enough for shapes, never for text. */\n --jl-brand: #0e88d3;\n /* The same hue, deep enough to carry text: buttons, links, the selection. 5.2:1 on white. */\n --jl-accent: #0c72b0;\n --jl-accent-strong: #095a8b;\n --jl-accent-soft: #e9f4fb;\n --jl-accent-line: #b7dbf2;\n --jl-on-accent: #ffffff;\n\n --jl-ok: #17784a;\n --jl-ok-soft: #ecf6f0;\n --jl-warn: #9a5b00;\n --jl-warn-soft: #fcf4e6;\n --jl-danger: #b42318;\n --jl-danger-soft: #fdf0ef;\n --jl-on-danger: #ffffff;\n --jl-info: #0c72b0;\n --jl-info-soft: #e9f4fb;\n\n /* Code: the accent for keywords, quiet colours from the same family for the rest. */\n --jl-code-keyword: #0c72b0;\n --jl-code-tag: #095a8b;\n --jl-code-attribute: #5b48c8;\n --jl-code-string: #17784a;\n --jl-code-number: #9a5b00;\n --jl-code-function: #3d5a80;\n --jl-code-property: #127a72;\n --jl-code-comment: #8a9099;\n\n --jl-radius-xs: 6px;\n --jl-radius-sm: 8px;\n --jl-radius: 10px;\n --jl-radius-lg: 12px;\n --jl-radius-xl: 20px;\n --jl-radius-pill: 999px;\n\n /* Spacing: a 4px grid, for the layouts pages build around the components. */\n --jl-space-1: 4px;\n --jl-space-2: 8px;\n --jl-space-3: 12px;\n --jl-space-4: 16px;\n --jl-space-5: 20px;\n --jl-space-6: 24px;\n --jl-space-8: 32px;\n --jl-space-10: 40px;\n --jl-space-12: 48px;\n --jl-space-16: 64px;\n\n --jl-control-sm: 30px;\n --jl-control-md: 36px;\n --jl-control-lg: 44px;\n /* The text people type in fields. */\n --jl-control-font: var(--jl-font-md);\n\n /* The widest a page gets: a form, most pages, and the tables and grids that need the room. */\n --jl-page-form: 720px;\n --jl-page-standard: 1120px;\n --jl-page-wide: 1400px;\n\n /* Flat: lines separate; only what floats (menus, dialogs, toasts) casts a shadow. */\n --jl-shadow-sm: 0 1px 2px rgb(11 11 12 / 0.05);\n --jl-shadow: 0 6px 16px -6px rgb(11 11 12 / 0.12);\n --jl-shadow-lg: 0 18px 44px -14px rgb(11 11 12 / 0.22);\n --jl-focus-ring: 0 0 0 3px color-mix(in srgb, var(--jl-brand) 35%, transparent);\n --jl-scrim: rgb(11 11 12 / 0.38);\n\n --jl-ease: cubic-bezier(0.2, 0.7, 0.2, 1);\n --jl-fast: 120ms;\n --jl-slow: 240ms;\n }\n\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n color-scheme: dark;\n --jl-text: #eceef1;\n --jl-text-2: #c3c8cf;\n --jl-muted: #959ca6;\n --jl-faint: #6b727c;\n --jl-bg: #0f1113;\n --jl-surface: #0f1113;\n --jl-surface-2: #16191c;\n --jl-surface-3: #1d2125;\n --jl-code-bg: #1d2125;\n --jl-line: #262a2f;\n --jl-line-soft: #1f2327;\n --jl-line-strong: #343a41;\n --jl-brand: #1a9be0;\n --jl-accent: #3ea6e8;\n --jl-accent-strong: #6cc0f2;\n --jl-accent-soft: #0f2a3b;\n --jl-accent-line: #1e4a66;\n --jl-on-accent: #04131c;\n --jl-ok: #4cc38a;\n --jl-ok-soft: #10261c;\n --jl-warn: #e5a54b;\n --jl-warn-soft: #2a2012;\n --jl-danger: #f0786c;\n --jl-danger-soft: #3a1a17;\n --jl-on-danger: #1c0604;\n --jl-info: #3ea6e8;\n --jl-info-soft: #0f2a3b;\n --jl-code-keyword: #3ea6e8;\n --jl-code-tag: #6cc0f2;\n --jl-code-attribute: #a99bf0;\n --jl-code-string: #4cc38a;\n --jl-code-number: #e5a54b;\n --jl-code-function: #8fb0d6;\n --jl-code-property: #4fc1b5;\n --jl-code-comment: #6b727c;\n --jl-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);\n --jl-shadow: 0 6px 18px -6px rgb(0 0 0 / 0.6);\n --jl-shadow-lg: 0 18px 44px -12px rgb(0 0 0 / 0.7);\n --jl-scrim: rgb(0 0 0 / 0.55);\n }\n }\n\n /* Dark: the same blue, lifted to read on a dark ground, with dark text on the buttons. */\n [data-theme=\"dark\"] {\n color-scheme: dark;\n --jl-text: #eceef1;\n --jl-text-2: #c3c8cf;\n --jl-muted: #959ca6;\n --jl-faint: #6b727c;\n --jl-bg: #0f1113;\n --jl-surface: #0f1113;\n --jl-surface-2: #16191c;\n --jl-surface-3: #1d2125;\n --jl-code-bg: #1d2125;\n --jl-line: #262a2f;\n --jl-line-soft: #1f2327;\n --jl-line-strong: #343a41;\n --jl-brand: #1a9be0;\n --jl-accent: #3ea6e8;\n --jl-accent-strong: #6cc0f2;\n --jl-accent-soft: #0f2a3b;\n --jl-accent-line: #1e4a66;\n --jl-on-accent: #04131c;\n --jl-ok: #4cc38a;\n --jl-ok-soft: #10261c;\n --jl-warn: #e5a54b;\n --jl-warn-soft: #2a2012;\n --jl-danger: #f0786c;\n --jl-danger-soft: #3a1a17;\n --jl-on-danger: #1c0604;\n --jl-info: #3ea6e8;\n --jl-info-soft: #0f2a3b;\n --jl-code-keyword: #3ea6e8;\n --jl-code-tag: #6cc0f2;\n --jl-code-attribute: #a99bf0;\n --jl-code-string: #4cc38a;\n --jl-code-number: #e5a54b;\n --jl-code-function: #8fb0d6;\n --jl-code-property: #4fc1b5;\n --jl-code-comment: #6b727c;\n --jl-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);\n --jl-shadow: 0 6px 18px -6px rgb(0 0 0 / 0.6);\n --jl-shadow-lg: 0 18px 44px -12px rgb(0 0 0 / 0.7);\n --jl-scrim: rgb(0 0 0 / 0.55);\n }\n\n /*\n * Touch screens: controls a finger can hit (44px for most), and field text phones do not zoom into when it takes\n * the focus (16px). Themed parts of a page re-declare the sizes, so they take these too.\n */\n @media (pointer: coarse) {\n :root,\n [data-theme=\"light\"],\n [data-theme=\"dark\"] {\n --jl-control-sm: 36px;\n --jl-control-md: 44px;\n --jl-control-lg: 48px;\n --jl-control-font: 16px;\n }\n }\n}\n";
|
|
3
3
|
export { tokens_default as default };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// src/templates/jl-page.css?raw
|
|
2
|
+
var jl_page_default = ":host {\n display: block;\n container-type: inline-size;\n --page-width: var(--jl-page-standard);\n}\n\n:host([width=\"form\"]) {\n --page-width: var(--jl-page-form);\n}\n\n:host([width=\"wide\"]) {\n --page-width: var(--jl-page-wide);\n}\n\n:host([width=\"full\"]) {\n --page-width: none;\n}\n\n.page {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: var(--jl-space-6);\n max-width: var(--page-width);\n margin: 0 auto;\n padding: var(--jl-space-8) var(--jl-space-10) var(--jl-space-12);\n}\n\n/* A grid's items grow to their content otherwise: a row of chips would widen the page. */\n::slotted(*) {\n min-width: 0;\n}\n\n.back {\n margin-left: -8px;\n}\n\n@container (max-width: 720px) {\n .page {\n padding: var(--jl-space-6) var(--jl-space-4) var(--jl-space-12);\n }\n}\n";
|
|
3
|
+
export { jl_page_default as default };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JlElement } from '../base/jl-element';
|
|
2
|
+
import type { PageWidth } from '../types/jl-page.types';
|
|
3
|
+
export declare const PAGE_WIDTHS: readonly PageWidth[];
|
|
4
|
+
/**
|
|
5
|
+
* A page of an app: its header (a back link named after the page it leads to, the heading, a sentence on what the
|
|
6
|
+
* page is for or the facts that identify its subject, the status, the actions), then the content, centred at one of
|
|
7
|
+
* four widths. With `app-name`, it also names the browser tab after its heading: "Order #3 · swstock".
|
|
8
|
+
*/
|
|
9
|
+
export declare class JlPage extends JlElement {
|
|
10
|
+
static readonly selector = "jl-page";
|
|
11
|
+
readonly events: Readonly<import("@jadis/core").UseEventsHandler<{
|
|
12
|
+
back: undefined;
|
|
13
|
+
}>>;
|
|
14
|
+
readonly heading: Readonly<Readonly<{
|
|
15
|
+
get(): string;
|
|
16
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
17
|
+
}>>;
|
|
18
|
+
readonly description: Readonly<Readonly<{
|
|
19
|
+
get(): string;
|
|
20
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
21
|
+
}>>;
|
|
22
|
+
/** `form` for one column of fields, `standard` for most pages, `wide` for tables and grids, `full` for the rest. */
|
|
23
|
+
readonly width: Readonly<Readonly<{
|
|
24
|
+
get(): PageWidth;
|
|
25
|
+
set(setter: ((prevState: PageWidth) => PageWidth) | PageWidth): void;
|
|
26
|
+
}>>;
|
|
27
|
+
/** Where the back link leads; no back link without it. */
|
|
28
|
+
readonly back: Readonly<Readonly<{
|
|
29
|
+
get(): string;
|
|
30
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
31
|
+
}>>;
|
|
32
|
+
/** The name of the page the back link leads to: "Orders". */
|
|
33
|
+
readonly backLabel: Readonly<Readonly<{
|
|
34
|
+
get(): string;
|
|
35
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
36
|
+
}>>;
|
|
37
|
+
/** The app's name, after the heading in the browser tab. Without it, the page leaves the tab's title alone. */
|
|
38
|
+
readonly appName: Readonly<Readonly<{
|
|
39
|
+
get(): string;
|
|
40
|
+
set(setter: string | ((prevState: string) => string)): void;
|
|
41
|
+
}>>;
|
|
42
|
+
private readonly _refs;
|
|
43
|
+
templateHtml(): Node;
|
|
44
|
+
templateCss(): string;
|
|
45
|
+
onConnect(): void;
|
|
46
|
+
/** The back link stays out of the header's breadcrumb slot without an address, so the header takes no room for it. */
|
|
47
|
+
private renderBack;
|
|
48
|
+
private renderTitle;
|
|
49
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { define } from "../lib/define.js";
|
|
5
|
+
import { registerIcons } from "../lib/icons.js";
|
|
6
|
+
import { setOrRemove } from "../lib/dom.js";
|
|
7
|
+
import { JlButton } from "../atoms/jl-button.js";
|
|
8
|
+
import { JlPageHeader } from "../organisms/jl-page-header.js";
|
|
9
|
+
import jl_page_default from "./jl-page.css_raw.js";
|
|
10
|
+
import { css } from "@jadis/core";
|
|
11
|
+
import { ArrowLeft } from "lucide";
|
|
12
|
+
import { jsx, jsxs } from "@jadis/core/jsx-runtime";
|
|
13
|
+
// src/templates/jl-page.tsx
|
|
14
|
+
registerIcons({ "arrow-left": ArrowLeft });
|
|
15
|
+
define(JlPageHeader);
|
|
16
|
+
var PAGE_WIDTHS = [
|
|
17
|
+
"standard",
|
|
18
|
+
"form",
|
|
19
|
+
"wide",
|
|
20
|
+
"full"
|
|
21
|
+
];
|
|
22
|
+
var BREADCRUMB_SLOT = "breadcrumb";
|
|
23
|
+
var DEFAULT_BACK_LABEL = "Back";
|
|
24
|
+
var TITLE_SEPARATOR = " · ";
|
|
25
|
+
/**
|
|
26
|
+
* A page of an app: its header (a back link named after the page it leads to, the heading, a sentence on what the
|
|
27
|
+
* page is for or the facts that identify its subject, the status, the actions), then the content, centred at one of
|
|
28
|
+
* four widths. With `app-name`, it also names the browser tab after its heading: "Order #3 · swstock".
|
|
29
|
+
*/
|
|
30
|
+
var JlPage = class extends JlElement {
|
|
31
|
+
static selector = "jl-page";
|
|
32
|
+
events = this.useEvents();
|
|
33
|
+
heading = this.useChange("", (value) => {
|
|
34
|
+
this._refs.header.heading.set(value);
|
|
35
|
+
this.renderTitle();
|
|
36
|
+
}, { attribute: "heading" });
|
|
37
|
+
description = this.useChange("", (value) => this._refs.header.description.set(value), { attribute: "description" });
|
|
38
|
+
/** `form` for one column of fields, `standard` for most pages, `wide` for tables and grids, `full` for the rest. */
|
|
39
|
+
width = this.useChange("standard", (value) => this.reflect("width", value), {
|
|
40
|
+
attribute: "width",
|
|
41
|
+
parse: oneOf(PAGE_WIDTHS, "standard")
|
|
42
|
+
});
|
|
43
|
+
/** Where the back link leads; no back link without it. */
|
|
44
|
+
back = this.useChange("", () => this.renderBack(), { attribute: "back" });
|
|
45
|
+
/** The name of the page the back link leads to: "Orders". */
|
|
46
|
+
backLabel = this.useChange("", () => this.renderBack(), { attribute: "back-label" });
|
|
47
|
+
/** The app's name, after the heading in the browser tab. Without it, the page leaves the tab's title alone. */
|
|
48
|
+
appName = this.useChange("", () => this.renderTitle(), { attribute: "app-name" });
|
|
49
|
+
_refs = this.useRefs((ref) => ({
|
|
50
|
+
back: ref(".back"),
|
|
51
|
+
header: ref("jl-page-header")
|
|
52
|
+
}));
|
|
53
|
+
templateHtml() {
|
|
54
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
55
|
+
class: "page",
|
|
56
|
+
attrs: { part: "base" },
|
|
57
|
+
children: [/* @__PURE__ */ jsxs("jl-page-header", { children: [
|
|
58
|
+
/* @__PURE__ */ jsx(JlButton, {
|
|
59
|
+
class: "back",
|
|
60
|
+
variant: "ghost",
|
|
61
|
+
size: "sm",
|
|
62
|
+
icon: "arrow-left"
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ jsx("slot", {
|
|
65
|
+
name: "breadcrumb",
|
|
66
|
+
slot: "breadcrumb"
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ jsx("slot", {
|
|
69
|
+
name: "actions",
|
|
70
|
+
slot: "actions"
|
|
71
|
+
}),
|
|
72
|
+
/* @__PURE__ */ jsx("slot", {
|
|
73
|
+
name: "meta",
|
|
74
|
+
slot: "meta"
|
|
75
|
+
})
|
|
76
|
+
] }), /* @__PURE__ */ jsx("slot", {})]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
templateCss() {
|
|
80
|
+
return css`${BASE_STYLES}${jl_page_default}`;
|
|
81
|
+
}
|
|
82
|
+
onConnect() {
|
|
83
|
+
this.renderTitle();
|
|
84
|
+
this._refs.back.events.register("press", () => this.events.emit("back"));
|
|
85
|
+
}
|
|
86
|
+
/** The back link stays out of the header's breadcrumb slot without an address, so the header takes no room for it. */
|
|
87
|
+
renderBack() {
|
|
88
|
+
const back = this._refs.back;
|
|
89
|
+
const href = this.back.get();
|
|
90
|
+
back.href.set(href);
|
|
91
|
+
back.label.set(this.backLabel.get() || DEFAULT_BACK_LABEL);
|
|
92
|
+
setOrRemove(back, "slot", href === "" ? "" : BREADCRUMB_SLOT);
|
|
93
|
+
}
|
|
94
|
+
renderTitle() {
|
|
95
|
+
const app = this.appName.get();
|
|
96
|
+
if (app === "" || !this.isConnected) return;
|
|
97
|
+
const heading = this.heading.get();
|
|
98
|
+
document.title = heading === "" ? app : `${heading}${TITLE_SEPARATOR}${app}`;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
JlPage.register();
|
|
102
|
+
export { JlPage, PAGE_WIDTHS };
|
package/dist/tokens.css
CHANGED
|
@@ -84,6 +84,13 @@
|
|
|
84
84
|
--jl-control-sm: 30px;
|
|
85
85
|
--jl-control-md: 36px;
|
|
86
86
|
--jl-control-lg: 44px;
|
|
87
|
+
/* The text people type in fields. */
|
|
88
|
+
--jl-control-font: var(--jl-font-md);
|
|
89
|
+
|
|
90
|
+
/* The widest a page gets: a form, most pages, and the tables and grids that need the room. */
|
|
91
|
+
--jl-page-form: 720px;
|
|
92
|
+
--jl-page-standard: 1120px;
|
|
93
|
+
--jl-page-wide: 1400px;
|
|
87
94
|
|
|
88
95
|
/* Flat: lines separate; only what floats (menus, dialogs, toasts) casts a shadow. */
|
|
89
96
|
--jl-shadow-sm: 0 1px 2px rgb(11 11 12 / 0.05);
|
|
@@ -185,4 +192,19 @@
|
|
|
185
192
|
--jl-shadow-lg: 0 18px 44px -12px rgb(0 0 0 / 0.7);
|
|
186
193
|
--jl-scrim: rgb(0 0 0 / 0.55);
|
|
187
194
|
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* Touch screens: controls a finger can hit (44px for most), and field text phones do not zoom into when it takes
|
|
198
|
+
* the focus (16px). Themed parts of a page re-declare the sizes, so they take these too.
|
|
199
|
+
*/
|
|
200
|
+
@media (pointer: coarse) {
|
|
201
|
+
:root,
|
|
202
|
+
[data-theme="light"],
|
|
203
|
+
[data-theme="dark"] {
|
|
204
|
+
--jl-control-sm: 36px;
|
|
205
|
+
--jl-control-md: 44px;
|
|
206
|
+
--jl-control-lg: 48px;
|
|
207
|
+
--jl-control-font: 16px;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
188
210
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Tone } from './options.types';
|
|
2
|
+
/**
|
|
3
|
+
* How one state of something shows as a badge: its word, its tone, its icon, and a sentence on what it means. One
|
|
4
|
+
* exhaustive map per kind of thing (`Record<OrderStatus, StatusLook>`) keeps every view saying a state the same way.
|
|
5
|
+
*/
|
|
6
|
+
export type StatusLook = {
|
|
7
|
+
label: string;
|
|
8
|
+
tone: Tone;
|
|
9
|
+
icon?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
};
|
|
@@ -8,3 +8,18 @@ export type ConfirmOptions = {
|
|
|
8
8
|
cancelLabel?: string;
|
|
9
9
|
tone?: ConfirmTone;
|
|
10
10
|
};
|
|
11
|
+
/** What `guardLeave()` watches, and what it asks, in the app's words. */
|
|
12
|
+
export type LeaveGuardOptions = {
|
|
13
|
+
/** Whether leaving now would lose changes. */
|
|
14
|
+
isDirty: () => boolean;
|
|
15
|
+
/** Ends the guard, such as the killSignal of the page it guards. */
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
/** "Leave without saving?" */
|
|
18
|
+
heading?: string;
|
|
19
|
+
/** "Your changes are lost." */
|
|
20
|
+
text?: string;
|
|
21
|
+
/** "Leave" */
|
|
22
|
+
leaveLabel?: string;
|
|
23
|
+
/** "Keep editing" */
|
|
24
|
+
stayLabel?: string;
|
|
25
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Where the data of a list or a page is: on its way, shown, there but empty, or failed to come. */
|
|
2
|
+
export type LoadState = 'loading' | 'ready' | 'empty' | 'error';
|
|
3
|
+
/** The shape the skeleton takes while the data comes: rows of a list, cards of a grid, or lines of text. */
|
|
4
|
+
export type LoadSkeleton = 'rows' | 'cards' | 'lines';
|
|
5
|
+
/** What `load()` needs: how to fetch the data, how to draw it, and whether it holds nothing to show. */
|
|
6
|
+
export type LoadTask<Value> = {
|
|
7
|
+
fetch: () => Promise<Value>;
|
|
8
|
+
draw: (value: Value) => void;
|
|
9
|
+
isEmpty?: (value: Value) => boolean;
|
|
10
|
+
};
|
|
@@ -8,7 +8,7 @@ export type ToastOptions = {
|
|
|
8
8
|
tone?: ToastTone;
|
|
9
9
|
/** An icon name; the tone's icon when absent. */
|
|
10
10
|
icon?: string;
|
|
11
|
-
/** How long it stays, in milliseconds; 0 keeps it until closed. */
|
|
11
|
+
/** How long it stays, in milliseconds: 5000, or 8000 for an error or with an action; 0 keeps it until closed. */
|
|
12
12
|
duration?: number;
|
|
13
13
|
/** Shows a close button; on by default. */
|
|
14
14
|
closable?: boolean;
|