@brandsync/wc 1.15.0 → 1.16.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/CHANGELOG.md +7 -0
- package/REFERENCE.md +82 -0
- package/custom-elements.json +1293 -443
- package/dist/brandsync-wc/brandsync-wc.esm.js +1 -1
- package/dist/brandsync-wc/{p-87d44938.entry.js → p-0159f0da.entry.js} +1 -1
- package/dist/brandsync-wc/p-0228a584.entry.js +1 -0
- package/dist/brandsync-wc/{p-6df48031.entry.js → p-15f4c343.entry.js} +1 -1
- package/dist/brandsync-wc/{p-3c21d0bf.entry.js → p-298158da.entry.js} +1 -1
- package/dist/brandsync-wc/{p-1b4c0971.entry.js → p-38fe9d07.entry.js} +1 -1
- package/dist/brandsync-wc/{p-bc97ece8.entry.js → p-42b79d95.entry.js} +1 -1
- package/dist/brandsync-wc/{p-b9c0b481.entry.js → p-521d0fc2.entry.js} +1 -1
- package/dist/brandsync-wc/{p-1bf8fbbb.entry.js → p-67629178.entry.js} +1 -1
- package/dist/brandsync-wc/{p-e294f698.entry.js → p-76748795.entry.js} +1 -1
- package/dist/brandsync-wc/p-7dc59e0b.entry.js +1 -0
- package/dist/brandsync-wc/p-949a4270.entry.js +1 -0
- package/dist/brandsync-wc/{p-7a072e64.entry.js → p-a12691d8.entry.js} +1 -1
- package/dist/brandsync-wc/{p-ac9472ec.entry.js → p-cc4c8c1d.entry.js} +1 -1
- package/dist/brandsync-wc/p-e9afb553.entry.js +1 -0
- package/dist/cjs/brandsync-wc.cjs.js +1 -1
- package/dist/cjs/bs-inline-tab.cjs.entry.js +1 -1
- package/dist/cjs/bs-input_3.cjs.entry.js +2 -2
- package/dist/cjs/bs-list-item.cjs.entry.js +66 -0
- package/dist/cjs/bs-list.cjs.entry.js +103 -0
- package/dist/cjs/bs-message-list.cjs.entry.js +1 -1
- package/dist/cjs/bs-navigation-drawer-item.cjs.entry.js +1 -1
- package/dist/cjs/bs-navigation-drawer.cjs.entry.js +2 -2
- package/dist/cjs/bs-pagination.cjs.entry.js +1 -1
- package/dist/cjs/bs-radio.cjs.entry.js +1 -1
- package/dist/cjs/bs-source-link.cjs.entry.js +2 -2
- package/dist/cjs/bs-stepper.cjs.entry.js +1 -1
- package/dist/cjs/bs-tooltip.cjs.entry.js +1 -1
- package/dist/cjs/bs-tree-item.cjs.entry.js +94 -0
- package/dist/cjs/bs-tree.cjs.entry.js +197 -0
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/collection-manifest.json +4 -0
- package/dist/collection/components/bs-list/bs-list.cmp.test.js +99 -0
- package/dist/collection/components/bs-list/bs-list.css +15 -0
- package/dist/collection/components/bs-list/bs-list.js +241 -0
- package/dist/collection/components/bs-list/bs-list.stories.js +43 -0
- package/dist/collection/components/bs-list-item/bs-list-item.cmp.test.js +137 -0
- package/dist/collection/components/bs-list-item/bs-list-item.css +139 -0
- package/dist/collection/components/bs-list-item/bs-list-item.js +343 -0
- package/dist/collection/components/bs-list-item/bs-list-item.stories.js +86 -0
- package/dist/collection/components/bs-menu/bs-menu.js +1 -1
- package/dist/collection/components/bs-menu-item/bs-menu-item.cmp.test.js +6 -0
- package/dist/collection/components/bs-menu-item/bs-menu-item.js +1 -1
- package/dist/collection/components/bs-message-list/bs-message-list.js +1 -1
- package/dist/collection/components/bs-pagination/bs-pagination.js +1 -1
- package/dist/collection/components/bs-radio/bs-radio.js +1 -1
- package/dist/collection/components/bs-source-link/bs-source-link.js +2 -2
- package/dist/collection/components/bs-stepper/bs-stepper/bs-stepper.js +1 -1
- package/dist/collection/components/bs-tab/bs-inline-tab/bs-inline-tab.js +1 -1
- package/dist/collection/components/bs-tooltip/bs-tooltip.js +1 -1
- package/dist/collection/components/bs-tree/bs-tree.cmp.test.js +166 -0
- package/dist/collection/components/bs-tree/bs-tree.css +8 -0
- package/dist/collection/components/bs-tree/bs-tree.js +354 -0
- package/dist/collection/components/bs-tree/bs-tree.stories.js +34 -0
- package/dist/collection/components/bs-tree-item/bs-tree-item.cmp.test.js +187 -0
- package/dist/collection/components/bs-tree-item/bs-tree-item.css +161 -0
- package/dist/collection/components/bs-tree-item/bs-tree-item.js +305 -0
- package/dist/collection/components/bs-tree-item/bs-tree-item.stories.js +54 -0
- package/dist/collection/components/ui-shell/bs-navigation-drawer/bs-navigation-drawer/bs-navigation-drawer.js +2 -2
- package/dist/collection/components/ui-shell/bs-navigation-drawer/bs-navigation-drawer-item/bs-navigation-drawer-item.js +1 -1
- package/dist/components/bs-breadcrumb-overflow.js +1 -1
- package/dist/components/bs-carousel.js +1 -1
- package/dist/components/bs-chatbot-header.js +1 -1
- package/dist/components/bs-citation-preview.js +1 -1
- package/dist/components/bs-datepicker.js +1 -1
- package/dist/components/bs-inline-tab.js +1 -1
- package/dist/components/bs-input.js +1 -1
- package/dist/components/bs-list-item.d.ts +11 -0
- package/dist/components/bs-list-item.js +1 -0
- package/dist/components/bs-list.d.ts +11 -0
- package/dist/components/bs-list.js +1 -0
- package/dist/components/bs-menu-item.js +1 -1
- package/dist/components/bs-menu.js +1 -1
- package/dist/components/bs-message-list.js +1 -1
- package/dist/components/bs-navigation-drawer-item.js +1 -1
- package/dist/components/bs-navigation-drawer.js +1 -1
- package/dist/components/bs-pagination.js +1 -1
- package/dist/components/bs-radio.js +1 -1
- package/dist/components/bs-session-timeout.js +1 -1
- package/dist/components/bs-source-link.js +1 -1
- package/dist/components/bs-stepper.js +1 -1
- package/dist/components/bs-timepicker.js +1 -1
- package/dist/components/bs-tooltip-trigger.js +1 -1
- package/dist/components/bs-tooltip.js +1 -1
- package/dist/components/bs-tree-item.d.ts +11 -0
- package/dist/components/bs-tree-item.js +1 -0
- package/dist/components/bs-tree.d.ts +11 -0
- package/dist/components/bs-tree.js +1 -0
- package/dist/components/p-BaJEv6rL.js +1 -0
- package/dist/components/{p-BvYkZcOU.js → p-Bb1-2M8K.js} +1 -1
- package/dist/components/{p-Bqe4waKJ.js → p-BdkE4auv.js} +1 -1
- package/dist/components/{p-ElcsxZSh.js → p-C93ymWm7.js} +1 -1
- package/dist/components/{p-ByKcHsj0.js → p-DFKc3lLA.js} +1 -1
- package/dist/components/{p-B3l4WpgS.js → p-J9cxcDhY.js} +1 -1
- package/dist/esm/brandsync-wc.js +1 -1
- package/dist/esm/bs-inline-tab.entry.js +1 -1
- package/dist/esm/bs-input_3.entry.js +2 -2
- package/dist/esm/bs-list-item.entry.js +64 -0
- package/dist/esm/bs-list.entry.js +101 -0
- package/dist/esm/bs-message-list.entry.js +1 -1
- package/dist/esm/bs-navigation-drawer-item.entry.js +1 -1
- package/dist/esm/bs-navigation-drawer.entry.js +2 -2
- package/dist/esm/bs-pagination.entry.js +1 -1
- package/dist/esm/bs-radio.entry.js +1 -1
- package/dist/esm/bs-source-link.entry.js +2 -2
- package/dist/esm/bs-stepper.entry.js +1 -1
- package/dist/esm/bs-tooltip.entry.js +1 -1
- package/dist/esm/bs-tree-item.entry.js +92 -0
- package/dist/esm/bs-tree.entry.js +195 -0
- package/dist/esm/loader.js +1 -1
- package/dist/types/components/bs-list/bs-list.cmp.test.d.ts +1 -0
- package/dist/types/components/bs-list/bs-list.d.ts +42 -0
- package/dist/types/components/bs-list/bs-list.stories.d.ts +13 -0
- package/dist/types/components/bs-list-item/bs-list-item.cmp.test.d.ts +1 -0
- package/dist/types/components/bs-list-item/bs-list-item.d.ts +85 -0
- package/dist/types/components/bs-list-item/bs-list-item.stories.d.ts +22 -0
- package/dist/types/components/bs-tree/bs-tree.cmp.test.d.ts +1 -0
- package/dist/types/components/bs-tree/bs-tree.d.ts +53 -0
- package/dist/types/components/bs-tree/bs-tree.stories.d.ts +11 -0
- package/dist/types/components/bs-tree-item/bs-tree-item.cmp.test.d.ts +1 -0
- package/dist/types/components/bs-tree-item/bs-tree-item.d.ts +60 -0
- package/dist/types/components/bs-tree-item/bs-tree-item.stories.d.ts +19 -0
- package/dist/types/components.d.ts +675 -0
- package/package.json +1 -1
- package/reference.d.ts +12 -0
- package/reference.json +250 -0
- package/dist/components/p-Mf1jorXP.js +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { render, h, describe, it, expect } from "@stencil/vitest";
|
|
2
|
+
import { userEvent } from "vitest/browser";
|
|
3
|
+
const mount = (props = {}) => render(h("bs-tree", { ariaLabel: "Files", ...props }, h("bs-tree-item", { label: "src", value: "src", expanded: true }, h("bs-tree-item", { label: "components", value: "components" }, h("bs-tree-item", { label: "button.tsx", value: "button" })), h("bs-tree-item", { label: "locked", value: "locked", disabled: true }), h("bs-tree-item", { label: "index.ts", value: "index" })), h("bs-tree-item", { label: "docs", value: "docs" }, h("bs-tree-item", { label: "readme", value: "readme" })), h("bs-tree-item", { label: "package.json", value: "pkg" })));
|
|
4
|
+
const get = (root, v) => Array.from(root.querySelectorAll('bs-tree-item')).find(i => i.value === v);
|
|
5
|
+
const tabbables = (root) => Array.from(root.querySelectorAll('bs-tree-item')).filter(i => i.getAttribute('tabindex') === '0');
|
|
6
|
+
const press = async (key, waitForChanges) => {
|
|
7
|
+
await userEvent.keyboard(key);
|
|
8
|
+
await waitForChanges();
|
|
9
|
+
};
|
|
10
|
+
describe('bs-tree', () => {
|
|
11
|
+
it('renders role=tree with the label and a single roving tab stop', async () => {
|
|
12
|
+
const { root, waitForChanges } = await mount();
|
|
13
|
+
await waitForChanges();
|
|
14
|
+
const tree = root;
|
|
15
|
+
const inner = tree.shadowRoot.querySelector('[role="tree"]');
|
|
16
|
+
expect(inner).toEqualAttribute('aria-label', 'Files');
|
|
17
|
+
expect(tabbables(root)).toEqual([get(root, 'src')]);
|
|
18
|
+
expect(get(root, 'locked').hasAttribute('tabindex')).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
it('puts the roving tab stop on the selected item', async () => {
|
|
21
|
+
const { root, waitForChanges } = await mount({ value: 'index' });
|
|
22
|
+
await waitForChanges();
|
|
23
|
+
expect(tabbables(root)).toEqual([get(root, 'index')]);
|
|
24
|
+
expect(get(root, 'index').selected).toBe(true);
|
|
25
|
+
expect(get(root, 'index')).toEqualAttribute('aria-selected', 'true');
|
|
26
|
+
});
|
|
27
|
+
it('selects on click, emits bsChange once, and keeps selection single', async () => {
|
|
28
|
+
const { root, waitForChanges } = await mount();
|
|
29
|
+
const changes = [];
|
|
30
|
+
root.addEventListener('bsChange', e => changes.push(e.detail));
|
|
31
|
+
get(root, 'index').shadowRoot.querySelector('[part="container"]').click();
|
|
32
|
+
await waitForChanges();
|
|
33
|
+
get(root, 'src').shadowRoot.querySelector('[part="container"]').click();
|
|
34
|
+
await waitForChanges();
|
|
35
|
+
expect(changes).toEqual(['index', 'src']);
|
|
36
|
+
expect(root.value).toBe('src');
|
|
37
|
+
expect(Array.from(root.querySelectorAll('bs-tree-item')).filter(i => i.selected)).toEqual([get(root, 'src')]);
|
|
38
|
+
// Row click on a parent selects but does not toggle.
|
|
39
|
+
expect(get(root, 'src').expanded).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('syncs value set from code without emitting bsChange', async () => {
|
|
42
|
+
const { root, waitForChanges } = await mount();
|
|
43
|
+
const changes = [];
|
|
44
|
+
root.addEventListener('bsChange', e => changes.push(e.detail));
|
|
45
|
+
root.value = 'pkg';
|
|
46
|
+
await waitForChanges();
|
|
47
|
+
expect(get(root, 'pkg').selected).toBe(true);
|
|
48
|
+
expect(changes).toEqual([]);
|
|
49
|
+
root.value = '';
|
|
50
|
+
await waitForChanges();
|
|
51
|
+
expect(get(root, 'pkg').selected).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it('selectable=false: rows do not select but caret still expands', async () => {
|
|
54
|
+
const { root, waitForChanges } = await mount({ selectable: false });
|
|
55
|
+
const changes = [];
|
|
56
|
+
root.addEventListener('bsChange', e => changes.push(e.detail));
|
|
57
|
+
get(root, 'index').shadowRoot.querySelector('[part="container"]').click();
|
|
58
|
+
await waitForChanges();
|
|
59
|
+
expect(changes).toEqual([]);
|
|
60
|
+
expect(get(root, 'index').selected).toBe(false);
|
|
61
|
+
get(root, 'docs').shadowRoot.querySelector('[part="caret"]').click();
|
|
62
|
+
await waitForChanges();
|
|
63
|
+
expect(get(root, 'docs').expanded).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('collapsed children leave the layout and navigation', async () => {
|
|
66
|
+
const { root, waitForChanges } = await mount();
|
|
67
|
+
await waitForChanges();
|
|
68
|
+
const readme = get(root, 'readme');
|
|
69
|
+
expect(readme.getBoundingClientRect().height).toBe(0);
|
|
70
|
+
get(root, 'docs').expanded = true;
|
|
71
|
+
await waitForChanges();
|
|
72
|
+
expect(readme.getBoundingClientRect().height).toBe(48);
|
|
73
|
+
});
|
|
74
|
+
it('ArrowDown / ArrowUp / Home / End move focus over visible, enabled items', async () => {
|
|
75
|
+
const { root, waitForChanges } = await mount();
|
|
76
|
+
await waitForChanges();
|
|
77
|
+
await userEvent.tab();
|
|
78
|
+
expect(document.activeElement).toBe(get(root, 'src'));
|
|
79
|
+
await press('{ArrowDown}', waitForChanges);
|
|
80
|
+
expect(document.activeElement).toBe(get(root, 'components'));
|
|
81
|
+
await press('{ArrowDown}', waitForChanges);
|
|
82
|
+
// 'locked' is disabled -> skipped.
|
|
83
|
+
expect(document.activeElement).toBe(get(root, 'index'));
|
|
84
|
+
await press('{ArrowDown}', waitForChanges);
|
|
85
|
+
expect(document.activeElement).toBe(get(root, 'docs'));
|
|
86
|
+
// readme is collapsed -> next is package.json
|
|
87
|
+
await press('{ArrowDown}', waitForChanges);
|
|
88
|
+
expect(document.activeElement).toBe(get(root, 'pkg'));
|
|
89
|
+
expect(tabbables(root)).toEqual([get(root, 'pkg')]);
|
|
90
|
+
await press('{ArrowUp}', waitForChanges);
|
|
91
|
+
expect(document.activeElement).toBe(get(root, 'docs'));
|
|
92
|
+
await press('{Home}', waitForChanges);
|
|
93
|
+
expect(document.activeElement).toBe(get(root, 'src'));
|
|
94
|
+
await press('{End}', waitForChanges);
|
|
95
|
+
expect(document.activeElement).toBe(get(root, 'pkg'));
|
|
96
|
+
});
|
|
97
|
+
it('ArrowRight expands then enters; ArrowLeft collapses then goes to the parent', async () => {
|
|
98
|
+
const { root, waitForChanges } = await mount();
|
|
99
|
+
await waitForChanges();
|
|
100
|
+
await userEvent.tab();
|
|
101
|
+
await press('{ArrowDown}', waitForChanges); // components (closed)
|
|
102
|
+
const toggles = [];
|
|
103
|
+
get(root, 'components').addEventListener('bsToggle', e => toggles.push(e.detail));
|
|
104
|
+
await press('{ArrowRight}', waitForChanges);
|
|
105
|
+
expect(get(root, 'components').expanded).toBe(true);
|
|
106
|
+
expect(get(root, 'components')).toEqualAttribute('aria-expanded', 'true');
|
|
107
|
+
expect(document.activeElement).toBe(get(root, 'components'));
|
|
108
|
+
await press('{ArrowRight}', waitForChanges);
|
|
109
|
+
expect(document.activeElement).toBe(get(root, 'button'));
|
|
110
|
+
await press('{ArrowLeft}', waitForChanges); // leaf -> parent
|
|
111
|
+
expect(document.activeElement).toBe(get(root, 'components'));
|
|
112
|
+
await press('{ArrowLeft}', waitForChanges); // collapse
|
|
113
|
+
expect(get(root, 'components').expanded).toBe(false);
|
|
114
|
+
expect(toggles).toEqual([true, false]);
|
|
115
|
+
await press('{ArrowLeft}', waitForChanges); // closed -> parent
|
|
116
|
+
expect(document.activeElement).toBe(get(root, 'src'));
|
|
117
|
+
});
|
|
118
|
+
it('Enter and Space select the focused item', async () => {
|
|
119
|
+
const { root, waitForChanges } = await mount();
|
|
120
|
+
const changes = [];
|
|
121
|
+
root.addEventListener('bsChange', e => changes.push(e.detail));
|
|
122
|
+
await waitForChanges();
|
|
123
|
+
await userEvent.tab();
|
|
124
|
+
await press('{Enter}', waitForChanges);
|
|
125
|
+
await press('{ArrowDown}', waitForChanges);
|
|
126
|
+
await press(' ', waitForChanges);
|
|
127
|
+
expect(changes).toEqual(['src', 'components']);
|
|
128
|
+
expect(get(root, 'components').selected).toBe(true);
|
|
129
|
+
expect(get(root, 'src').selected).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
it('* expands all siblings of the focused item', async () => {
|
|
132
|
+
const { root, waitForChanges } = await mount();
|
|
133
|
+
await waitForChanges();
|
|
134
|
+
await userEvent.tab();
|
|
135
|
+
await press('{ArrowDown}', waitForChanges); // components
|
|
136
|
+
await press('*', waitForChanges);
|
|
137
|
+
expect(get(root, 'components').expanded).toBe(true);
|
|
138
|
+
await press('{End}', waitForChanges); // pkg (top level)
|
|
139
|
+
await press('*', waitForChanges);
|
|
140
|
+
expect(get(root, 'docs').expanded).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
it('expandAll / collapseAll', async () => {
|
|
143
|
+
const { root, waitForChanges } = await mount();
|
|
144
|
+
await root.expandAll();
|
|
145
|
+
await waitForChanges();
|
|
146
|
+
expect(Array.from(root.querySelectorAll('bs-tree-item')).filter(i => i.expanded).length).toBe(root.querySelectorAll('bs-tree-item').length);
|
|
147
|
+
await root.collapseAll();
|
|
148
|
+
await waitForChanges();
|
|
149
|
+
expect(Array.from(root.querySelectorAll('bs-tree-item')).some(i => i.expanded)).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
it('handles items added later (levels, tabindex)', async () => {
|
|
152
|
+
const { root, waitForChanges } = await mount();
|
|
153
|
+
await waitForChanges();
|
|
154
|
+
const docs = get(root, 'docs');
|
|
155
|
+
const added = document.createElement('bs-tree-item');
|
|
156
|
+
added.label = 'late';
|
|
157
|
+
added.value = 'late';
|
|
158
|
+
docs.appendChild(added);
|
|
159
|
+
docs.expanded = true;
|
|
160
|
+
await waitForChanges();
|
|
161
|
+
await new Promise(r => setTimeout(r, 50));
|
|
162
|
+
expect(added).toEqualAttribute('aria-level', '2');
|
|
163
|
+
expect(added.getAttribute('tabindex')).toBe('-1');
|
|
164
|
+
expect(tabbables(root).length).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { h } from "@stencil/core";
|
|
2
|
+
/**
|
|
3
|
+
* A hierarchical list of `bs-tree-item` rows with single selection, expand/collapse and the
|
|
4
|
+
* WAI-ARIA tree keyboard pattern.
|
|
5
|
+
*
|
|
6
|
+
* The tree owns `role="tree"`, the roving `tabindex` (exactly one enabled, visible item is
|
|
7
|
+
* tabbable: the selected one, otherwise the first), selection (kept in sync with each item's
|
|
8
|
+
* `selected`) and keyboard navigation over the visible (rendered, non-disabled) items. Items own
|
|
9
|
+
* their expansion state and emit `bsSelect` / `bsToggle`.
|
|
10
|
+
*
|
|
11
|
+
* Built from: `bs-tree-item` rows.
|
|
12
|
+
*
|
|
13
|
+
* @slot - Default slot: top-level `bs-tree-item` elements (nested ones go inside their parent item).
|
|
14
|
+
* @part container - The `role="tree"` wrapper.
|
|
15
|
+
*/
|
|
16
|
+
export class BsTree {
|
|
17
|
+
el;
|
|
18
|
+
/** Accessible name of the tree. Bound onto the internal `role="tree"` element (an `aria-label`
|
|
19
|
+
* on the host would not reach it). */
|
|
20
|
+
ariaLabel = null;
|
|
21
|
+
/** The `value` (or, when empty, the `label`) of the selected item. Two-way synced with each
|
|
22
|
+
* item's `selected` (single selection). Setting it from code does not emit `bsChange`. */
|
|
23
|
+
value = '';
|
|
24
|
+
/** When `false`, rows do not select (clicks and Enter/Space do nothing) but parents still
|
|
25
|
+
* expand and collapse. */
|
|
26
|
+
selectable = true;
|
|
27
|
+
/** Emitted when the user changes the selection (click, Enter/Space), with the new value. Not
|
|
28
|
+
* emitted when `value` is set from code. */
|
|
29
|
+
bsChange;
|
|
30
|
+
observer;
|
|
31
|
+
connectedCallback() {
|
|
32
|
+
this.observer = new MutationObserver(() => this.refresh());
|
|
33
|
+
this.observer.observe(this.el, {
|
|
34
|
+
childList: true,
|
|
35
|
+
subtree: true,
|
|
36
|
+
attributes: true,
|
|
37
|
+
attributeFilter: ['expanded', 'disabled'],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
disconnectedCallback() {
|
|
41
|
+
this.observer?.disconnect();
|
|
42
|
+
}
|
|
43
|
+
componentDidLoad() {
|
|
44
|
+
if (!this.value) {
|
|
45
|
+
const preselected = this.items().find(i => i.selected || i.hasAttribute('selected'));
|
|
46
|
+
if (preselected)
|
|
47
|
+
this.value = this.keyOf(preselected);
|
|
48
|
+
}
|
|
49
|
+
this.syncSelection();
|
|
50
|
+
this.refresh();
|
|
51
|
+
}
|
|
52
|
+
onValueChange() {
|
|
53
|
+
this.syncSelection();
|
|
54
|
+
this.refresh();
|
|
55
|
+
}
|
|
56
|
+
/** Expands every parent item. */
|
|
57
|
+
async expandAll() {
|
|
58
|
+
this.items().forEach(i => (i.expanded = true));
|
|
59
|
+
}
|
|
60
|
+
/** Collapses every parent item. */
|
|
61
|
+
async collapseAll() {
|
|
62
|
+
this.items().forEach(i => (i.expanded = false));
|
|
63
|
+
}
|
|
64
|
+
items() {
|
|
65
|
+
return Array.from(this.el.querySelectorAll('bs-tree-item'));
|
|
66
|
+
}
|
|
67
|
+
keyOf(item) {
|
|
68
|
+
return item.value || item.label;
|
|
69
|
+
}
|
|
70
|
+
isParent(item) {
|
|
71
|
+
return Array.from(item.children).some(c => c.tagName === 'BS-TREE-ITEM');
|
|
72
|
+
}
|
|
73
|
+
isRendered(item) {
|
|
74
|
+
let p = item.parentElement;
|
|
75
|
+
while (p && p !== this.el) {
|
|
76
|
+
if (p.tagName === 'BS-TREE-ITEM' && !p.expanded)
|
|
77
|
+
return false;
|
|
78
|
+
p = p.parentElement;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
navigable() {
|
|
83
|
+
return this.items().filter(i => !i.disabled && this.isRendered(i));
|
|
84
|
+
}
|
|
85
|
+
syncSelection() {
|
|
86
|
+
let found = false;
|
|
87
|
+
this.items().forEach(i => {
|
|
88
|
+
const sel = !!this.value && !found && this.keyOf(i) === this.value;
|
|
89
|
+
if (sel)
|
|
90
|
+
found = true;
|
|
91
|
+
if (i.selected !== sel)
|
|
92
|
+
i.selected = sel;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Roving tabindex: one tabbable item (selected if navigable, else the first). */
|
|
96
|
+
refresh(preferred) {
|
|
97
|
+
const nav = this.navigable();
|
|
98
|
+
const target = preferred && nav.includes(preferred) ? preferred : nav.find(i => i.selected) ?? nav[0];
|
|
99
|
+
this.items().forEach(i => {
|
|
100
|
+
if (i.disabled)
|
|
101
|
+
i.removeAttribute('tabindex');
|
|
102
|
+
else
|
|
103
|
+
i.setAttribute('tabindex', i === target ? '0' : '-1');
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
focusItem(item) {
|
|
107
|
+
if (!item)
|
|
108
|
+
return;
|
|
109
|
+
this.refresh(item);
|
|
110
|
+
item.focus();
|
|
111
|
+
}
|
|
112
|
+
select(item) {
|
|
113
|
+
if (!this.selectable || item.disabled)
|
|
114
|
+
return;
|
|
115
|
+
const key = this.keyOf(item);
|
|
116
|
+
if (key === this.value)
|
|
117
|
+
return;
|
|
118
|
+
this.value = key;
|
|
119
|
+
this.syncSelection();
|
|
120
|
+
this.refresh(item);
|
|
121
|
+
this.bsChange.emit(key);
|
|
122
|
+
}
|
|
123
|
+
onItemSelect(e) {
|
|
124
|
+
const item = e.target.closest('bs-tree-item');
|
|
125
|
+
if (item)
|
|
126
|
+
this.select(item);
|
|
127
|
+
}
|
|
128
|
+
onFocusIn(e) {
|
|
129
|
+
const item = e.target.closest?.('bs-tree-item');
|
|
130
|
+
if (item && !item.disabled && e.target === item)
|
|
131
|
+
this.refresh(item);
|
|
132
|
+
}
|
|
133
|
+
onKeyDown(e) {
|
|
134
|
+
const item = e.target.closest?.('bs-tree-item');
|
|
135
|
+
if (!item || e.target !== item || e.altKey || e.ctrlKey || e.metaKey)
|
|
136
|
+
return;
|
|
137
|
+
const nav = this.navigable();
|
|
138
|
+
const idx = nav.indexOf(item);
|
|
139
|
+
const rtl = getComputedStyle(this.el).direction === 'rtl';
|
|
140
|
+
let key = e.key;
|
|
141
|
+
if (rtl && key === 'ArrowRight')
|
|
142
|
+
key = 'ArrowLeft';
|
|
143
|
+
else if (rtl && key === 'ArrowLeft')
|
|
144
|
+
key = 'ArrowRight';
|
|
145
|
+
const parent = this.isParent(item);
|
|
146
|
+
switch (key) {
|
|
147
|
+
case 'ArrowDown':
|
|
148
|
+
this.focusItem(nav[idx + 1]);
|
|
149
|
+
break;
|
|
150
|
+
case 'ArrowUp':
|
|
151
|
+
if (idx > 0)
|
|
152
|
+
this.focusItem(nav[idx - 1]);
|
|
153
|
+
break;
|
|
154
|
+
case 'Home':
|
|
155
|
+
this.focusItem(nav[0]);
|
|
156
|
+
break;
|
|
157
|
+
case 'End':
|
|
158
|
+
this.focusItem(nav[nav.length - 1]);
|
|
159
|
+
break;
|
|
160
|
+
case 'ArrowRight':
|
|
161
|
+
if (!parent)
|
|
162
|
+
return;
|
|
163
|
+
if (!item.expanded)
|
|
164
|
+
item.setExpanded(true);
|
|
165
|
+
else
|
|
166
|
+
this.focusItem(nav.find(i => i.parentElement === item));
|
|
167
|
+
break;
|
|
168
|
+
case 'ArrowLeft':
|
|
169
|
+
if (parent && item.expanded)
|
|
170
|
+
item.setExpanded(false);
|
|
171
|
+
else {
|
|
172
|
+
const up = item.parentElement;
|
|
173
|
+
if (up && up.tagName === 'BS-TREE-ITEM' && !up.disabled)
|
|
174
|
+
this.focusItem(up);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'Enter':
|
|
178
|
+
case ' ':
|
|
179
|
+
this.select(item);
|
|
180
|
+
break;
|
|
181
|
+
case '*':
|
|
182
|
+
Array.from(item.parentElement.children)
|
|
183
|
+
.filter((c) => c.tagName === 'BS-TREE-ITEM')
|
|
184
|
+
.forEach(sib => sib.setExpanded(true));
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
}
|
|
191
|
+
render() {
|
|
192
|
+
return (h("div", { key: '40bf9c9ae1f488a82f3d2972e5f9206db865928d', part: "container", class: "bs-tree", role: "tree", "aria-label": this.ariaLabel }, h("slot", { key: 'eb72026e90f834759d8249ed1a291aa7ca30ac2d' })));
|
|
193
|
+
}
|
|
194
|
+
static get is() { return "bs-tree"; }
|
|
195
|
+
static get encapsulation() { return "shadow"; }
|
|
196
|
+
static get originalStyleUrls() {
|
|
197
|
+
return {
|
|
198
|
+
"$": ["bs-tree.css"]
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
static get styleUrls() {
|
|
202
|
+
return {
|
|
203
|
+
"$": ["bs-tree.css"]
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
static get properties() {
|
|
207
|
+
return {
|
|
208
|
+
"ariaLabel": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"mutable": false,
|
|
211
|
+
"complexType": {
|
|
212
|
+
"original": "string | null",
|
|
213
|
+
"resolved": "string",
|
|
214
|
+
"references": {}
|
|
215
|
+
},
|
|
216
|
+
"required": false,
|
|
217
|
+
"optional": false,
|
|
218
|
+
"docs": {
|
|
219
|
+
"tags": [],
|
|
220
|
+
"text": "Accessible name of the tree. Bound onto the internal `role=\"tree\"` element (an `aria-label`\non the host would not reach it)."
|
|
221
|
+
},
|
|
222
|
+
"getter": false,
|
|
223
|
+
"setter": false,
|
|
224
|
+
"reflect": false,
|
|
225
|
+
"attribute": "aria-label",
|
|
226
|
+
"defaultValue": "null"
|
|
227
|
+
},
|
|
228
|
+
"value": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"mutable": true,
|
|
231
|
+
"complexType": {
|
|
232
|
+
"original": "string",
|
|
233
|
+
"resolved": "string",
|
|
234
|
+
"references": {}
|
|
235
|
+
},
|
|
236
|
+
"required": false,
|
|
237
|
+
"optional": false,
|
|
238
|
+
"docs": {
|
|
239
|
+
"tags": [],
|
|
240
|
+
"text": "The `value` (or, when empty, the `label`) of the selected item. Two-way synced with each\nitem's `selected` (single selection). Setting it from code does not emit `bsChange`."
|
|
241
|
+
},
|
|
242
|
+
"getter": false,
|
|
243
|
+
"setter": false,
|
|
244
|
+
"reflect": true,
|
|
245
|
+
"attribute": "value",
|
|
246
|
+
"defaultValue": "''"
|
|
247
|
+
},
|
|
248
|
+
"selectable": {
|
|
249
|
+
"type": "boolean",
|
|
250
|
+
"mutable": false,
|
|
251
|
+
"complexType": {
|
|
252
|
+
"original": "boolean",
|
|
253
|
+
"resolved": "boolean",
|
|
254
|
+
"references": {}
|
|
255
|
+
},
|
|
256
|
+
"required": false,
|
|
257
|
+
"optional": false,
|
|
258
|
+
"docs": {
|
|
259
|
+
"tags": [],
|
|
260
|
+
"text": "When `false`, rows do not select (clicks and Enter/Space do nothing) but parents still\nexpand and collapse."
|
|
261
|
+
},
|
|
262
|
+
"getter": false,
|
|
263
|
+
"setter": false,
|
|
264
|
+
"reflect": false,
|
|
265
|
+
"attribute": "selectable",
|
|
266
|
+
"defaultValue": "true"
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
static get events() {
|
|
271
|
+
return [{
|
|
272
|
+
"method": "bsChange",
|
|
273
|
+
"name": "bsChange",
|
|
274
|
+
"bubbles": true,
|
|
275
|
+
"cancelable": true,
|
|
276
|
+
"composed": true,
|
|
277
|
+
"docs": {
|
|
278
|
+
"tags": [],
|
|
279
|
+
"text": "Emitted when the user changes the selection (click, Enter/Space), with the new value. Not\nemitted when `value` is set from code."
|
|
280
|
+
},
|
|
281
|
+
"complexType": {
|
|
282
|
+
"original": "string",
|
|
283
|
+
"resolved": "string",
|
|
284
|
+
"references": {}
|
|
285
|
+
}
|
|
286
|
+
}];
|
|
287
|
+
}
|
|
288
|
+
static get methods() {
|
|
289
|
+
return {
|
|
290
|
+
"expandAll": {
|
|
291
|
+
"complexType": {
|
|
292
|
+
"signature": "() => Promise<void>",
|
|
293
|
+
"parameters": [],
|
|
294
|
+
"references": {
|
|
295
|
+
"Promise": {
|
|
296
|
+
"location": "global",
|
|
297
|
+
"id": "global::Promise"
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"return": "Promise<void>"
|
|
301
|
+
},
|
|
302
|
+
"docs": {
|
|
303
|
+
"text": "Expands every parent item.",
|
|
304
|
+
"tags": []
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"collapseAll": {
|
|
308
|
+
"complexType": {
|
|
309
|
+
"signature": "() => Promise<void>",
|
|
310
|
+
"parameters": [],
|
|
311
|
+
"references": {
|
|
312
|
+
"Promise": {
|
|
313
|
+
"location": "global",
|
|
314
|
+
"id": "global::Promise"
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"return": "Promise<void>"
|
|
318
|
+
},
|
|
319
|
+
"docs": {
|
|
320
|
+
"text": "Collapses every parent item.",
|
|
321
|
+
"tags": []
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
static get elementRef() { return "el"; }
|
|
327
|
+
static get watchers() {
|
|
328
|
+
return [{
|
|
329
|
+
"propName": "value",
|
|
330
|
+
"methodName": "onValueChange"
|
|
331
|
+
}];
|
|
332
|
+
}
|
|
333
|
+
static get listeners() {
|
|
334
|
+
return [{
|
|
335
|
+
"name": "bsSelect",
|
|
336
|
+
"method": "onItemSelect",
|
|
337
|
+
"target": undefined,
|
|
338
|
+
"capture": false,
|
|
339
|
+
"passive": false
|
|
340
|
+
}, {
|
|
341
|
+
"name": "focusin",
|
|
342
|
+
"method": "onFocusIn",
|
|
343
|
+
"target": undefined,
|
|
344
|
+
"capture": false,
|
|
345
|
+
"passive": false
|
|
346
|
+
}, {
|
|
347
|
+
"name": "keydown",
|
|
348
|
+
"method": "onKeyDown",
|
|
349
|
+
"target": undefined,
|
|
350
|
+
"capture": false,
|
|
351
|
+
"passive": false
|
|
352
|
+
}];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { componentDescription } from "../../stories-utils";
|
|
3
|
+
const items = html `
|
|
4
|
+
<bs-tree-item label="src" value="src" expanded>
|
|
5
|
+
<bs-tree-item label="components" value="components" expanded>
|
|
6
|
+
<bs-tree-item label="button.tsx" value="button"></bs-tree-item>
|
|
7
|
+
<bs-tree-item label="input.tsx" value="input"></bs-tree-item>
|
|
8
|
+
</bs-tree-item>
|
|
9
|
+
<bs-tree-item label="index.ts" value="index"></bs-tree-item>
|
|
10
|
+
</bs-tree-item>
|
|
11
|
+
<bs-tree-item label="docs" value="docs">
|
|
12
|
+
<bs-tree-item label="readme.md" value="readme"></bs-tree-item>
|
|
13
|
+
</bs-tree-item>
|
|
14
|
+
<bs-tree-item label="archive" value="archive" disabled></bs-tree-item>
|
|
15
|
+
<bs-tree-item label="package.json" value="package"></bs-tree-item>
|
|
16
|
+
`;
|
|
17
|
+
const meta = {
|
|
18
|
+
title: 'Components/bs-tree',
|
|
19
|
+
parameters: { docs: { description: { component: componentDescription('bs-tree') } } },
|
|
20
|
+
render: args => html `
|
|
21
|
+
<div style="width: 360px">
|
|
22
|
+
<bs-tree ariaLabel="Project files" .value=${args.value} .selectable=${args.selectable}>${items}</bs-tree>
|
|
23
|
+
</div>
|
|
24
|
+
`,
|
|
25
|
+
argTypes: {
|
|
26
|
+
value: { control: 'text' },
|
|
27
|
+
selectable: { control: 'boolean' },
|
|
28
|
+
},
|
|
29
|
+
args: { value: '', selectable: true },
|
|
30
|
+
};
|
|
31
|
+
export default meta;
|
|
32
|
+
export const Default = {};
|
|
33
|
+
export const ControlledValue = { args: { value: 'button' } };
|
|
34
|
+
export const NonSelectable = { args: { selectable: false } };
|