@f-ewald/components 1.11.0 → 1.13.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/README.md +7 -0
- package/custom-elements.json +1651 -115
- package/dist/auto-scroll.d.ts +37 -0
- package/dist/auto-scroll.d.ts.map +1 -0
- package/dist/auto-scroll.js +100 -0
- package/dist/auto-scroll.js.map +1 -0
- package/dist/form-field.d.ts +39 -0
- package/dist/form-field.d.ts.map +1 -0
- package/dist/form-field.js +145 -0
- package/dist/form-field.js.map +1 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +1 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/load-more.d.ts +33 -0
- package/dist/load-more.d.ts.map +1 -0
- package/dist/load-more.js +82 -0
- package/dist/load-more.js.map +1 -0
- package/dist/percent-bar-chart.d.ts +32 -3
- package/dist/percent-bar-chart.d.ts.map +1 -1
- package/dist/percent-bar-chart.js +101 -36
- package/dist/percent-bar-chart.js.map +1 -1
- package/dist/scroll-to-bottom.d.ts +41 -0
- package/dist/scroll-to-bottom.d.ts.map +1 -0
- package/dist/scroll-to-bottom.js +189 -0
- package/dist/scroll-to-bottom.js.map +1 -0
- package/dist/scroll-to-top.d.ts +41 -0
- package/dist/scroll-to-top.d.ts.map +1 -0
- package/dist/scroll-to-top.js +189 -0
- package/dist/scroll-to-top.js.map +1 -0
- package/dist/tree-view.d.ts +44 -0
- package/dist/tree-view.d.ts.map +1 -0
- package/dist/tree-view.js +200 -0
- package/dist/tree-view.js.map +1 -0
- package/dist/ui-checkbox.d.ts +49 -0
- package/dist/ui-checkbox.d.ts.map +1 -0
- package/dist/ui-checkbox.js +228 -0
- package/dist/ui-checkbox.js.map +1 -0
- package/dist/utils/scroll.d.ts +9 -0
- package/dist/utils/scroll.d.ts.map +1 -0
- package/dist/utils/scroll.js +30 -0
- package/dist/utils/scroll.js.map +1 -0
- package/docs/auto-scroll.md +52 -0
- package/docs/form-field.md +63 -0
- package/docs/layouts/form-page.md +8 -4
- package/docs/load-more.md +43 -0
- package/docs/percent-bar-chart.md +19 -5
- package/docs/scroll-to-bottom.md +68 -0
- package/docs/scroll-to-top.md +59 -0
- package/docs/tree-view.md +69 -0
- package/docs/ui-checkbox.md +58 -0
- package/llms.txt +223 -6
- package/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html } from "lit";
|
|
8
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
9
|
+
import { iconChevronDown } from "./icons.js";
|
|
10
|
+
import { distanceFromBottom, scrollToEdge } from "./utils/scroll.js";
|
|
11
|
+
import { tokens } from "./tokens.js";
|
|
12
|
+
/**
|
|
13
|
+
* Overlay button that appears once the page (or a given `target` container)
|
|
14
|
+
* has scrolled more than `threshold` pixels away from the bottom edge, and
|
|
15
|
+
* scrolls back to the bottom on click.
|
|
16
|
+
*
|
|
17
|
+
* With no `target` (default), the button is `position: fixed` to the
|
|
18
|
+
* viewport. When `target` is set, the button switches to `position:
|
|
19
|
+
* absolute` and expects to be placed as a descendant of a `position:
|
|
20
|
+
* relative` (or otherwise positioned) ancestor that establishes the visual
|
|
21
|
+
* bounds to float within — typically `target` itself, given `overflow-y:
|
|
22
|
+
* auto; position: relative`, so the button stays pinned to that container's
|
|
23
|
+
* own visible corner as its content scrolls, rather than floating over the
|
|
24
|
+
* whole page.
|
|
25
|
+
*
|
|
26
|
+
* @element scroll-to-bottom
|
|
27
|
+
* @fires scroll-to-bottom-triggered - The button was clicked, just before
|
|
28
|
+
* scrolling; detail: `{ target }`.
|
|
29
|
+
*/
|
|
30
|
+
let ScrollToBottom = class ScrollToBottom extends LitElement {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
/** Scrollable container to control; `null` (default) scrolls `window`. */
|
|
34
|
+
this.target = null;
|
|
35
|
+
/** Pixels scrolled away from the bottom before the button appears. */
|
|
36
|
+
this.threshold = 200;
|
|
37
|
+
/** Visible button text, and its accessible name. */
|
|
38
|
+
this.label = "Scroll to bottom";
|
|
39
|
+
this._visible = false;
|
|
40
|
+
this.#listenedTo = null;
|
|
41
|
+
this.#onScroll = () => {
|
|
42
|
+
this._visible = distanceFromBottom(this.target ?? window) > this.threshold;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
static { this.styles = [
|
|
46
|
+
tokens,
|
|
47
|
+
css `
|
|
48
|
+
:host {
|
|
49
|
+
position: fixed;
|
|
50
|
+
bottom: 0.75rem;
|
|
51
|
+
right: 0.75rem;
|
|
52
|
+
z-index: 40;
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
opacity: 0;
|
|
55
|
+
visibility: hidden;
|
|
56
|
+
transition:
|
|
57
|
+
opacity 150ms ease,
|
|
58
|
+
visibility 0s linear 150ms;
|
|
59
|
+
}
|
|
60
|
+
:host([contained]) {
|
|
61
|
+
position: absolute;
|
|
62
|
+
}
|
|
63
|
+
:host([visible]) {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
visibility: visible;
|
|
66
|
+
transition:
|
|
67
|
+
opacity 150ms ease,
|
|
68
|
+
visibility 0s linear 0s;
|
|
69
|
+
}
|
|
70
|
+
button {
|
|
71
|
+
pointer-events: auto;
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 0.25rem;
|
|
75
|
+
height: 2rem;
|
|
76
|
+
box-sizing: border-box;
|
|
77
|
+
padding: 0 0.75rem;
|
|
78
|
+
color: var(--ui-text, #0f172a);
|
|
79
|
+
background: var(--ui-surface, #ffffff);
|
|
80
|
+
border: 1px solid var(--ui-border, #e2e8f0);
|
|
81
|
+
border-radius: 999px;
|
|
82
|
+
box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
font-family: var(
|
|
85
|
+
--ui-font,
|
|
86
|
+
ui-sans-serif,
|
|
87
|
+
system-ui,
|
|
88
|
+
sans-serif,
|
|
89
|
+
"Apple Color Emoji",
|
|
90
|
+
"Segoe UI Emoji",
|
|
91
|
+
"Segoe UI Symbol",
|
|
92
|
+
"Noto Color Emoji"
|
|
93
|
+
);
|
|
94
|
+
font-size: var(--ui-font-size-sm, 0.75rem);
|
|
95
|
+
font-weight: var(--ui-font-weight-medium, 500);
|
|
96
|
+
line-height: var(--ui-line-height-tight, 1.25);
|
|
97
|
+
white-space: nowrap;
|
|
98
|
+
}
|
|
99
|
+
button:hover {
|
|
100
|
+
background: var(--ui-surface-muted, #f8fafc);
|
|
101
|
+
}
|
|
102
|
+
button:focus-visible {
|
|
103
|
+
outline: none;
|
|
104
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
105
|
+
}
|
|
106
|
+
@media (prefers-reduced-motion: reduce) {
|
|
107
|
+
:host {
|
|
108
|
+
transition: none;
|
|
109
|
+
}
|
|
110
|
+
:host([visible]) {
|
|
111
|
+
transition: none;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
@media (forced-colors: active) {
|
|
115
|
+
button:focus-visible {
|
|
116
|
+
outline: 2px solid CanvasText;
|
|
117
|
+
outline-offset: 2px;
|
|
118
|
+
box-shadow: none;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`,
|
|
122
|
+
]; }
|
|
123
|
+
#listenedTo;
|
|
124
|
+
connectedCallback() {
|
|
125
|
+
super.connectedCallback();
|
|
126
|
+
this.#subscribe();
|
|
127
|
+
}
|
|
128
|
+
disconnectedCallback() {
|
|
129
|
+
super.disconnectedCallback();
|
|
130
|
+
this.#unsubscribe();
|
|
131
|
+
}
|
|
132
|
+
willUpdate(changed) {
|
|
133
|
+
if (changed.has("target")) {
|
|
134
|
+
this.toggleAttribute("contained", this.target !== null);
|
|
135
|
+
// Resubscribing here (not in `updated()`) lets `#onScroll()`'s
|
|
136
|
+
// `_visible` write fold into the update already in progress instead of
|
|
137
|
+
// scheduling a new one. Guarded against the initial update, where
|
|
138
|
+
// `target` is always in `changed` too, but `connectedCallback` already
|
|
139
|
+
// subscribed to it.
|
|
140
|
+
if (this.#listenedTo !== (this.target ?? window))
|
|
141
|
+
this.#subscribe();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
updated(changed) {
|
|
145
|
+
if (changed.has("_visible"))
|
|
146
|
+
this.toggleAttribute("visible", this._visible);
|
|
147
|
+
}
|
|
148
|
+
#subscribe() {
|
|
149
|
+
this.#unsubscribe();
|
|
150
|
+
this.#listenedTo = this.target ?? window;
|
|
151
|
+
this.#listenedTo.addEventListener("scroll", this.#onScroll, { passive: true });
|
|
152
|
+
this.#onScroll();
|
|
153
|
+
}
|
|
154
|
+
#unsubscribe() {
|
|
155
|
+
this.#listenedTo?.removeEventListener("scroll", this.#onScroll);
|
|
156
|
+
this.#listenedTo = null;
|
|
157
|
+
}
|
|
158
|
+
#onScroll;
|
|
159
|
+
#onClick() {
|
|
160
|
+
const target = this.target ?? window;
|
|
161
|
+
this.dispatchEvent(new CustomEvent("scroll-to-bottom-triggered", { detail: { target }, bubbles: true, composed: true }));
|
|
162
|
+
scrollToEdge(target, "bottom");
|
|
163
|
+
}
|
|
164
|
+
render() {
|
|
165
|
+
return html `
|
|
166
|
+
<button type="button" @click=${() => this.#onClick()}>
|
|
167
|
+
${iconChevronDown(14)}
|
|
168
|
+
<span>${this.label}</span>
|
|
169
|
+
</button>
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
__decorate([
|
|
174
|
+
property({ attribute: false })
|
|
175
|
+
], ScrollToBottom.prototype, "target", void 0);
|
|
176
|
+
__decorate([
|
|
177
|
+
property({ type: Number })
|
|
178
|
+
], ScrollToBottom.prototype, "threshold", void 0);
|
|
179
|
+
__decorate([
|
|
180
|
+
property()
|
|
181
|
+
], ScrollToBottom.prototype, "label", void 0);
|
|
182
|
+
__decorate([
|
|
183
|
+
state()
|
|
184
|
+
], ScrollToBottom.prototype, "_visible", void 0);
|
|
185
|
+
ScrollToBottom = __decorate([
|
|
186
|
+
customElement("scroll-to-bottom")
|
|
187
|
+
], ScrollToBottom);
|
|
188
|
+
export { ScrollToBottom };
|
|
189
|
+
//# sourceMappingURL=scroll-to-bottom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-to-bottom.js","sourceRoot":"","sources":["../src/scroll-to-bottom.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconChevronDown } from \"./icons.js\";\nimport { distanceFromBottom, scrollToEdge } from \"./utils/scroll.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Overlay button that appears once the page (or a given `target` container)\n * has scrolled more than `threshold` pixels away from the bottom edge, and\n * scrolls back to the bottom on click.\n *\n * With no `target` (default), the button is `position: fixed` to the\n * viewport. When `target` is set, the button switches to `position:\n * absolute` and expects to be placed as a descendant of a `position:\n * relative` (or otherwise positioned) ancestor that establishes the visual\n * bounds to float within — typically `target` itself, given `overflow-y:\n * auto; position: relative`, so the button stays pinned to that container's\n * own visible corner as its content scrolls, rather than floating over the\n * whole page.\n *\n * @element scroll-to-bottom\n * @fires scroll-to-bottom-triggered - The button was clicked, just before\n * scrolling; detail: `{ target }`.\n */\n@customElement(\"scroll-to-bottom\")\nexport class ScrollToBottom extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n position: fixed;\n bottom: 0.75rem;\n right: 0.75rem;\n z-index: 40;\n pointer-events: none;\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 150ms ease,\n visibility 0s linear 150ms;\n }\n :host([contained]) {\n position: absolute;\n }\n :host([visible]) {\n opacity: 1;\n visibility: visible;\n transition:\n opacity 150ms ease,\n visibility 0s linear 0s;\n }\n button {\n pointer-events: auto;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n height: 2rem;\n box-sizing: border-box;\n padding: 0 0.75rem;\n color: var(--ui-text, #0f172a);\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: 999px;\n box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));\n cursor: pointer;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: var(--ui-font-weight-medium, 500);\n line-height: var(--ui-line-height-tight, 1.25);\n white-space: nowrap;\n }\n button:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (prefers-reduced-motion: reduce) {\n :host {\n transition: none;\n }\n :host([visible]) {\n transition: none;\n }\n }\n @media (forced-colors: active) {\n button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n /** Scrollable container to control; `null` (default) scrolls `window`. */\n @property({ attribute: false }) target: HTMLElement | null = null;\n /** Pixels scrolled away from the bottom before the button appears. */\n @property({ type: Number }) threshold = 200;\n /** Visible button text, and its accessible name. */\n @property() label = \"Scroll to bottom\";\n\n @state() private _visible = false;\n\n #listenedTo: HTMLElement | Window | null = null;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.#subscribe();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#unsubscribe();\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n if (changed.has(\"target\")) {\n this.toggleAttribute(\"contained\", this.target !== null);\n // Resubscribing here (not in `updated()`) lets `#onScroll()`'s\n // `_visible` write fold into the update already in progress instead of\n // scheduling a new one. Guarded against the initial update, where\n // `target` is always in `changed` too, but `connectedCallback` already\n // subscribed to it.\n if (this.#listenedTo !== (this.target ?? window)) this.#subscribe();\n }\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"_visible\")) this.toggleAttribute(\"visible\", this._visible);\n }\n\n #subscribe(): void {\n this.#unsubscribe();\n this.#listenedTo = this.target ?? window;\n this.#listenedTo.addEventListener(\"scroll\", this.#onScroll, { passive: true });\n this.#onScroll();\n }\n\n #unsubscribe(): void {\n this.#listenedTo?.removeEventListener(\"scroll\", this.#onScroll);\n this.#listenedTo = null;\n }\n\n #onScroll = (): void => {\n this._visible = distanceFromBottom(this.target ?? window) > this.threshold;\n };\n\n #onClick(): void {\n const target = this.target ?? window;\n this.dispatchEvent(\n new CustomEvent(\"scroll-to-bottom-triggered\", { detail: { target }, bubbles: true, composed: true }),\n );\n scrollToEdge(target, \"bottom\");\n }\n\n override render() {\n return html`\n <button type=\"button\" @click=${() => this.#onClick()}>\n ${iconChevronDown(14)}\n <span>${this.label}</span>\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"scroll-to-bottom\": ScrollToBottom;\n }\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues } from "lit";
|
|
2
|
+
/**
|
|
3
|
+
* Overlay button that appears once the page (or a given `target` container)
|
|
4
|
+
* has scrolled more than `threshold` pixels away from the top edge, and
|
|
5
|
+
* scrolls back to the top on click.
|
|
6
|
+
*
|
|
7
|
+
* With no `target` (default), the button is `position: fixed` to the
|
|
8
|
+
* viewport. When `target` is set, the button switches to `position:
|
|
9
|
+
* absolute` and expects to be placed as a descendant of a `position:
|
|
10
|
+
* relative` (or otherwise positioned) ancestor that establishes the visual
|
|
11
|
+
* bounds to float within — typically `target` itself, given `overflow-y:
|
|
12
|
+
* auto; position: relative`, so the button stays pinned to that container's
|
|
13
|
+
* own visible corner as its content scrolls, rather than floating over the
|
|
14
|
+
* whole page.
|
|
15
|
+
*
|
|
16
|
+
* @element scroll-to-top
|
|
17
|
+
* @fires scroll-to-top-triggered - The button was clicked, just before
|
|
18
|
+
* scrolling; detail: `{ target }`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ScrollToTop extends LitElement {
|
|
21
|
+
#private;
|
|
22
|
+
static styles: import("lit").CSSResult[];
|
|
23
|
+
/** Scrollable container to control; `null` (default) scrolls `window`. */
|
|
24
|
+
target: HTMLElement | null;
|
|
25
|
+
/** Pixels scrolled away from the top before the button appears. */
|
|
26
|
+
threshold: number;
|
|
27
|
+
/** Visible button text, and its accessible name. */
|
|
28
|
+
label: string;
|
|
29
|
+
private _visible;
|
|
30
|
+
connectedCallback(): void;
|
|
31
|
+
disconnectedCallback(): void;
|
|
32
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
33
|
+
protected updated(changed: PropertyValues): void;
|
|
34
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
35
|
+
}
|
|
36
|
+
declare global {
|
|
37
|
+
interface HTMLElementTagNameMap {
|
|
38
|
+
"scroll-to-top": ScrollToTop;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=scroll-to-top.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-to-top.d.ts","sourceRoot":"","sources":["../src/scroll-to-top.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAMjE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,WAAY,SAAQ,UAAU;;IACzC,OAAgB,MAAM,4BA6EpB;IAEF,0EAA0E;IAC1C,MAAM,EAAE,WAAW,GAAG,IAAI,CAAQ;IAClE,mEAAmE;IACvC,SAAS,SAAO;IAC5C,oDAAoD;IACxC,KAAK,SAAmB;IAE3B,OAAO,CAAC,QAAQ,CAAS;IAIzB,iBAAiB,IAAI,IAAI,CAGjC;IAEQ,oBAAoB,IAAI,IAAI,CAGpC;IAED,UAAmB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAU3D;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAExD;IA0BQ,MAAM,yCAOd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,WAAW,CAAC;KAC9B;CACF"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html } from "lit";
|
|
8
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
9
|
+
import { iconChevronUp } from "./icons.js";
|
|
10
|
+
import { distanceFromTop, scrollToEdge } from "./utils/scroll.js";
|
|
11
|
+
import { tokens } from "./tokens.js";
|
|
12
|
+
/**
|
|
13
|
+
* Overlay button that appears once the page (or a given `target` container)
|
|
14
|
+
* has scrolled more than `threshold` pixels away from the top edge, and
|
|
15
|
+
* scrolls back to the top on click.
|
|
16
|
+
*
|
|
17
|
+
* With no `target` (default), the button is `position: fixed` to the
|
|
18
|
+
* viewport. When `target` is set, the button switches to `position:
|
|
19
|
+
* absolute` and expects to be placed as a descendant of a `position:
|
|
20
|
+
* relative` (or otherwise positioned) ancestor that establishes the visual
|
|
21
|
+
* bounds to float within — typically `target` itself, given `overflow-y:
|
|
22
|
+
* auto; position: relative`, so the button stays pinned to that container's
|
|
23
|
+
* own visible corner as its content scrolls, rather than floating over the
|
|
24
|
+
* whole page.
|
|
25
|
+
*
|
|
26
|
+
* @element scroll-to-top
|
|
27
|
+
* @fires scroll-to-top-triggered - The button was clicked, just before
|
|
28
|
+
* scrolling; detail: `{ target }`.
|
|
29
|
+
*/
|
|
30
|
+
let ScrollToTop = class ScrollToTop extends LitElement {
|
|
31
|
+
constructor() {
|
|
32
|
+
super(...arguments);
|
|
33
|
+
/** Scrollable container to control; `null` (default) scrolls `window`. */
|
|
34
|
+
this.target = null;
|
|
35
|
+
/** Pixels scrolled away from the top before the button appears. */
|
|
36
|
+
this.threshold = 200;
|
|
37
|
+
/** Visible button text, and its accessible name. */
|
|
38
|
+
this.label = "Scroll to top";
|
|
39
|
+
this._visible = false;
|
|
40
|
+
this.#listenedTo = null;
|
|
41
|
+
this.#onScroll = () => {
|
|
42
|
+
this._visible = distanceFromTop(this.target ?? window) > this.threshold;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
static { this.styles = [
|
|
46
|
+
tokens,
|
|
47
|
+
css `
|
|
48
|
+
:host {
|
|
49
|
+
position: fixed;
|
|
50
|
+
bottom: 0.75rem;
|
|
51
|
+
right: 0.75rem;
|
|
52
|
+
z-index: 40;
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
opacity: 0;
|
|
55
|
+
visibility: hidden;
|
|
56
|
+
transition:
|
|
57
|
+
opacity 150ms ease,
|
|
58
|
+
visibility 0s linear 150ms;
|
|
59
|
+
}
|
|
60
|
+
:host([contained]) {
|
|
61
|
+
position: absolute;
|
|
62
|
+
}
|
|
63
|
+
:host([visible]) {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
visibility: visible;
|
|
66
|
+
transition:
|
|
67
|
+
opacity 150ms ease,
|
|
68
|
+
visibility 0s linear 0s;
|
|
69
|
+
}
|
|
70
|
+
button {
|
|
71
|
+
pointer-events: auto;
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 0.25rem;
|
|
75
|
+
height: 2rem;
|
|
76
|
+
box-sizing: border-box;
|
|
77
|
+
padding: 0 0.75rem;
|
|
78
|
+
color: var(--ui-text, #0f172a);
|
|
79
|
+
background: var(--ui-surface, #ffffff);
|
|
80
|
+
border: 1px solid var(--ui-border, #e2e8f0);
|
|
81
|
+
border-radius: 999px;
|
|
82
|
+
box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
font-family: var(
|
|
85
|
+
--ui-font,
|
|
86
|
+
ui-sans-serif,
|
|
87
|
+
system-ui,
|
|
88
|
+
sans-serif,
|
|
89
|
+
"Apple Color Emoji",
|
|
90
|
+
"Segoe UI Emoji",
|
|
91
|
+
"Segoe UI Symbol",
|
|
92
|
+
"Noto Color Emoji"
|
|
93
|
+
);
|
|
94
|
+
font-size: var(--ui-font-size-sm, 0.75rem);
|
|
95
|
+
font-weight: var(--ui-font-weight-medium, 500);
|
|
96
|
+
line-height: var(--ui-line-height-tight, 1.25);
|
|
97
|
+
white-space: nowrap;
|
|
98
|
+
}
|
|
99
|
+
button:hover {
|
|
100
|
+
background: var(--ui-surface-muted, #f8fafc);
|
|
101
|
+
}
|
|
102
|
+
button:focus-visible {
|
|
103
|
+
outline: none;
|
|
104
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
105
|
+
}
|
|
106
|
+
@media (prefers-reduced-motion: reduce) {
|
|
107
|
+
:host {
|
|
108
|
+
transition: none;
|
|
109
|
+
}
|
|
110
|
+
:host([visible]) {
|
|
111
|
+
transition: none;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
@media (forced-colors: active) {
|
|
115
|
+
button:focus-visible {
|
|
116
|
+
outline: 2px solid CanvasText;
|
|
117
|
+
outline-offset: 2px;
|
|
118
|
+
box-shadow: none;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`,
|
|
122
|
+
]; }
|
|
123
|
+
#listenedTo;
|
|
124
|
+
connectedCallback() {
|
|
125
|
+
super.connectedCallback();
|
|
126
|
+
this.#subscribe();
|
|
127
|
+
}
|
|
128
|
+
disconnectedCallback() {
|
|
129
|
+
super.disconnectedCallback();
|
|
130
|
+
this.#unsubscribe();
|
|
131
|
+
}
|
|
132
|
+
willUpdate(changed) {
|
|
133
|
+
if (changed.has("target")) {
|
|
134
|
+
this.toggleAttribute("contained", this.target !== null);
|
|
135
|
+
// Resubscribing here (not in `updated()`) lets `#onScroll()`'s
|
|
136
|
+
// `_visible` write fold into the update already in progress instead of
|
|
137
|
+
// scheduling a new one. Guarded against the initial update, where
|
|
138
|
+
// `target` is always in `changed` too, but `connectedCallback` already
|
|
139
|
+
// subscribed to it.
|
|
140
|
+
if (this.#listenedTo !== (this.target ?? window))
|
|
141
|
+
this.#subscribe();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
updated(changed) {
|
|
145
|
+
if (changed.has("_visible"))
|
|
146
|
+
this.toggleAttribute("visible", this._visible);
|
|
147
|
+
}
|
|
148
|
+
#subscribe() {
|
|
149
|
+
this.#unsubscribe();
|
|
150
|
+
this.#listenedTo = this.target ?? window;
|
|
151
|
+
this.#listenedTo.addEventListener("scroll", this.#onScroll, { passive: true });
|
|
152
|
+
this.#onScroll();
|
|
153
|
+
}
|
|
154
|
+
#unsubscribe() {
|
|
155
|
+
this.#listenedTo?.removeEventListener("scroll", this.#onScroll);
|
|
156
|
+
this.#listenedTo = null;
|
|
157
|
+
}
|
|
158
|
+
#onScroll;
|
|
159
|
+
#onClick() {
|
|
160
|
+
const target = this.target ?? window;
|
|
161
|
+
this.dispatchEvent(new CustomEvent("scroll-to-top-triggered", { detail: { target }, bubbles: true, composed: true }));
|
|
162
|
+
scrollToEdge(target, "top");
|
|
163
|
+
}
|
|
164
|
+
render() {
|
|
165
|
+
return html `
|
|
166
|
+
<button type="button" @click=${() => this.#onClick()}>
|
|
167
|
+
${iconChevronUp(14)}
|
|
168
|
+
<span>${this.label}</span>
|
|
169
|
+
</button>
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
__decorate([
|
|
174
|
+
property({ attribute: false })
|
|
175
|
+
], ScrollToTop.prototype, "target", void 0);
|
|
176
|
+
__decorate([
|
|
177
|
+
property({ type: Number })
|
|
178
|
+
], ScrollToTop.prototype, "threshold", void 0);
|
|
179
|
+
__decorate([
|
|
180
|
+
property()
|
|
181
|
+
], ScrollToTop.prototype, "label", void 0);
|
|
182
|
+
__decorate([
|
|
183
|
+
state()
|
|
184
|
+
], ScrollToTop.prototype, "_visible", void 0);
|
|
185
|
+
ScrollToTop = __decorate([
|
|
186
|
+
customElement("scroll-to-top")
|
|
187
|
+
], ScrollToTop);
|
|
188
|
+
export { ScrollToTop };
|
|
189
|
+
//# sourceMappingURL=scroll-to-top.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-to-top.js","sourceRoot":"","sources":["../src/scroll-to-top.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconChevronUp } from \"./icons.js\";\nimport { distanceFromTop, scrollToEdge } from \"./utils/scroll.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Overlay button that appears once the page (or a given `target` container)\n * has scrolled more than `threshold` pixels away from the top edge, and\n * scrolls back to the top on click.\n *\n * With no `target` (default), the button is `position: fixed` to the\n * viewport. When `target` is set, the button switches to `position:\n * absolute` and expects to be placed as a descendant of a `position:\n * relative` (or otherwise positioned) ancestor that establishes the visual\n * bounds to float within — typically `target` itself, given `overflow-y:\n * auto; position: relative`, so the button stays pinned to that container's\n * own visible corner as its content scrolls, rather than floating over the\n * whole page.\n *\n * @element scroll-to-top\n * @fires scroll-to-top-triggered - The button was clicked, just before\n * scrolling; detail: `{ target }`.\n */\n@customElement(\"scroll-to-top\")\nexport class ScrollToTop extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n position: fixed;\n bottom: 0.75rem;\n right: 0.75rem;\n z-index: 40;\n pointer-events: none;\n opacity: 0;\n visibility: hidden;\n transition:\n opacity 150ms ease,\n visibility 0s linear 150ms;\n }\n :host([contained]) {\n position: absolute;\n }\n :host([visible]) {\n opacity: 1;\n visibility: visible;\n transition:\n opacity 150ms ease,\n visibility 0s linear 0s;\n }\n button {\n pointer-events: auto;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n height: 2rem;\n box-sizing: border-box;\n padding: 0 0.75rem;\n color: var(--ui-text, #0f172a);\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: 999px;\n box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));\n cursor: pointer;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: var(--ui-font-weight-medium, 500);\n line-height: var(--ui-line-height-tight, 1.25);\n white-space: nowrap;\n }\n button:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (prefers-reduced-motion: reduce) {\n :host {\n transition: none;\n }\n :host([visible]) {\n transition: none;\n }\n }\n @media (forced-colors: active) {\n button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n /** Scrollable container to control; `null` (default) scrolls `window`. */\n @property({ attribute: false }) target: HTMLElement | null = null;\n /** Pixels scrolled away from the top before the button appears. */\n @property({ type: Number }) threshold = 200;\n /** Visible button text, and its accessible name. */\n @property() label = \"Scroll to top\";\n\n @state() private _visible = false;\n\n #listenedTo: HTMLElement | Window | null = null;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.#subscribe();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.#unsubscribe();\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n if (changed.has(\"target\")) {\n this.toggleAttribute(\"contained\", this.target !== null);\n // Resubscribing here (not in `updated()`) lets `#onScroll()`'s\n // `_visible` write fold into the update already in progress instead of\n // scheduling a new one. Guarded against the initial update, where\n // `target` is always in `changed` too, but `connectedCallback` already\n // subscribed to it.\n if (this.#listenedTo !== (this.target ?? window)) this.#subscribe();\n }\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"_visible\")) this.toggleAttribute(\"visible\", this._visible);\n }\n\n #subscribe(): void {\n this.#unsubscribe();\n this.#listenedTo = this.target ?? window;\n this.#listenedTo.addEventListener(\"scroll\", this.#onScroll, { passive: true });\n this.#onScroll();\n }\n\n #unsubscribe(): void {\n this.#listenedTo?.removeEventListener(\"scroll\", this.#onScroll);\n this.#listenedTo = null;\n }\n\n #onScroll = (): void => {\n this._visible = distanceFromTop(this.target ?? window) > this.threshold;\n };\n\n #onClick(): void {\n const target = this.target ?? window;\n this.dispatchEvent(\n new CustomEvent(\"scroll-to-top-triggered\", { detail: { target }, bubbles: true, composed: true }),\n );\n scrollToEdge(target, \"top\");\n }\n\n override render() {\n return html`\n <button type=\"button\" @click=${() => this.#onClick()}>\n ${iconChevronUp(14)}\n <span>${this.label}</span>\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"scroll-to-top\": ScrollToTop;\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
/** One tree node: a folder when `children` is set (even `[]`), a leaf otherwise. */
|
|
3
|
+
export interface TreeNode {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
children?: TreeNode[];
|
|
7
|
+
data?: unknown;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A generic, presentational tree shell: renders `nodes` recursively, one row
|
|
11
|
+
* per node, with each row's content produced by `renderNode` (default: plain
|
|
12
|
+
* label). Modeled on `data-table`'s headless pattern — knows nothing about
|
|
13
|
+
* what a node's `data` means beyond what `renderNode` does with it.
|
|
14
|
+
*
|
|
15
|
+
* A node with a `children` array (even empty) is a folder: clicking or
|
|
16
|
+
* activating its row toggles expand/collapse instead of firing `node-click`.
|
|
17
|
+
* A node with no `children` is a leaf: clicking or activating its row fires
|
|
18
|
+
* `node-click`. Folders start collapsed; set `default-expanded` to start
|
|
19
|
+
* every folder expanded instead. Expansion state is otherwise managed
|
|
20
|
+
* internally and untouched by later `nodes` updates, so a user's manual
|
|
21
|
+
* toggles survive a data refresh.
|
|
22
|
+
*
|
|
23
|
+
* @element tree-view
|
|
24
|
+
* @fires node-click - A leaf row was activated; detail is `{ id, data }`.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TreeView extends LitElement {
|
|
27
|
+
#private;
|
|
28
|
+
static styles: import("lit").CSSResult[];
|
|
29
|
+
/** Tree data; opaque to this component beyond what `renderNode` does with it. */
|
|
30
|
+
nodes: TreeNode[];
|
|
31
|
+
/** Produces a row's rendered content for `node`. Default: plain label text. */
|
|
32
|
+
renderNode: (node: TreeNode) => unknown;
|
|
33
|
+
/** Start every folder expanded instead of the default all-collapsed. */
|
|
34
|
+
defaultExpanded: boolean;
|
|
35
|
+
private expanded;
|
|
36
|
+
willUpdate(changed: Map<string, unknown>): void;
|
|
37
|
+
render(): unknown;
|
|
38
|
+
}
|
|
39
|
+
declare global {
|
|
40
|
+
interface HTMLElementTagNameMap {
|
|
41
|
+
"tree-view": TreeView;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=tree-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-view.d.ts","sourceRoot":"","sources":["../src/tree-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2C,MAAM,KAAK,CAAC;AAM1E,oFAAoF;AACpF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,QAAS,SAAQ,UAAU;;IACtC,OAAgB,MAAM,4BAsDpB;IAEF,iFAAiF;IACjD,KAAK,EAAE,QAAQ,EAAE,CAAM;IACvD,+EAA+E;IAC/C,UAAU,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAwB;IAC/F,wEAAwE;IACZ,eAAe,UAAS;IAE3E,OAAO,CAAC,QAAQ,CAAqB;IAGrC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAcvD;IAyEQ,MAAM,YAMd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,WAAW,EAAE,QAAQ,CAAC;KACvB;CACF"}
|