luxen-ui 0.9.0 → 0.9.2
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/cdn/custom-elements.json +148 -123
- package/cdn/elements/dropdown-item/dropdown-item.js +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/prose-editor/prose-editor.d.ts +24 -2
- package/cdn/elements/prose-editor/prose-editor.d.ts.map +1 -1
- package/cdn/elements/prose-editor/prose-editor.js +40 -39
- package/cdn/elements/prose-editor/prose-editor.js.map +1 -1
- package/cdn/elements/tree/tree.d.ts +11 -1
- package/cdn/elements/tree/tree.d.ts.map +1 -1
- package/cdn/elements/tree/tree.js +1 -3
- package/cdn/elements/tree/tree.js.map +1 -1
- package/cdn/elements/tree-item/tree-item.d.ts +17 -1
- package/cdn/elements/tree-item/tree-item.d.ts.map +1 -1
- package/cdn/elements/tree-item/tree-item.js +2 -1
- package/cdn/elements/tree-item/tree-item.js.map +1 -1
- package/cdn/standalone.css +4 -0
- package/cdn/standalone.js +99 -50
- package/cdn/standalone.js.map +1 -1
- package/cdn/styles/elements/button.css +4 -0
- package/dist/css/elements/button.css +4 -0
- package/dist/custom-elements.json +148 -123
- package/dist/elements/dropdown-item/dropdown-item.css +1 -0
- package/dist/elements/prose-editor/prose-editor.d.ts +24 -2
- package/dist/elements/prose-editor/prose-editor.d.ts.map +1 -1
- package/dist/elements/prose-editor/prose-editor.js +81 -48
- package/dist/elements/tree/tree.css +1 -1
- package/dist/elements/tree/tree.d.ts +11 -1
- package/dist/elements/tree/tree.d.ts.map +1 -1
- package/dist/elements/tree/tree.js +37 -11
- package/dist/elements/tree-item/tree-item.css +5 -1
- package/dist/elements/tree-item/tree-item.d.ts +17 -1
- package/dist/elements/tree-item/tree-item.d.ts.map +1 -1
- package/dist/elements/tree-item/tree-item.js +51 -10
- package/dist/metadata/index.json +22 -3
- package/dist/metadata/tree-item.json +20 -1
- package/dist/metadata/tree.json +1 -1
- package/dist/templates/elements/tree.md +18 -0
- package/package.json +4 -2
- package/postcss-plugin-prefix.js +43 -1
package/cdn/standalone.js
CHANGED
|
@@ -5081,7 +5081,7 @@ __decorate([n$1({ attribute: "min-width" })], Dropdown.prototype, "minWidth", nu
|
|
|
5081
5081
|
define("dropdown", Dropdown);
|
|
5082
5082
|
//#endregion
|
|
5083
5083
|
//#region src/html/elements/dropdown-item/dropdown-item.ts
|
|
5084
|
-
var styles$10 = r$6(":host {\n display: block;\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0.375rem 0.5rem;\n cursor: pointer;\n outline: none;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--l-color-text-primary, CanvasText);\n white-space: nowrap;\n}\n\n.item:focus-visible {\n background: var(--l-color-bg-state-hover);\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\n}\n\n:host(:not([checked])) .check svg {\n visibility: hidden;\n}\n\n::slotted([slot='prefix']),\n::slotted([slot='suffix']) {\n display: flex;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1;\n}\n");
|
|
5084
|
+
var styles$10 = r$6(":host {\n display: block;\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0.375rem 0.5rem;\n cursor: pointer;\n outline: none;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--l-color-text-primary, CanvasText);\n white-space: nowrap;\n text-align: start;\n}\n\n.item:focus-visible {\n background: var(--l-color-bg-state-hover);\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\n}\n\n:host(:not([checked])) .check svg {\n visibility: hidden;\n}\n\n::slotted([slot='prefix']),\n::slotted([slot='suffix']) {\n display: flex;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1;\n}\n");
|
|
5085
5085
|
/**
|
|
5086
5086
|
* A menu item for use inside `<l-dropdown>`.
|
|
5087
5087
|
*
|
|
@@ -33104,12 +33104,18 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33104
33104
|
this.#_toolbarPlacement_accessor_storage = "top";
|
|
33105
33105
|
this.#_autofocus_accessor_storage = false;
|
|
33106
33106
|
this.#_placeholder_accessor_storage = "";
|
|
33107
|
-
this
|
|
33107
|
+
this._emojiOpenAtPointerDown = false;
|
|
33108
33108
|
this._onFocus = () => {
|
|
33109
33109
|
if (!this.disabled && document.activeElement !== this.editor?.view.dom) this.focus();
|
|
33110
33110
|
};
|
|
33111
33111
|
this._onKeyDown = (event) => {
|
|
33112
|
-
if (event.key
|
|
33112
|
+
if (event.key !== "Escape" || !this._emojiPicker?.matches(":popover-open")) return;
|
|
33113
|
+
this._emojiPicker.hidePopover();
|
|
33114
|
+
event.preventDefault();
|
|
33115
|
+
event.stopPropagation();
|
|
33116
|
+
};
|
|
33117
|
+
this._onEmojiButtonPointerDown = () => {
|
|
33118
|
+
this._emojiOpenAtPointerDown = !!this._emojiPicker?.matches(":popover-open");
|
|
33113
33119
|
};
|
|
33114
33120
|
}
|
|
33115
33121
|
static {
|
|
@@ -33171,13 +33177,6 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33171
33177
|
set placeholder(value) {
|
|
33172
33178
|
this.#_placeholder_accessor_storage = value;
|
|
33173
33179
|
}
|
|
33174
|
-
#__emojiPickerActive_accessor_storage;
|
|
33175
|
-
get _emojiPickerActive() {
|
|
33176
|
-
return this.#__emojiPickerActive_accessor_storage;
|
|
33177
|
-
}
|
|
33178
|
-
set _emojiPickerActive(value) {
|
|
33179
|
-
this.#__emojiPickerActive_accessor_storage = value;
|
|
33180
|
-
}
|
|
33181
33180
|
get validationTarget() {
|
|
33182
33181
|
return this._editorRoot;
|
|
33183
33182
|
}
|
|
@@ -33203,19 +33202,18 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33203
33202
|
onTransaction: () => this.requestUpdate(),
|
|
33204
33203
|
onSelectionUpdate: () => this.requestUpdate()
|
|
33205
33204
|
});
|
|
33206
|
-
document.addEventListener("keydown", this._onKeyDown);
|
|
33207
33205
|
this.addEventListener("focus", this._onFocus);
|
|
33206
|
+
document.addEventListener("keydown", this._onKeyDown, true);
|
|
33208
33207
|
this._syncValue();
|
|
33209
33208
|
this.requestUpdate();
|
|
33210
33209
|
}
|
|
33211
33210
|
updated(changed) {
|
|
33212
33211
|
if (changed.has("disabled") && this.editor) this.editor.setEditable(!this.disabled);
|
|
33213
|
-
if (changed.has("_emojiPickerActive")) this._positionEmojiPicker();
|
|
33214
33212
|
}
|
|
33215
33213
|
disconnectedCallback() {
|
|
33216
33214
|
super.disconnectedCallback();
|
|
33217
|
-
document.removeEventListener("keydown", this._onKeyDown);
|
|
33218
33215
|
this.removeEventListener("focus", this._onFocus);
|
|
33216
|
+
document.removeEventListener("keydown", this._onKeyDown, true);
|
|
33219
33217
|
this.editor?.destroy();
|
|
33220
33218
|
this._editorRoot?.remove();
|
|
33221
33219
|
this._emojiPicker?.remove();
|
|
@@ -33328,24 +33326,28 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33328
33326
|
this._editorRoot = root;
|
|
33329
33327
|
return root;
|
|
33330
33328
|
}
|
|
33331
|
-
async
|
|
33332
|
-
|
|
33329
|
+
async _onEmojiButtonClick(event) {
|
|
33330
|
+
const picker = await this._ensureEmojiPicker();
|
|
33331
|
+
if (event.detail === 0 ? picker.matches(":popover-open") : this._emojiOpenAtPointerDown) {
|
|
33332
|
+
if (picker.matches(":popover-open")) picker.hidePopover();
|
|
33333
|
+
} else if (!picker.matches(":popover-open")) picker.showPopover();
|
|
33334
|
+
}
|
|
33335
|
+
_ensureEmojiPicker() {
|
|
33336
|
+
if (this._emojiPicker) return Promise.resolve(this._emojiPicker);
|
|
33337
|
+
return this._emojiPickerPromise ??= (async () => {
|
|
33333
33338
|
const [{ Picker }, { default: data }] = await Promise.all([Promise.resolve().then(() => (init_module(), module_exports)), Promise.resolve().then(() => (init_native(), native_exports))]);
|
|
33334
33339
|
const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
33335
|
-
|
|
33340
|
+
const picker = new Picker({
|
|
33336
33341
|
parent: this._topLayerContainer(),
|
|
33337
33342
|
data,
|
|
33338
33343
|
theme: dark ? "dark" : "light",
|
|
33339
33344
|
onEmojiSelect: ({ native }) => {
|
|
33340
33345
|
this.editor.chain().focus().insertContent(native).run();
|
|
33341
|
-
|
|
33342
|
-
},
|
|
33343
|
-
onClickOutside: (event) => {
|
|
33344
|
-
if (!event.composedPath().includes(this._emojiButton)) this._emojiPickerActive = false;
|
|
33346
|
+
picker.hidePopover();
|
|
33345
33347
|
}
|
|
33346
33348
|
});
|
|
33347
|
-
|
|
33348
|
-
Object.assign(
|
|
33349
|
+
picker.setAttribute("popover", "auto");
|
|
33350
|
+
Object.assign(picker.style, {
|
|
33349
33351
|
position: "fixed",
|
|
33350
33352
|
inset: "auto",
|
|
33351
33353
|
margin: "0",
|
|
@@ -33353,8 +33355,12 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33353
33355
|
border: "0",
|
|
33354
33356
|
background: "transparent"
|
|
33355
33357
|
});
|
|
33356
|
-
|
|
33357
|
-
|
|
33358
|
+
picker.addEventListener("toggle", (event) => {
|
|
33359
|
+
if (event.newState === "open") this._positionEmojiPicker();
|
|
33360
|
+
});
|
|
33361
|
+
this._emojiPicker = picker;
|
|
33362
|
+
return picker;
|
|
33363
|
+
})();
|
|
33358
33364
|
}
|
|
33359
33365
|
/**
|
|
33360
33366
|
* The element the emoji picker is appended to. Inside a modal `<l-dialog>` the
|
|
@@ -33378,13 +33384,8 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33378
33384
|
}
|
|
33379
33385
|
async _positionEmojiPicker() {
|
|
33380
33386
|
const button = this._emojiButton;
|
|
33381
|
-
if (!button || !this._emojiPicker) return;
|
|
33382
33387
|
const picker = this._emojiPicker;
|
|
33383
|
-
if (!
|
|
33384
|
-
if (picker.matches(":popover-open")) picker.hidePopover();
|
|
33385
|
-
return;
|
|
33386
|
-
}
|
|
33387
|
-
if (!picker.matches(":popover-open")) picker.showPopover();
|
|
33388
|
+
if (!button || !picker || !picker.matches(":popover-open")) return;
|
|
33388
33389
|
const { x, y } = await computePosition(button, picker, {
|
|
33389
33390
|
strategy: "fixed",
|
|
33390
33391
|
placement: "bottom",
|
|
@@ -33399,7 +33400,7 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33399
33400
|
top: `${y}px`
|
|
33400
33401
|
});
|
|
33401
33402
|
}
|
|
33402
|
-
_renderButton(command, label, icon, onClick, active = false) {
|
|
33403
|
+
_renderButton(command, label, icon, onClick, active = false, onPointerDown) {
|
|
33403
33404
|
const iconTag = staticTag("icon");
|
|
33404
33405
|
return u`
|
|
33405
33406
|
<button
|
|
@@ -33410,6 +33411,7 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33410
33411
|
aria-label=${label}
|
|
33411
33412
|
aria-pressed=${active}
|
|
33412
33413
|
title=${label}
|
|
33414
|
+
@pointerdown=${onPointerDown}
|
|
33413
33415
|
@click=${onClick}
|
|
33414
33416
|
>
|
|
33415
33417
|
<${iconTag} name=${icon}></${iconTag}>
|
|
@@ -33437,7 +33439,7 @@ var ProseEditor = class extends LuxenFormAssociatedElement {
|
|
|
33437
33439
|
case "code-block": return this._renderButton("code-block", "Code block", "ri:code-box-line", () => this.toggleCodeBlock(), editor.isActive("codeBlock"));
|
|
33438
33440
|
case "horizontal-rule": return this._renderButton("horizontal-rule", "Horizontal rule", "ri:separator", () => this.setHorizontalRule());
|
|
33439
33441
|
case "link": return this._renderButton("link", "Link", "ri:link", () => this.toggleLink(), editor.isActive("link"));
|
|
33440
|
-
case "emoji": return this._renderButton("emoji", "Emoji", "ri:emotion-line", () => void this.
|
|
33442
|
+
case "emoji": return this._renderButton("emoji", "Emoji", "ri:emotion-line", (event) => void this._onEmojiButtonClick(event), false, this._onEmojiButtonPointerDown);
|
|
33441
33443
|
case "attachment": return this._renderButton("attachment", "Attach file", "ri:attachment-2", () => this.emit("add-file"));
|
|
33442
33444
|
case "undo": return this._renderButton("undo", "Undo", "ri:arrow-go-back-line", () => this.undo());
|
|
33443
33445
|
case "redo": return this._renderButton("redo", "Redo", "ri:arrow-go-forward-line", () => this.redo());
|
|
@@ -33487,7 +33489,6 @@ __decorate([n$1({
|
|
|
33487
33489
|
reflect: true
|
|
33488
33490
|
})], ProseEditor.prototype, "autofocus", null);
|
|
33489
33491
|
__decorate([n$1()], ProseEditor.prototype, "placeholder", null);
|
|
33490
|
-
__decorate([r$2()], ProseEditor.prototype, "_emojiPickerActive", null);
|
|
33491
33492
|
//#endregion
|
|
33492
33493
|
//#region src/html/elements/prose-editor/index.ts
|
|
33493
33494
|
define("prose-editor", ProseEditor);
|
|
@@ -34533,10 +34534,13 @@ __decorate([n$1()], Tooltip.prototype, "trigger", null);
|
|
|
34533
34534
|
define("tooltip", Tooltip);
|
|
34534
34535
|
//#endregion
|
|
34535
34536
|
//#region src/html/elements/tree/tree.ts
|
|
34536
|
-
var styles$1 = r$6(":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px;\n --indent-guide-style: solid;\n --indent-guide-color: var(--l-color-border-interactive, light-dark(#d1d5db, #3a4048));\n --row-height: 1.75rem;\n --row-padding-inline: 0.25rem;\n --chevron-size: 1.125rem;\n --item-gap: 0.375rem;\n\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-family: inherit;\n line-height: 1.5;\n}\n\n.tree {\n display: block;\n outline: none;\n}\n\n.tree:focus-visible {\n outline: 2px solid var(--l-focus-ring
|
|
34537
|
+
var styles$1 = r$6(":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px;\n --indent-guide-style: solid;\n --indent-guide-color: var(--l-color-border-interactive, light-dark(#d1d5db, #3a4048));\n --row-height: 1.75rem;\n --row-padding-inline: 0.25rem;\n --chevron-size: 1.125rem;\n --item-gap: 0.375rem;\n\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-family: inherit;\n line-height: 1.5;\n}\n\n.tree {\n display: block;\n outline: none;\n}\n\n.tree:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n border-radius: 0.375rem;\n}\n");
|
|
34537
34538
|
/**
|
|
34538
34539
|
* A hierarchical tree view composed of `<l-tree-item>` children.
|
|
34539
34540
|
*
|
|
34541
|
+
* The host carries `role="tree"`, so give it an accessible name with
|
|
34542
|
+
* `aria-label` or `aria-labelledby` (e.g. `<l-tree aria-label="Files">`).
|
|
34543
|
+
*
|
|
34540
34544
|
* @slot - One or more `l-tree-item` elements.
|
|
34541
34545
|
*
|
|
34542
34546
|
* @csspart base - The root tree container.
|
|
@@ -34557,6 +34561,7 @@ var styles$1 = r$6(":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px
|
|
|
34557
34561
|
var Tree = class extends LuxenElement {
|
|
34558
34562
|
constructor(..._args) {
|
|
34559
34563
|
super(..._args);
|
|
34564
|
+
this._internals = this.attachInternals();
|
|
34560
34565
|
this._lastFocusedItem = null;
|
|
34561
34566
|
this.#_selection_accessor_storage = "single";
|
|
34562
34567
|
this.#_independent_accessor_storage = false;
|
|
@@ -34697,6 +34702,8 @@ var Tree = class extends LuxenElement {
|
|
|
34697
34702
|
}
|
|
34698
34703
|
connectedCallback() {
|
|
34699
34704
|
super.connectedCallback();
|
|
34705
|
+
this._internals.role = "tree";
|
|
34706
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "tree");
|
|
34700
34707
|
this._mutationObserver = new MutationObserver(() => this._syncAll());
|
|
34701
34708
|
this._mutationObserver.observe(this, {
|
|
34702
34709
|
childList: true,
|
|
@@ -34711,6 +34718,11 @@ var Tree = class extends LuxenElement {
|
|
|
34711
34718
|
this.removeEventListener("l-tree-item-toggle", this._onItemToggle);
|
|
34712
34719
|
}
|
|
34713
34720
|
updated(changed) {
|
|
34721
|
+
if (changed.has("selection")) {
|
|
34722
|
+
const multiselectable = this.selection === "multiple" ? "true" : "false";
|
|
34723
|
+
this._internals.ariaMultiSelectable = multiselectable;
|
|
34724
|
+
this.setAttribute("aria-multiselectable", multiselectable);
|
|
34725
|
+
}
|
|
34714
34726
|
if (changed.has("selection") || changed.has("independent")) this._syncAll();
|
|
34715
34727
|
}
|
|
34716
34728
|
/** Returns all items in document (flat) order, including nested ones. */
|
|
@@ -34739,14 +34751,24 @@ var Tree = class extends LuxenElement {
|
|
|
34739
34751
|
return;
|
|
34740
34752
|
}
|
|
34741
34753
|
const showCheckbox = this.selection === "multiple";
|
|
34742
|
-
|
|
34754
|
+
this._syncLevel(roots, 0, showCheckbox);
|
|
34743
34755
|
this._updateBranchStates();
|
|
34744
34756
|
this._ensureTabStop();
|
|
34745
34757
|
}
|
|
34746
|
-
|
|
34747
|
-
|
|
34748
|
-
|
|
34749
|
-
|
|
34758
|
+
/**
|
|
34759
|
+
* Sync depth, checkbox visibility and ARIA position for a sibling group, then
|
|
34760
|
+
* recurse. `aria-level`/`aria-setsize`/`aria-posinset` let screen readers
|
|
34761
|
+
* announce "level N, M of K" — valuable here because `lazy` items mean the
|
|
34762
|
+
* full set isn't always in the DOM (see WAI-ARIA Tree View pattern).
|
|
34763
|
+
*/
|
|
34764
|
+
_syncLevel(items, depth, showCheckbox) {
|
|
34765
|
+
const setSize = items.length;
|
|
34766
|
+
items.forEach((item, index) => {
|
|
34767
|
+
item.depth = depth;
|
|
34768
|
+
item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);
|
|
34769
|
+
item.setPosition(depth + 1, index + 1, setSize);
|
|
34770
|
+
this._syncLevel(item.getChildrenItems(), depth + 1, showCheckbox);
|
|
34771
|
+
});
|
|
34750
34772
|
}
|
|
34751
34773
|
_canShowCheckboxOn(_item) {
|
|
34752
34774
|
if (this.selection !== "multiple") return false;
|
|
@@ -34781,6 +34803,7 @@ var Tree = class extends LuxenElement {
|
|
|
34781
34803
|
switch (this.selection) {
|
|
34782
34804
|
case "single":
|
|
34783
34805
|
this._setSingleSelection(item);
|
|
34806
|
+
if (!item.isLeaf()) item.toggle();
|
|
34784
34807
|
break;
|
|
34785
34808
|
case "leaf":
|
|
34786
34809
|
if (item.isLeaf()) this._setSingleSelection(item);
|
|
@@ -34864,8 +34887,6 @@ var Tree = class extends LuxenElement {
|
|
|
34864
34887
|
<div
|
|
34865
34888
|
class="tree"
|
|
34866
34889
|
part="base"
|
|
34867
|
-
role="tree"
|
|
34868
|
-
aria-multiselectable=${this.selection === "multiple" ? "true" : "false"}
|
|
34869
34890
|
@click=${this._onClick}
|
|
34870
34891
|
@keydown=${this._onKeyDown}
|
|
34871
34892
|
@focusin=${this._onFocusIn}
|
|
@@ -34894,7 +34915,7 @@ define("tree", Tree);
|
|
|
34894
34915
|
var checkbox_appearance_styles_default = r$6("/* Shared checkbox appearance — the visual skin behind `.l-checkbox`.\n\n Colocated with its `checkbox-appearance.styles.ts` wrapper so Shadow-DOM\n elements that render their own native checkbox (e.g. `<l-tree-item>` in\n `selection=\"multiple\"`) can pull in the exact same look via `static styles`.\n The global light-DOM `checkbox.css` primitive `@import`s this file too, so\n both surfaces stay in sync. The global `.l-checkbox` class cannot pierce a\n shadow boundary, but the `--l-form-control-*` tokens this rule relies on DO\n inherit across shadow roots.\n\n The checkmark / indeterminate dash are drawn with a `background-image` SVG on\n the input itself — `::before`/`::after` do not paint on replaced elements\n like `<input>`. `background-image` can't read the host's `currentColor`, so\n the glyph color is baked white. This stays legible because the accent\n (`--l-form-control-activated-color`) is a stable color that does not invert\n between light and dark. Override `--checkmark` to swap the icon. */\n\n@layer components {\n .l-checkbox,\n :where(l-form-field:not([unstyled])) > input[type='checkbox']:not([role='switch']) {\n /* Public knobs */\n --size: var(--l-form-control-toggle-size);\n --accent: var(--l-form-control-activated-color);\n --checkmark: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>');\n --_dash: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/></svg>');\n\n box-sizing: border-box;\n flex: 0 0 auto;\n inline-size: var(--size);\n block-size: var(--size);\n margin: 0;\n padding: 0;\n appearance: none;\n border: var(--l-form-control-border-width) solid var(--l-form-control-border-color);\n border-radius: calc(var(--size) * 0.2);\n background-color: var(--l-form-control-background-color);\n background-repeat: no-repeat;\n background-position: center;\n background-size: 75%;\n vertical-align: middle;\n cursor: pointer;\n transition-property: background-color, border-color;\n transition-duration: 150ms;\n\n &:checked,\n &:indeterminate {\n border-color: var(--accent);\n background-color: var(--accent);\n }\n\n &:checked {\n background-image: var(--checkmark);\n }\n\n &:indeterminate {\n background-image: var(--_dash);\n }\n\n @media (hover: hover) {\n &:hover:not(:disabled) {\n border-color: var(--l-form-control-border-color-hover);\n }\n }\n\n &:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n }\n\n /* Invalid: only after interaction (`:user-invalid`), or forced via\n `aria-invalid` (set by `l-form-field` / server-side validation).\n Overriding `--accent` makes a checked invalid box fill with the error\n color too (not just the border). */\n &:user-invalid,\n &[aria-invalid='true'] {\n --accent: var(--l-form-control-border-color-invalid);\n border-color: var(--l-form-control-border-color-invalid);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .l-checkbox,\n :where(l-form-field:not([unstyled])) > input[type='checkbox']:not([role='switch']) {\n transition-duration: 0ms;\n }\n }\n}\n");
|
|
34895
34916
|
//#endregion
|
|
34896
34917
|
//#region src/html/elements/tree-item/tree-item.ts
|
|
34897
|
-
var styles = r$6(":host {\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n:host([disabled]) {\n opacity: 0.4;\n}\n\n:host([disabled]) .item {\n cursor: not-allowed;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--item-gap);\n min-block-size: var(--row-height);\n padding-inline: var(--row-padding-inline);\n padding-inline-start: calc(var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline));\n border-radius: 0.375rem;\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n transition:\n background-color 120ms ease,\n color 120ms ease;\n position: relative;\n}\n\n.item:focus-visible,\n:host(:focus-visible) .item {\n outline: 2px solid var(--l-focus-ring
|
|
34918
|
+
var styles = r$6(":host {\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-size: 0.875rem;\n line-height: 1.5;\n /* The host is the roving-tabindex focus target, but the visible ring is drawn\n on the inner `.item` row. Suppress the host's UA outline so it doesn't paint\n a second ring around the whole subtree (row + children). */\n outline: none;\n}\n\n:host([disabled]) {\n opacity: 0.4;\n}\n\n:host([disabled]) .item {\n cursor: not-allowed;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--item-gap);\n min-block-size: var(--row-height);\n padding-inline: var(--row-padding-inline);\n padding-inline-start: calc(var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline));\n border-radius: 0.375rem;\n cursor: pointer;\n -webkit-user-select: none;\n user-select: none;\n transition:\n background-color 120ms ease,\n color 120ms ease;\n position: relative;\n}\n\n.item:focus-visible,\n:host(:focus-visible) .item {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 1px;\n}\n\n@media (hover: hover) {\n :host(:not([disabled])) .item:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n:host([selected]:not([disabled])) .item {\n background-color: var(--l-color-bg-state-selected);\n}\n\n.expand {\n inline-size: var(--chevron-size);\n block-size: var(--chevron-size);\n display: grid;\n place-items: center;\n flex: none;\n color: var(--l-color-text-secondary, CanvasText);\n border-radius: 3px;\n cursor: pointer;\n}\n\n/* Hide the DEFAULT fallback chevron SVG on a leaf — slotted content\n (user-provided icon, avatar, etc.) remains visible because it lives outside\n the slot in the DOM tree. The `.expand` span keeps its fixed --chevron-size\n so leaf rows stay aligned with branches. */\n\n:host(:not(:state(has-children)):not([lazy])) .expand > slot > svg {\n display: none;\n}\n\n.expand svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* The checkbox appearance comes from the shared `.l-checkbox` skin imported\n above; this rule only governs visibility. */\n\n:host(:not(:state(checkbox))) .checkbox {\n display: none;\n}\n\n.label {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.label ::slotted(*) {\n min-inline-size: 0;\n}\n\n/* Wrapper around the content slot + children — anchor for the indent guide. */\n\n.branch {\n position: relative;\n}\n\n/* Content slot — block area between the row and the children.\n Aligned under the label text: same left offset as the row's label\n (depth indent + row padding + chevron + gap). Visible for leaves (no\n children) and for expanded branches; hidden when a branch is collapsed\n (mirrors the children visibility). */\n\n.content {\n display: block;\n padding-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + var(--chevron-size) +\n var(--item-gap)\n );\n padding-inline-end: var(--row-padding-inline);\n}\n\n:host(:state(has-children):not([expanded])) .content {\n display: none;\n}\n\n.children {\n display: none;\n}\n\n:host([expanded]) .children {\n display: block;\n}\n\n/* Vertical indent guide — spans the content + children block, starting\n right below the row so it never overlaps the chevron/avatar.\n The guide's visual centre sits exactly on the parent chevron's centre. */\n\n.branch::before {\n content: '';\n position: absolute;\n inset-block: 0;\n inset-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + (var(--chevron-size) / 2) -\n (var(--indent-guide-width) / 2)\n );\n inline-size: 0;\n border-inline-start: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);\n pointer-events: none;\n}\n\n/* Only render the guide for open branches that have children. */\n\n:host(:not([expanded])) .branch::before,\n:host(:not(:state(has-children))) .branch::before {\n display: none;\n}\n\n.spinner {\n inline-size: 0.875rem;\n block-size: 0.875rem;\n border-radius: 50%;\n border: 2px solid var(--l-color-border, currentColor);\n border-block-start-color: transparent;\n animation: spin 700ms linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
34898
34919
|
/**
|
|
34899
34920
|
* A node inside `<l-tree>`. Nested `<l-tree-item>` children become sub-nodes.
|
|
34900
34921
|
*
|
|
@@ -35014,6 +35035,17 @@ var TreeItem = class extends LuxenElement {
|
|
|
35014
35035
|
get depth() {
|
|
35015
35036
|
return this._depth;
|
|
35016
35037
|
}
|
|
35038
|
+
/**
|
|
35039
|
+
* Set by `<l-tree>`: ARIA position within the tree. `level` is 1-based depth,
|
|
35040
|
+
* `posInSet`/`setSize` describe the item's rank among its siblings. These let
|
|
35041
|
+
* screen readers announce "level 2, 3 of 5" even when `lazy` children keep the
|
|
35042
|
+
* full set out of the DOM.
|
|
35043
|
+
*/
|
|
35044
|
+
setPosition(level, posInSet, setSize) {
|
|
35045
|
+
this._aria("ariaLevel", "aria-level", String(level));
|
|
35046
|
+
this._aria("ariaPosInSet", "aria-posinset", String(posInSet));
|
|
35047
|
+
this._aria("ariaSetSize", "aria-setsize", String(setSize));
|
|
35048
|
+
}
|
|
35017
35049
|
/** Whether this item has nested tree-item children. */
|
|
35018
35050
|
get hasChildren() {
|
|
35019
35051
|
return this._hasChildren;
|
|
@@ -35036,6 +35068,7 @@ var TreeItem = class extends LuxenElement {
|
|
|
35036
35068
|
connectedCallback() {
|
|
35037
35069
|
super.connectedCallback();
|
|
35038
35070
|
this._internals.role = "treeitem";
|
|
35071
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "treeitem");
|
|
35039
35072
|
this._childObserver = new MutationObserver(() => this._syncChildren());
|
|
35040
35073
|
this._childObserver.observe(this, { childList: true });
|
|
35041
35074
|
this._syncChildren();
|
|
@@ -35046,11 +35079,28 @@ var TreeItem = class extends LuxenElement {
|
|
|
35046
35079
|
}
|
|
35047
35080
|
updated(changed) {
|
|
35048
35081
|
if (changed.has("expanded")) {
|
|
35049
|
-
this.
|
|
35082
|
+
this._reflectExpanded();
|
|
35083
|
+
if (this.expanded && this.lazy) this.emit("lazy-load");
|
|
35050
35084
|
this.emit(this.expanded ? "expand" : "collapse");
|
|
35051
35085
|
}
|
|
35052
|
-
if (changed.has("selected")) this.
|
|
35053
|
-
if (changed.has("disabled")) this.
|
|
35086
|
+
if (changed.has("selected")) this._aria("ariaSelected", "aria-selected", String(this.selected));
|
|
35087
|
+
if (changed.has("disabled")) this._aria("ariaDisabled", "aria-disabled", this.disabled ? "true" : null);
|
|
35088
|
+
if (changed.has("loading")) this._aria("ariaBusy", "aria-busy", this.loading ? "true" : null);
|
|
35089
|
+
}
|
|
35090
|
+
/** Leaf items omit `aria-expanded` entirely; branches reflect their state. */
|
|
35091
|
+
_reflectExpanded() {
|
|
35092
|
+
this._aria("ariaExpanded", "aria-expanded", this.isLeaf() ? null : String(this.expanded));
|
|
35093
|
+
}
|
|
35094
|
+
/**
|
|
35095
|
+
* Write an ARIA state to BOTH ElementInternals (the semantic source, in the
|
|
35096
|
+
* accessibility tree) and a content attribute (so `[aria-*]` CSS / query / test
|
|
35097
|
+
* selectors keep matching — same belt-and-suspenders as the mirrored `role`).
|
|
35098
|
+
* A `null` value clears both.
|
|
35099
|
+
*/
|
|
35100
|
+
_aria(key, attr, value) {
|
|
35101
|
+
this._internals[key] = value;
|
|
35102
|
+
if (value === null) this.removeAttribute(attr);
|
|
35103
|
+
else this.setAttribute(attr, value);
|
|
35054
35104
|
}
|
|
35055
35105
|
_setState(name, on) {
|
|
35056
35106
|
if (!this._internals.states) return;
|
|
@@ -35067,14 +35117,12 @@ var TreeItem = class extends LuxenElement {
|
|
|
35067
35117
|
this._hasChildren = count > 0;
|
|
35068
35118
|
this._setState("has-children", this._hasChildren);
|
|
35069
35119
|
if (!this._hasChildren && !this.lazy && this.expanded) this.expanded = false;
|
|
35070
|
-
this.
|
|
35120
|
+
this._reflectExpanded();
|
|
35071
35121
|
}
|
|
35072
|
-
/** Toggle expand state.
|
|
35122
|
+
/** Toggle expand state. Opening a `lazy` item emits `lazy-load` (via `updated`). */
|
|
35073
35123
|
toggle() {
|
|
35074
35124
|
if (this.isLeaf() && !this.lazy) return;
|
|
35075
|
-
|
|
35076
|
-
if (next && this.lazy) this.emit("lazy-load");
|
|
35077
|
-
this.expanded = next;
|
|
35125
|
+
this.expanded = !this.expanded;
|
|
35078
35126
|
}
|
|
35079
35127
|
render() {
|
|
35080
35128
|
return b`
|
|
@@ -35124,6 +35172,7 @@ var TreeItem = class extends LuxenElement {
|
|
|
35124
35172
|
part="checkbox"
|
|
35125
35173
|
type="checkbox"
|
|
35126
35174
|
tabindex="-1"
|
|
35175
|
+
aria-hidden="true"
|
|
35127
35176
|
.checked=${this.selected}
|
|
35128
35177
|
.indeterminate=${this.indeterminate}
|
|
35129
35178
|
?disabled=${this.disabled}
|