@deepfuture/dui-components 1.2.0 → 1.3.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/all.d.ts +4 -0
- package/all.js +3 -0
- package/package.json +10 -2
- package/theme/index.d.ts +38 -0
- package/theme/index.js +165 -0
- package/tree/index.d.ts +6 -0
- package/tree/index.js +5 -0
- package/tree/tree-item.d.ts +5 -0
- package/tree/tree-item.js +144 -0
- package/tree/tree.d.ts +5 -0
- package/tree/tree.js +51 -0
package/all.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ import "./textarea/index.js";
|
|
|
44
44
|
import "./toggle/index.js";
|
|
45
45
|
import "./toolbar/index.js";
|
|
46
46
|
import "./tooltip/index.js";
|
|
47
|
+
import "./tree/index.js";
|
|
47
48
|
import "./trunc/index.js";
|
|
48
49
|
export { DuiAccordion } from "./accordion/index.js";
|
|
49
50
|
export { DuiAccordionItem } from "./accordion/index.js";
|
|
@@ -133,6 +134,8 @@ export { DuiToolbar } from "./toolbar/index.js";
|
|
|
133
134
|
export { DuiTooltip } from "./tooltip/index.js";
|
|
134
135
|
export { DuiTooltipPopup } from "./tooltip/index.js";
|
|
135
136
|
export { DuiTooltipTrigger } from "./tooltip/index.js";
|
|
137
|
+
export { DuiTree } from "./tree/index.js";
|
|
138
|
+
export { DuiTreeItem } from "./tree/index.js";
|
|
136
139
|
export { DuiTrunc } from "./trunc/index.js";
|
|
137
140
|
export type { AccordionContext } from "@deepfuture/dui-primitives/accordion";
|
|
138
141
|
export type { AlertDialogOpenChangeDetail } from "@deepfuture/dui-primitives/alert-dialog";
|
|
@@ -159,3 +162,4 @@ export type { TextareaResize } from "@deepfuture/dui-primitives/textarea";
|
|
|
159
162
|
export type { ToggleGroupContext } from "@deepfuture/dui-primitives/toggle";
|
|
160
163
|
export type { TooltipOpenChangeDetail } from "@deepfuture/dui-primitives/tooltip";
|
|
161
164
|
export type { TooltipContext, TooltipSide } from "@deepfuture/dui-primitives/tooltip";
|
|
165
|
+
export type { SelectionMode, TreeContext } from "@deepfuture/dui-primitives/tree";
|
package/all.js
CHANGED
|
@@ -44,6 +44,7 @@ import "./textarea/index.js";
|
|
|
44
44
|
import "./toggle/index.js";
|
|
45
45
|
import "./toolbar/index.js";
|
|
46
46
|
import "./tooltip/index.js";
|
|
47
|
+
import "./tree/index.js";
|
|
47
48
|
import "./trunc/index.js";
|
|
48
49
|
export { DuiAccordion } from "./accordion/index.js";
|
|
49
50
|
export { DuiAccordionItem } from "./accordion/index.js";
|
|
@@ -133,4 +134,6 @@ export { DuiToolbar } from "./toolbar/index.js";
|
|
|
133
134
|
export { DuiTooltip } from "./tooltip/index.js";
|
|
134
135
|
export { DuiTooltipPopup } from "./tooltip/index.js";
|
|
135
136
|
export { DuiTooltipTrigger } from "./tooltip/index.js";
|
|
137
|
+
export { DuiTree } from "./tree/index.js";
|
|
138
|
+
export { DuiTreeItem } from "./tree/index.js";
|
|
136
139
|
export { DuiTrunc } from "./trunc/index.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepfuture/dui-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "DUI styled web components — extends dui-primitives with design tokens and variant CSS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -170,6 +170,10 @@
|
|
|
170
170
|
"import": "./textarea/index.js",
|
|
171
171
|
"types": "./textarea/index.d.ts"
|
|
172
172
|
},
|
|
173
|
+
"./theme": {
|
|
174
|
+
"import": "./theme/index.js",
|
|
175
|
+
"types": "./theme/index.d.ts"
|
|
176
|
+
},
|
|
173
177
|
"./toggle": {
|
|
174
178
|
"import": "./toggle/index.js",
|
|
175
179
|
"types": "./toggle/index.d.ts"
|
|
@@ -182,6 +186,10 @@
|
|
|
182
186
|
"import": "./tooltip/index.js",
|
|
183
187
|
"types": "./tooltip/index.d.ts"
|
|
184
188
|
},
|
|
189
|
+
"./tree": {
|
|
190
|
+
"import": "./tree/index.js",
|
|
191
|
+
"types": "./tree/index.d.ts"
|
|
192
|
+
},
|
|
185
193
|
"./tokens": {
|
|
186
194
|
"import": "./tokens/tokens.js",
|
|
187
195
|
"types": "./tokens/tokens.d.ts"
|
|
@@ -206,7 +214,7 @@
|
|
|
206
214
|
"README.md"
|
|
207
215
|
],
|
|
208
216
|
"dependencies": {
|
|
209
|
-
"@deepfuture/dui-primitives": "1.
|
|
217
|
+
"@deepfuture/dui-primitives": "1.3.0",
|
|
210
218
|
"lit": "^3.3.2",
|
|
211
219
|
"@lit/context": "^1.1.3"
|
|
212
220
|
},
|
package/theme/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme configuration API — bridges DESIGN.md intent to DUI runtime tokens.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { applyTheme } from "@deepfuture/dui-components/theme";
|
|
6
|
+
*
|
|
7
|
+
* applyTheme({
|
|
8
|
+
* light: { accent: "oklch(0.55 0.25 160)" },
|
|
9
|
+
* dark: { accent: "oklch(0.75 0.18 160)" },
|
|
10
|
+
* fonts: { sans: "Inter", mono: "Geist Mono" },
|
|
11
|
+
* radius: "0.5rem",
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* Call after importing any DUI component. Appends an adopted stylesheet
|
|
15
|
+
* that overrides DUI's defaults in the correct cascade position.
|
|
16
|
+
*/
|
|
17
|
+
export type ThemePrimitives = {
|
|
18
|
+
background?: string;
|
|
19
|
+
foreground?: string;
|
|
20
|
+
accent?: string;
|
|
21
|
+
destructive?: string;
|
|
22
|
+
};
|
|
23
|
+
export type ThemeFonts = {
|
|
24
|
+
sans?: string;
|
|
25
|
+
mono?: string;
|
|
26
|
+
serif?: string;
|
|
27
|
+
};
|
|
28
|
+
export type ThemeConfig = {
|
|
29
|
+
/** Light mode color primitives. */
|
|
30
|
+
light?: ThemePrimitives;
|
|
31
|
+
/** Dark mode color primitives. If omitted, dark mode is derived from light. */
|
|
32
|
+
dark?: ThemePrimitives;
|
|
33
|
+
/** Font family overrides. Values are family names (e.g. "Inter"), not full stacks. */
|
|
34
|
+
fonts?: ThemeFonts;
|
|
35
|
+
/** Base border-radius (e.g. "0.5rem" or "8px"). The full scale is derived from this value. */
|
|
36
|
+
radius?: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function applyTheme(config: ThemeConfig): void;
|
package/theme/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme configuration API — bridges DESIGN.md intent to DUI runtime tokens.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { applyTheme } from "@deepfuture/dui-components/theme";
|
|
6
|
+
*
|
|
7
|
+
* applyTheme({
|
|
8
|
+
* light: { accent: "oklch(0.55 0.25 160)" },
|
|
9
|
+
* dark: { accent: "oklch(0.75 0.18 160)" },
|
|
10
|
+
* fonts: { sans: "Inter", mono: "Geist Mono" },
|
|
11
|
+
* radius: "0.5rem",
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* Call after importing any DUI component. Appends an adopted stylesheet
|
|
15
|
+
* that overrides DUI's defaults in the correct cascade position.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Apply a theme configuration to the document.
|
|
19
|
+
*
|
|
20
|
+
* Creates a CSSStyleSheet and appends it to `document.adoptedStyleSheets`
|
|
21
|
+
* after DUI's token sheet, so overrides cascade correctly.
|
|
22
|
+
*
|
|
23
|
+
* Safe to call multiple times — each call replaces the previous theme sheet.
|
|
24
|
+
*/
|
|
25
|
+
let themeSheet = null;
|
|
26
|
+
export function applyTheme(config) {
|
|
27
|
+
const rules = [];
|
|
28
|
+
// --- Color primitives ---
|
|
29
|
+
const lightVars = buildColorVars(config.light);
|
|
30
|
+
const darkVars = buildColorVars(config.dark ?? deriveDark(config.light));
|
|
31
|
+
if (lightVars) {
|
|
32
|
+
rules.push(`:root:not([data-theme="dark"]) { ${lightVars} }`);
|
|
33
|
+
}
|
|
34
|
+
if (darkVars) {
|
|
35
|
+
rules.push(`:root[data-theme="dark"] { ${darkVars} }`);
|
|
36
|
+
}
|
|
37
|
+
// --- Fonts and radius (theme-independent, go on :root) ---
|
|
38
|
+
const rootVars = buildRootVars(config);
|
|
39
|
+
if (rootVars) {
|
|
40
|
+
rules.push(`:root { ${rootVars} }`);
|
|
41
|
+
}
|
|
42
|
+
const css = rules.join("\n");
|
|
43
|
+
if (!css)
|
|
44
|
+
return;
|
|
45
|
+
// Replace previous theme sheet if one exists
|
|
46
|
+
if (themeSheet) {
|
|
47
|
+
const idx = document.adoptedStyleSheets.indexOf(themeSheet);
|
|
48
|
+
if (idx !== -1) {
|
|
49
|
+
document.adoptedStyleSheets = document.adoptedStyleSheets.filter((s) => s !== themeSheet);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
themeSheet = new CSSStyleSheet();
|
|
53
|
+
themeSheet.replaceSync(css);
|
|
54
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];
|
|
55
|
+
}
|
|
56
|
+
// --- Internals ---
|
|
57
|
+
function buildColorVars(primitives) {
|
|
58
|
+
if (!primitives)
|
|
59
|
+
return "";
|
|
60
|
+
const entries = [];
|
|
61
|
+
if (primitives.background)
|
|
62
|
+
entries.push(`--background: ${primitives.background};`);
|
|
63
|
+
if (primitives.foreground)
|
|
64
|
+
entries.push(`--foreground: ${primitives.foreground};`);
|
|
65
|
+
if (primitives.accent)
|
|
66
|
+
entries.push(`--accent: ${primitives.accent};`);
|
|
67
|
+
if (primitives.destructive)
|
|
68
|
+
entries.push(`--destructive: ${primitives.destructive};`);
|
|
69
|
+
return entries.join(" ");
|
|
70
|
+
}
|
|
71
|
+
function buildRootVars(config) {
|
|
72
|
+
const entries = [];
|
|
73
|
+
// Fonts
|
|
74
|
+
if (config.fonts?.sans) {
|
|
75
|
+
entries.push(`--font-sans: '${config.fonts.sans}', system-ui, -apple-system, sans-serif;`);
|
|
76
|
+
}
|
|
77
|
+
if (config.fonts?.mono) {
|
|
78
|
+
entries.push(`--font-mono: '${config.fonts.mono}', ui-monospace, SFMono-Regular, monospace;`);
|
|
79
|
+
}
|
|
80
|
+
if (config.fonts?.serif) {
|
|
81
|
+
entries.push(`--font-serif: '${config.fonts.serif}', ui-serif, Georgia, serif;`);
|
|
82
|
+
}
|
|
83
|
+
// Radius scale derived from base
|
|
84
|
+
if (config.radius) {
|
|
85
|
+
const base = parseRadiusToRem(config.radius);
|
|
86
|
+
if (base !== null) {
|
|
87
|
+
entries.push(`--radius-xs: ${rem(Math.max(base * 0.25, 0))};`);
|
|
88
|
+
entries.push(`--radius-sm: ${rem(Math.max(base * 0.5, 0))};`);
|
|
89
|
+
entries.push(`--radius-md: ${rem(base)};`);
|
|
90
|
+
entries.push(`--radius-lg: ${rem(base * 2)};`);
|
|
91
|
+
entries.push(`--radius-xl: ${rem(base * 3)};`);
|
|
92
|
+
entries.push(`--radius-2xl: ${rem(base * 4)};`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return entries.join(" ");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Derive dark mode primitives from light mode values.
|
|
99
|
+
*
|
|
100
|
+
* Strategy: parse OKLCH values and adjust lightness.
|
|
101
|
+
* - background: invert L (0.97 → 0.15), add slight chroma from accent hue
|
|
102
|
+
* - foreground: invert L (0.15 → 0.93)
|
|
103
|
+
* - accent: boost L (+0.20), reduce C slightly
|
|
104
|
+
* - destructive: boost L (+0.15), reduce C slightly
|
|
105
|
+
*
|
|
106
|
+
* Falls back to DUI's built-in dark defaults if parsing fails.
|
|
107
|
+
*/
|
|
108
|
+
function deriveDark(light) {
|
|
109
|
+
if (!light)
|
|
110
|
+
return undefined;
|
|
111
|
+
const dark = {};
|
|
112
|
+
if (light.background) {
|
|
113
|
+
const parsed = parseOklch(light.background);
|
|
114
|
+
if (parsed) {
|
|
115
|
+
// Invert: light bg → dark bg. Add a hint of accent hue chroma.
|
|
116
|
+
const accentParsed = light.accent ? parseOklch(light.accent) : null;
|
|
117
|
+
const hue = accentParsed?.h ?? 0;
|
|
118
|
+
dark.background = `oklch(${(1 - parsed.l).toFixed(2)} 0.015 ${hue})`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (light.foreground) {
|
|
122
|
+
const parsed = parseOklch(light.foreground);
|
|
123
|
+
if (parsed) {
|
|
124
|
+
dark.foreground = `oklch(${(1 - parsed.l + 0.08).toFixed(2)} 0 0)`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (light.accent) {
|
|
128
|
+
const parsed = parseOklch(light.accent);
|
|
129
|
+
if (parsed) {
|
|
130
|
+
dark.accent = `oklch(${Math.min(parsed.l + 0.20, 0.90).toFixed(2)} ${Math.max(parsed.c - 0.07, 0.05).toFixed(2)} ${parsed.h})`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (light.destructive) {
|
|
134
|
+
const parsed = parseOklch(light.destructive);
|
|
135
|
+
if (parsed) {
|
|
136
|
+
dark.destructive = `oklch(${Math.min(parsed.l + 0.15, 0.85).toFixed(2)} ${Math.max(parsed.c - 0.04, 0.05).toFixed(2)} ${parsed.h})`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return Object.keys(dark).length > 0 ? dark : undefined;
|
|
140
|
+
}
|
|
141
|
+
/** Parse an oklch(...) string into { l, c, h } numbers. */
|
|
142
|
+
function parseOklch(value) {
|
|
143
|
+
const match = value.match(/oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s*\)/);
|
|
144
|
+
if (!match)
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
l: parseFloat(match[1]),
|
|
148
|
+
c: parseFloat(match[2]),
|
|
149
|
+
h: parseFloat(match[3]),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** Parse a radius value (rem or px) to rem number. */
|
|
153
|
+
function parseRadiusToRem(value) {
|
|
154
|
+
const remMatch = value.match(/^([\d.]+)\s*rem$/);
|
|
155
|
+
if (remMatch)
|
|
156
|
+
return parseFloat(remMatch[1]);
|
|
157
|
+
const pxMatch = value.match(/^([\d.]+)\s*px$/);
|
|
158
|
+
if (pxMatch)
|
|
159
|
+
return parseFloat(pxMatch[1]) / 16;
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/** Format a number as a rem string. */
|
|
163
|
+
function rem(value) {
|
|
164
|
+
return value === 0 ? "0" : `${Number(value.toFixed(4))}rem`;
|
|
165
|
+
}
|
package/tree/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./tree.js";
|
|
2
|
+
import "./tree-item.js";
|
|
3
|
+
export { DuiTree } from "./tree.js";
|
|
4
|
+
export { DuiTreeItem } from "./tree-item.js";
|
|
5
|
+
export type { SelectionMode, TreeContext } from "@deepfuture/dui-primitives/tree";
|
|
6
|
+
export { actionEvent, expandedChangeEvent, loadChildrenEvent, selectionChangeEvent, } from "@deepfuture/dui-primitives/tree";
|
package/tree/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { css, unsafeCSS } from "lit";
|
|
2
|
+
import { DuiTreeItemPrimitive } from "@deepfuture/dui-primitives/tree";
|
|
3
|
+
import "../_install.js";
|
|
4
|
+
// Lucide chevron-right, encoded for use as a CSS mask
|
|
5
|
+
const chevronMask = unsafeCSS(`url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")`);
|
|
6
|
+
// Lucide loader-2 (rotating arc), used as a spinner mask while loading
|
|
7
|
+
const spinnerMask = unsafeCSS(`url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E")`);
|
|
8
|
+
const styles = css `
|
|
9
|
+
/* ── Row container ─────────────────────────────────────────────────── */
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* IMPORTANT: All visual styling lives on [part="content"], NOT [part="root"].
|
|
13
|
+
* [part="root"] wraps both the row and the children group, so :hover on it
|
|
14
|
+
* would bleed onto descendant rows. See the hover-bleed regression demo.
|
|
15
|
+
*/
|
|
16
|
+
[part="content"] {
|
|
17
|
+
height: var(--dui-tree-row-height);
|
|
18
|
+
padding-inline-start: calc(
|
|
19
|
+
var(--_tree-row-px) +
|
|
20
|
+
(var(--dui-tree-level, 1) - 1) * var(--dui-tree-indent)
|
|
21
|
+
);
|
|
22
|
+
padding-inline-end: var(--_tree-row-px);
|
|
23
|
+
margin-block: calc(var(--dui-tree-row-spacing) / 2);
|
|
24
|
+
margin-inline: var(--space-1);
|
|
25
|
+
border-radius: var(--dui-tree-row-radius);
|
|
26
|
+
gap: var(--_tree-inline-gap);
|
|
27
|
+
color: var(--text-1);
|
|
28
|
+
font-family: var(--font-sans);
|
|
29
|
+
font-size: var(--_tree-label-font-size);
|
|
30
|
+
line-height: var(--_tree-label-line-height);
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
transition-property: background, box-shadow, color;
|
|
33
|
+
transition-duration: var(--duration-faster);
|
|
34
|
+
transition-timing-function: var(--ease-out-3);
|
|
35
|
+
/* Allow children to truncate */
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (hover: hover) {
|
|
40
|
+
[part="content"]:hover {
|
|
41
|
+
background: var(--dui-tree-hover-bg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:host([data-selected]) [part="content"] {
|
|
46
|
+
background: var(--dui-tree-selected-bg);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:host([data-disabled]) [part="content"] {
|
|
50
|
+
opacity: 0.4;
|
|
51
|
+
cursor: not-allowed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── Focus ring ─────────────────────────────────────────────────────── */
|
|
55
|
+
|
|
56
|
+
[part="root"]:focus-visible {
|
|
57
|
+
outline: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[part="root"]:focus-visible [part="content"] {
|
|
61
|
+
box-shadow:
|
|
62
|
+
0 0 0 var(--focus-ring-offset) var(--background),
|
|
63
|
+
0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width))
|
|
64
|
+
var(--focus-ring-color);
|
|
65
|
+
/* Keep the highlighted row above siblings so the ring isn't clipped. */
|
|
66
|
+
position: relative;
|
|
67
|
+
z-index: 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ── Indicator (chevron / spinner / leaf placeholder) ─────────────── */
|
|
71
|
+
|
|
72
|
+
[part="indicator"] {
|
|
73
|
+
width: var(--_tree-indicator-size);
|
|
74
|
+
height: var(--_tree-indicator-size);
|
|
75
|
+
color: var(--text-2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Branch chevron */
|
|
79
|
+
:host([data-branch]) [part="indicator"]::before {
|
|
80
|
+
content: "";
|
|
81
|
+
display: block;
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: 100%;
|
|
84
|
+
background: currentColor;
|
|
85
|
+
-webkit-mask: ${chevronMask} center / contain no-repeat;
|
|
86
|
+
mask: ${chevronMask} center / contain no-repeat;
|
|
87
|
+
transition-property: transform;
|
|
88
|
+
transition-duration: var(--duration-fast);
|
|
89
|
+
transition-timing-function: var(--ease-out-3);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:host([data-expanded]) [part="indicator"]::before {
|
|
93
|
+
transform: rotate(90deg);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Loading spinner replaces the chevron in-place */
|
|
97
|
+
:host([data-loading]) [part="indicator"]::before {
|
|
98
|
+
-webkit-mask: ${spinnerMask} center / contain no-repeat;
|
|
99
|
+
mask: ${spinnerMask} center / contain no-repeat;
|
|
100
|
+
transform: none;
|
|
101
|
+
animation: dui-tree-spin 0.9s linear infinite;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@keyframes dui-tree-spin {
|
|
105
|
+
from {
|
|
106
|
+
transform: rotate(0deg);
|
|
107
|
+
}
|
|
108
|
+
to {
|
|
109
|
+
transform: rotate(360deg);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── Slotted content ────────────────────────────────────────────────── */
|
|
114
|
+
|
|
115
|
+
::slotted([slot="label"]) {
|
|
116
|
+
min-width: 0;
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
text-overflow: ellipsis;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
::slotted([slot="end"]) {
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
color: var(--text-2);
|
|
125
|
+
font-size: var(--_tree-end-font-size);
|
|
126
|
+
line-height: var(--_tree-end-line-height);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ── Reduced motion ────────────────────────────────────────────────── */
|
|
130
|
+
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
[part="content"],
|
|
133
|
+
:host([data-branch]) [part="indicator"]::before {
|
|
134
|
+
transition-duration: 0s;
|
|
135
|
+
}
|
|
136
|
+
:host([data-loading]) [part="indicator"]::before {
|
|
137
|
+
animation: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
export class DuiTreeItem extends DuiTreeItemPrimitive {
|
|
142
|
+
static styles = [...DuiTreeItemPrimitive.styles, styles];
|
|
143
|
+
}
|
|
144
|
+
customElements.define(DuiTreeItem.tagName, DuiTreeItem);
|
package/tree/tree.d.ts
ADDED
package/tree/tree.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
import { DuiTreePrimitive } from "@deepfuture/dui-primitives/tree";
|
|
3
|
+
import "../_install.js";
|
|
4
|
+
const styles = css `
|
|
5
|
+
/* ── Sizes — write to public tokens on :host so consumer overrides win ── */
|
|
6
|
+
|
|
7
|
+
:host {
|
|
8
|
+
/* Default = sm */
|
|
9
|
+
--dui-tree-row-height: var(--component-height-sm);
|
|
10
|
+
--dui-tree-indent: var(--space-4);
|
|
11
|
+
--dui-tree-row-spacing: 0;
|
|
12
|
+
--dui-tree-row-radius: var(--radius-sm);
|
|
13
|
+
--dui-tree-hover-bg: oklch(from var(--foreground) l c h / 0.05);
|
|
14
|
+
--dui-tree-selected-bg: oklch(from var(--foreground) l c h / 0.10);
|
|
15
|
+
|
|
16
|
+
/* Internal-only (not part of public token surface) */
|
|
17
|
+
--_tree-label-font-size: var(--text-xs);
|
|
18
|
+
--_tree-label-line-height: var(--text-xs--line-height);
|
|
19
|
+
--_tree-end-font-size: var(--text-2xs);
|
|
20
|
+
--_tree-end-line-height: var(--text-2xs--line-height);
|
|
21
|
+
--_tree-indicator-size: 14px;
|
|
22
|
+
--_tree-row-px: var(--space-2);
|
|
23
|
+
--_tree-inline-gap: var(--space-2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:host([size="md"]) {
|
|
27
|
+
--dui-tree-row-height: var(--component-height-md);
|
|
28
|
+
--dui-tree-indent: var(--space-5);
|
|
29
|
+
--_tree-label-font-size: var(--text-sm);
|
|
30
|
+
--_tree-label-line-height: var(--text-sm--line-height);
|
|
31
|
+
--_tree-end-font-size: var(--text-xs);
|
|
32
|
+
--_tree-end-line-height: var(--text-xs--line-height);
|
|
33
|
+
--_tree-indicator-size: var(--space-4);
|
|
34
|
+
--_tree-inline-gap: var(--space-2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:host([size="lg"]) {
|
|
38
|
+
--dui-tree-row-height: var(--component-height-lg);
|
|
39
|
+
--dui-tree-indent: var(--space-6);
|
|
40
|
+
--_tree-label-font-size: var(--text-sm);
|
|
41
|
+
--_tree-label-line-height: var(--text-sm--line-height);
|
|
42
|
+
--_tree-end-font-size: var(--text-xs);
|
|
43
|
+
--_tree-end-line-height: var(--text-xs--line-height);
|
|
44
|
+
--_tree-indicator-size: var(--space-4);
|
|
45
|
+
--_tree-inline-gap: var(--space-2_5);
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
export class DuiTree extends DuiTreePrimitive {
|
|
49
|
+
static styles = [...DuiTreePrimitive.styles, styles];
|
|
50
|
+
}
|
|
51
|
+
customElements.define(DuiTree.tagName, DuiTree);
|