@geoffcox/sterling-svelte 2.0.2 → 2.0.4
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 +18 -0
- package/dist/@types/clickOutside.d.ts +15 -0
- package/dist/Button.svelte +29 -0
- package/dist/Button.svelte.d.ts +8 -0
- package/dist/Callout.svelte +243 -0
- package/dist/Callout.svelte.d.ts +14 -0
- package/dist/Callout.types.d.ts +11 -0
- package/dist/Callout.types.js +1 -0
- package/dist/Checkbox.svelte +62 -0
- package/dist/Checkbox.svelte.d.ts +9 -0
- package/dist/Dialog.svelte +201 -0
- package/dist/Dialog.svelte.d.ts +14 -0
- package/dist/Dropdown.svelte +159 -0
- package/dist/Dropdown.svelte.d.ts +23 -0
- package/dist/Input.svelte +80 -0
- package/dist/Input.svelte.d.ts +11 -0
- package/dist/Label.constants.d.ts +2 -0
- package/dist/Label.constants.js +2 -0
- package/dist/Label.svelte +135 -0
- package/dist/Label.svelte.d.ts +17 -0
- package/dist/Link.svelte +31 -0
- package/dist/Link.svelte.d.ts +11 -0
- package/dist/List.constants.d.ts +1 -0
- package/dist/List.constants.js +1 -0
- package/dist/List.svelte +258 -0
- package/dist/List.svelte.d.ts +19 -0
- package/dist/List.types.d.ts +5 -0
- package/dist/List.types.js +1 -0
- package/dist/ListItem.svelte +64 -0
- package/dist/ListItem.svelte.d.ts +12 -0
- package/dist/Menu.svelte +105 -0
- package/dist/Menu.svelte.d.ts +12 -0
- package/dist/MenuBar.constants.d.ts +1 -0
- package/dist/MenuBar.constants.js +1 -0
- package/dist/MenuBar.svelte +144 -0
- package/dist/MenuBar.svelte.d.ts +12 -0
- package/dist/MenuBar.types.d.ts +4 -0
- package/dist/MenuBar.types.js +1 -0
- package/dist/MenuButton.svelte +156 -0
- package/dist/MenuButton.svelte.d.ts +20 -0
- package/dist/MenuItem.constants.d.ts +2 -0
- package/dist/MenuItem.constants.js +2 -0
- package/dist/MenuItem.svelte +431 -0
- package/dist/MenuItem.svelte.d.ts +22 -0
- package/dist/MenuItem.types.d.ts +20 -0
- package/dist/MenuItem.types.js +1 -0
- package/dist/MenuItem.utils.d.ts +7 -0
- package/dist/MenuItem.utils.js +36 -0
- package/dist/MenuSeparator.svelte +11 -0
- package/dist/MenuSeparator.svelte.d.ts +5 -0
- package/dist/Pagination.svelte +267 -0
- package/dist/Pagination.svelte.d.ts +4 -0
- package/dist/Pagination.types.d.ts +24 -0
- package/dist/Pagination.types.js +1 -0
- package/dist/Popover.constants.d.ts +1 -0
- package/dist/Popover.constants.js +14 -0
- package/dist/Popover.svelte +175 -0
- package/dist/Popover.svelte.d.ts +14 -0
- package/dist/Popover.types.d.ts +4 -0
- package/dist/Popover.types.js +1 -0
- package/dist/Portal.constants.d.ts +2 -0
- package/dist/Portal.constants.js +2 -0
- package/dist/Portal.types.d.ts +3 -0
- package/dist/Portal.types.js +1 -0
- package/dist/Progress.constants.d.ts +1 -0
- package/dist/Progress.constants.js +1 -0
- package/dist/Progress.svelte +61 -0
- package/dist/Progress.svelte.d.ts +11 -0
- package/dist/Progress.types.d.ts +4 -0
- package/dist/Progress.types.js +1 -0
- package/dist/Radio.svelte +65 -0
- package/dist/Radio.svelte.d.ts +12 -0
- package/dist/Select.svelte +262 -0
- package/dist/Select.svelte.d.ts +26 -0
- package/dist/Slider.svelte +182 -0
- package/dist/Slider.svelte.d.ts +18 -0
- package/dist/Switch.svelte +92 -0
- package/dist/Switch.svelte.d.ts +21 -0
- package/dist/Tab.svelte +66 -0
- package/dist/Tab.svelte.d.ts +11 -0
- package/dist/TabList.constants.d.ts +1 -0
- package/dist/TabList.constants.js +1 -0
- package/dist/TabList.svelte +253 -0
- package/dist/TabList.svelte.d.ts +17 -0
- package/dist/TabList.types.d.ts +5 -0
- package/dist/TabList.types.js +1 -0
- package/dist/TextArea.constants.d.ts +1 -0
- package/dist/TextArea.constants.js +1 -0
- package/dist/TextArea.svelte +116 -0
- package/dist/TextArea.svelte.d.ts +18 -0
- package/dist/TextArea.types.d.ts +4 -0
- package/dist/TextArea.types.js +1 -0
- package/dist/Tooltip.svelte +95 -0
- package/dist/Tooltip.svelte.d.ts +10 -0
- package/dist/Tree.constants.d.ts +1 -0
- package/dist/Tree.constants.js +1 -0
- package/dist/Tree.svelte +81 -0
- package/dist/Tree.svelte.d.ts +14 -0
- package/dist/Tree.types.d.ts +5 -0
- package/dist/Tree.types.js +1 -0
- package/dist/TreeChevron.svelte +39 -0
- package/dist/TreeChevron.svelte.d.ts +8 -0
- package/dist/TreeItem.constants.d.ts +1 -0
- package/dist/TreeItem.constants.js +1 -0
- package/dist/TreeItem.svelte +396 -0
- package/dist/TreeItem.svelte.d.ts +22 -0
- package/dist/TreeItem.types.d.ts +4 -0
- package/dist/TreeItem.types.js +1 -0
- package/dist/actions/applyLightDarkMode.d.ts +11 -0
- package/dist/actions/applyLightDarkMode.js +37 -0
- package/dist/actions/clickOutside.d.ts +15 -0
- package/dist/actions/clickOutside.js +28 -0
- package/dist/actions/colorScheme.d.ts +8 -0
- package/dist/actions/colorScheme.js +26 -0
- package/dist/actions/extraClass.d.ts +9 -0
- package/dist/actions/extraClass.js +15 -0
- package/dist/actions/forwardEvents.d.ts +12 -0
- package/dist/actions/forwardEvents.js +32 -0
- package/dist/actions/portal.d.ts +8 -0
- package/dist/actions/portal.js +19 -0
- package/dist/actions/trapKeyboardFocus.d.ts +3 -0
- package/dist/actions/trapKeyboardFocus.js +52 -0
- package/dist/idGenerator.d.ts +5 -0
- package/dist/idGenerator.js +11 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +56 -0
- package/dist/mediaQueries/prefersColorSchemeDark.d.ts +1 -0
- package/dist/mediaQueries/prefersColorSchemeDark.js +14 -0
- package/dist/mediaQueries/prefersReducedMotion.d.ts +1 -0
- package/dist/mediaQueries/prefersReducedMotion.js +14 -0
- package/dist/mediaQueries/usingKeyboard.d.ts +1 -0
- package/dist/mediaQueries/usingKeyboard.js +17 -0
- package/package.json +17 -13
package/dist/Tree.svelte
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<svelte:options runes={true} />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { setContext } from 'svelte';
|
|
5
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
6
|
+
import { usingKeyboard } from './mediaQueries/usingKeyboard';
|
|
7
|
+
import { TREE_CONTEXT_KEY } from './Tree.constants';
|
|
8
|
+
import type { TreeContext } from './Tree.types';
|
|
9
|
+
|
|
10
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
11
|
+
disabled?: boolean | null;
|
|
12
|
+
expandedValues?: string[];
|
|
13
|
+
selectedValue?: string;
|
|
14
|
+
onExpandCollapse?: (expandedValues: string[]) => void;
|
|
15
|
+
onSelect?: (selectedValue: string | undefined) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
children,
|
|
20
|
+
class: _class,
|
|
21
|
+
disabled = false,
|
|
22
|
+
expandedValues = $bindable([]),
|
|
23
|
+
onExpandCollapse,
|
|
24
|
+
onSelect,
|
|
25
|
+
selectedValue = $bindable(undefined),
|
|
26
|
+
...rest
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
let treeRef: HTMLDivElement;
|
|
30
|
+
|
|
31
|
+
let treeContext = {
|
|
32
|
+
get disabled() {
|
|
33
|
+
return disabled;
|
|
34
|
+
},
|
|
35
|
+
get expandedValues() {
|
|
36
|
+
return expandedValues;
|
|
37
|
+
},
|
|
38
|
+
set expandedValues(value: string[]) {
|
|
39
|
+
expandedValues = value;
|
|
40
|
+
},
|
|
41
|
+
get selectedValue() {
|
|
42
|
+
return selectedValue;
|
|
43
|
+
},
|
|
44
|
+
set selectedValue(value: string | undefined) {
|
|
45
|
+
selectedValue = value;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
setContext<TreeContext>(TREE_CONTEXT_KEY, treeContext);
|
|
50
|
+
|
|
51
|
+
$effect(() => {
|
|
52
|
+
onSelect?.(selectedValue);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
$effect(() => {
|
|
56
|
+
onExpandCollapse?.(expandedValues);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const blur = () => {
|
|
60
|
+
treeRef?.blur();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const focus = (options?: FocusOptions) => {
|
|
64
|
+
treeRef?.focus(options);
|
|
65
|
+
};
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<div
|
|
69
|
+
bind:this={treeRef}
|
|
70
|
+
aria-disabled={disabled}
|
|
71
|
+
class={['sterling-tree', _class]}
|
|
72
|
+
class:disabled
|
|
73
|
+
class:using-keyboard={$usingKeyboard}
|
|
74
|
+
role="tree"
|
|
75
|
+
tabindex="0"
|
|
76
|
+
{...rest}
|
|
77
|
+
>
|
|
78
|
+
<div class="container">
|
|
79
|
+
{@render children?.()}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
3
|
+
disabled?: boolean | null;
|
|
4
|
+
expandedValues?: string[];
|
|
5
|
+
selectedValue?: string;
|
|
6
|
+
onExpandCollapse?: (expandedValues: string[]) => void;
|
|
7
|
+
onSelect?: (selectedValue: string | undefined) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const Tree: import("svelte").Component<Props, {
|
|
10
|
+
blur: () => void;
|
|
11
|
+
focus: (options?: FocusOptions) => void;
|
|
12
|
+
}, "selectedValue" | "expandedValues">;
|
|
13
|
+
type Tree = ReturnType<typeof Tree>;
|
|
14
|
+
export default Tree;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<svelte:options runes={true} />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
6
|
+
|
|
7
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
8
|
+
expanded?: boolean | null | undefined;
|
|
9
|
+
hasChildren?: boolean | null | undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let { class: _class, expanded = false, hasChildren = false, ...rest }: Props = $props();
|
|
13
|
+
|
|
14
|
+
let previousExpanded = expanded;
|
|
15
|
+
let mounted = false;
|
|
16
|
+
|
|
17
|
+
onMount(() => {
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
mounted = true;
|
|
20
|
+
}, 0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let animate = $state(false);
|
|
24
|
+
|
|
25
|
+
$effect(() => {
|
|
26
|
+
if (expanded !== previousExpanded && mounted) {
|
|
27
|
+
animate = true;
|
|
28
|
+
}
|
|
29
|
+
previousExpanded = expanded;
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
class={['sterling-tree-chevron', _class]}
|
|
35
|
+
class:leaf={!hasChildren}
|
|
36
|
+
class:animate
|
|
37
|
+
class:expanded
|
|
38
|
+
{...rest}
|
|
39
|
+
></div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
3
|
+
expanded?: boolean | null | undefined;
|
|
4
|
+
hasChildren?: boolean | null | undefined;
|
|
5
|
+
};
|
|
6
|
+
declare const TreeChevron: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type TreeChevron = ReturnType<typeof TreeChevron>;
|
|
8
|
+
export default TreeChevron;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TREE_ITEM_CONTEXT_KEY = "sterlingTreeItem";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TREE_ITEM_CONTEXT_KEY = 'sterlingTreeItem';
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
<svelte:options runes={true} />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { getContext, setContext, type Snippet } from 'svelte';
|
|
5
|
+
import type { HTMLAttributes, KeyboardEventHandler, MouseEventHandler } from 'svelte/elements';
|
|
6
|
+
import type { SlideParams, TransitionConfig } from 'svelte/transition';
|
|
7
|
+
import { slide } from 'svelte/transition';
|
|
8
|
+
import { prefersReducedMotion } from './mediaQueries/prefersReducedMotion';
|
|
9
|
+
import { TREE_CONTEXT_KEY } from './Tree.constants';
|
|
10
|
+
import type { TreeContext } from './Tree.types';
|
|
11
|
+
import TreeChevron from './TreeChevron.svelte';
|
|
12
|
+
import { TREE_ITEM_CONTEXT_KEY } from './TreeItem.constants';
|
|
13
|
+
import type { TreeItemContext } from './TreeItem.types';
|
|
14
|
+
|
|
15
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
16
|
+
disabled?: boolean | null | undefined;
|
|
17
|
+
icon?: Snippet;
|
|
18
|
+
label?: string | Snippet;
|
|
19
|
+
value: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
children,
|
|
24
|
+
class: _class,
|
|
25
|
+
disabled = false,
|
|
26
|
+
icon,
|
|
27
|
+
label,
|
|
28
|
+
style,
|
|
29
|
+
value,
|
|
30
|
+
...rest
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
|
|
33
|
+
const slideNoOp = (node: Element, params?: SlideParams): TransitionConfig => {
|
|
34
|
+
return { delay: 0, duration: 0 };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let slideMotion = $derived(!$prefersReducedMotion ? slide : slideNoOp);
|
|
38
|
+
|
|
39
|
+
const treeContext = getContext<TreeContext>(TREE_CONTEXT_KEY) || {
|
|
40
|
+
disabled: false,
|
|
41
|
+
expandedValues: [],
|
|
42
|
+
selectedValue: null
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const treeItemContext = getContext<TreeItemContext>(TREE_ITEM_CONTEXT_KEY) || {
|
|
46
|
+
depth: 0,
|
|
47
|
+
disabled: false
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let _disabled = $derived(disabled || treeItemContext.disabled || treeContext.disabled);
|
|
51
|
+
|
|
52
|
+
// Using $derived would be preferred, but this helps avoid
|
|
53
|
+
// updates to every tree item when expandedValues changes.
|
|
54
|
+
let expanded = $state(treeContext.expandedValues?.includes(value));
|
|
55
|
+
$effect(() => {
|
|
56
|
+
let expandedCandidate = treeContext.expandedValues?.includes(value);
|
|
57
|
+
if (expandedCandidate !== expanded) {
|
|
58
|
+
expanded = expandedCandidate;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Using $derived would be preferred, but this helps avoid
|
|
63
|
+
// updates to every list item when selectedValue changes.
|
|
64
|
+
let selected = $state(treeContext.selectedValue === value);
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (treeContext.selectedValue === value && !selected) {
|
|
67
|
+
selected = true;
|
|
68
|
+
} else if (treeContext.selectedValue !== value && selected) {
|
|
69
|
+
selected = false;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let treeItemRef: HTMLDivElement;
|
|
74
|
+
let itemRef: HTMLDivElement;
|
|
75
|
+
|
|
76
|
+
let treeItemChildContext: TreeItemContext = {
|
|
77
|
+
get disabled() {
|
|
78
|
+
return _disabled;
|
|
79
|
+
},
|
|
80
|
+
get depth() {
|
|
81
|
+
return treeItemContext.depth ? treeItemContext.depth + 1 : 1;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
setContext<TreeItemContext>(TREE_ITEM_CONTEXT_KEY, treeItemChildContext);
|
|
86
|
+
|
|
87
|
+
const collapseItem = (index?: number) => {
|
|
88
|
+
if (!_disabled) {
|
|
89
|
+
index =
|
|
90
|
+
index ?? treeContext.expandedValues.findIndex((expandedValue) => expandedValue === value);
|
|
91
|
+
if (index !== -1) {
|
|
92
|
+
treeContext.expandedValues = [
|
|
93
|
+
...treeContext.expandedValues.slice(0, index),
|
|
94
|
+
...treeContext.expandedValues.slice(index + 1)
|
|
95
|
+
];
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return false;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const collapse = () => collapseItem();
|
|
104
|
+
|
|
105
|
+
const expandItem = (index?: number) => {
|
|
106
|
+
if (!_disabled) {
|
|
107
|
+
index =
|
|
108
|
+
index ?? treeContext.expandedValues.findIndex((expandedValue) => expandedValue === value);
|
|
109
|
+
if (index === -1) {
|
|
110
|
+
treeContext.expandedValues = [...treeContext.expandedValues, value];
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const expand = () => expandItem();
|
|
119
|
+
|
|
120
|
+
export const toggleExpanded = () => {
|
|
121
|
+
if (!_disabled && children) {
|
|
122
|
+
const index = treeContext.expandedValues.findIndex(
|
|
123
|
+
(expandedValue) => expandedValue === value
|
|
124
|
+
);
|
|
125
|
+
return index !== -1 ? collapseItem(index) : expandItem(index);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const blurItem = (treeItemElement: Element) => {
|
|
132
|
+
if (!_disabled) {
|
|
133
|
+
const item = treeItemElement?.querySelector<HTMLElement>('.item');
|
|
134
|
+
item?.blur();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const blur = () => {
|
|
139
|
+
blurItem(treeItemRef);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const focusItem = (treeItemElement: Element, options?: FocusOptions) => {
|
|
143
|
+
if (!_disabled) {
|
|
144
|
+
const item = treeItemElement as HTMLElement;
|
|
145
|
+
item?.focus(options);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const focus = (options?: FocusOptions) => {
|
|
150
|
+
focusItem(treeItemRef);
|
|
151
|
+
treeItemRef?.focus(options);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const clickItem = (treeItemElement: Element) => {
|
|
155
|
+
if (!_disabled) {
|
|
156
|
+
const item = treeItemElement as HTMLElement;
|
|
157
|
+
item?.click();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const click = () => {
|
|
162
|
+
clickItem(treeItemRef);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const selectItemByValue = (value: string) => {
|
|
166
|
+
if (!_disabled) {
|
|
167
|
+
treeContext.selectedValue = value;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const select = () => {
|
|
172
|
+
if (!_disabled) {
|
|
173
|
+
selectItemByValue(value);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const selectParent = () => {
|
|
178
|
+
if (!_disabled) {
|
|
179
|
+
let candidate = treeItemRef.parentElement?.closest<Element>('[role="treeitem"][data-value]');
|
|
180
|
+
let candidateValue = candidate?.getAttribute('data-value');
|
|
181
|
+
|
|
182
|
+
if (candidateValue && candidate) {
|
|
183
|
+
selectItemByValue(candidateValue);
|
|
184
|
+
focusItem(candidate);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return false;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const selectPrevious = () => {
|
|
193
|
+
if (!_disabled) {
|
|
194
|
+
let candidate: Element | undefined | null = undefined;
|
|
195
|
+
let candidateValue: string | null | undefined = undefined;
|
|
196
|
+
|
|
197
|
+
const previousSibling = treeItemRef?.previousElementSibling;
|
|
198
|
+
if (previousSibling) {
|
|
199
|
+
// look for the last (recursive) decendant of ths previous sibling
|
|
200
|
+
const decendants = previousSibling.querySelectorAll('[role="treeitem"][data-value]');
|
|
201
|
+
if (decendants) {
|
|
202
|
+
candidate = decendants[decendants.length - 1];
|
|
203
|
+
candidateValue = candidate?.getAttribute('data-value');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// look for the previous sibling
|
|
207
|
+
if (!candidateValue) {
|
|
208
|
+
candidate = previousSibling;
|
|
209
|
+
candidateValue = candidate?.getAttribute('data-value');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// look for the parent
|
|
213
|
+
if (!candidateValue) {
|
|
214
|
+
candidate = treeItemRef.parentElement?.closest<Element>('[role="treeitem"][data-value]');
|
|
215
|
+
candidateValue = candidate?.getAttribute('data-value');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (candidateValue && candidate) {
|
|
219
|
+
selectItemByValue(candidateValue);
|
|
220
|
+
focusItem(candidate);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return false;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const selectNext = () => {
|
|
229
|
+
if (!_disabled) {
|
|
230
|
+
let candidateValue: string | null | undefined = undefined;
|
|
231
|
+
|
|
232
|
+
// look for decendants
|
|
233
|
+
let candidate = treeItemRef.querySelector('[role="treeitem"][data-value]');
|
|
234
|
+
candidateValue = candidate?.getAttribute('data-value');
|
|
235
|
+
|
|
236
|
+
// look for next sibling
|
|
237
|
+
if (!candidateValue) {
|
|
238
|
+
candidate = treeItemRef.nextElementSibling;
|
|
239
|
+
while (candidate && candidate.getAttribute('data-value') === null) {
|
|
240
|
+
candidate = candidate.nextElementSibling;
|
|
241
|
+
}
|
|
242
|
+
candidateValue = candidate?.getAttribute('data-value');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// look for next sibling of ancestor
|
|
246
|
+
if (!candidateValue) {
|
|
247
|
+
let ancestor: Element | null | undefined = treeItemRef.parentElement?.closest<Element>(
|
|
248
|
+
'[role="treeitem"][data-value]'
|
|
249
|
+
);
|
|
250
|
+
while (ancestor && !candidateValue) {
|
|
251
|
+
candidate = ancestor?.nextElementSibling;
|
|
252
|
+
candidateValue = candidate?.getAttribute('data-value');
|
|
253
|
+
ancestor = ancestor.parentElement?.closest<Element>('[role="treeitem"][data-value]');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (candidateValue && candidate) {
|
|
258
|
+
selectItemByValue(candidateValue);
|
|
259
|
+
focusItem(candidate);
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return false;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const onClick: MouseEventHandler<HTMLDivElement> = (event) => {
|
|
268
|
+
const eventTarget = event.target as Node;
|
|
269
|
+
if (!_disabled && itemRef.contains(eventTarget)) {
|
|
270
|
+
toggleExpanded();
|
|
271
|
+
select();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
rest.onclick?.(event);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const onKeydown: KeyboardEventHandler<HTMLDivElement> = (event) => {
|
|
279
|
+
if (!event.altKey && !event.ctrlKey && !event.shiftKey) {
|
|
280
|
+
switch (event.key) {
|
|
281
|
+
case 'Enter':
|
|
282
|
+
case ' ':
|
|
283
|
+
if (toggleExpanded()) {
|
|
284
|
+
event.preventDefault();
|
|
285
|
+
event.stopPropagation();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
case 'ArrowRight':
|
|
290
|
+
/*
|
|
291
|
+
When focus is on a closed item, opens the item; focus does not move.
|
|
292
|
+
When focus is on an open item, moves focus to the first child item.
|
|
293
|
+
When focus is on an end item (a tree item with no children), does nothing.
|
|
294
|
+
*/
|
|
295
|
+
if (children) {
|
|
296
|
+
if (expanded) {
|
|
297
|
+
if (selectNext()) {
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
event.stopPropagation();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
} else if (expandItem()) {
|
|
303
|
+
event.preventDefault();
|
|
304
|
+
event.stopPropagation();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
case 'ArrowLeft':
|
|
310
|
+
/*
|
|
311
|
+
When focus is on an open item, closes the item.
|
|
312
|
+
When focus is on a child item that is also either an end item or a closed item, moves focus to its parent item.
|
|
313
|
+
When focus is on a closed `tree`, does nothing.
|
|
314
|
+
*/
|
|
315
|
+
if (expanded && children) {
|
|
316
|
+
if (collapseItem()) {
|
|
317
|
+
event.preventDefault();
|
|
318
|
+
event.stopPropagation();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
} else if (selectParent()) {
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
event.stopPropagation();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
case 'ArrowUp':
|
|
328
|
+
/*
|
|
329
|
+
Moves focus to the previous item that is focusable without opening or closing a item.
|
|
330
|
+
*/
|
|
331
|
+
if (selectPrevious()) {
|
|
332
|
+
event.preventDefault();
|
|
333
|
+
event.stopPropagation();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
case 'ArrowDown':
|
|
338
|
+
/*
|
|
339
|
+
Moves focus to the next item that is focusable without opening or closing a item.
|
|
340
|
+
*/
|
|
341
|
+
if (selectNext()) {
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
event.stopPropagation();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
rest.onkeydown?.(event);
|
|
351
|
+
};
|
|
352
|
+
</script>
|
|
353
|
+
|
|
354
|
+
<div
|
|
355
|
+
bind:this={treeItemRef}
|
|
356
|
+
aria-selected={selected ? true : undefined}
|
|
357
|
+
aria-expanded={expanded}
|
|
358
|
+
class={['sterling-tree-item', _class]}
|
|
359
|
+
class:disabled={_disabled}
|
|
360
|
+
class:expanded
|
|
361
|
+
class:item-disabled={disabled}
|
|
362
|
+
class:leaf={!children}
|
|
363
|
+
class:parent-disabled={treeItemContext.disabled}
|
|
364
|
+
class:selected
|
|
365
|
+
class:tree-disabled={treeContext.disabled}
|
|
366
|
+
data-value={value}
|
|
367
|
+
role="treeitem"
|
|
368
|
+
tabindex={selected ? 0 : -1}
|
|
369
|
+
{...rest}
|
|
370
|
+
onclick={onClick}
|
|
371
|
+
onkeydown={onKeydown}
|
|
372
|
+
style={`--sterling-tree-item-depth: ${treeItemContext.depth}; ${style}`}
|
|
373
|
+
>
|
|
374
|
+
<!-- TODO: In RTL consider position of icon and label get reversed -->
|
|
375
|
+
<div bind:this={itemRef} class="item" class:selected>
|
|
376
|
+
{#if icon}
|
|
377
|
+
{@render icon()}
|
|
378
|
+
{:else}
|
|
379
|
+
<TreeChevron {expanded} hasChildren={!!children} />
|
|
380
|
+
{/if}
|
|
381
|
+
{#if label}
|
|
382
|
+
{#if typeof label === 'string'}
|
|
383
|
+
{label}
|
|
384
|
+
{:else}
|
|
385
|
+
{@render label()}
|
|
386
|
+
{/if}
|
|
387
|
+
{:else}
|
|
388
|
+
{value}
|
|
389
|
+
{/if}
|
|
390
|
+
</div>
|
|
391
|
+
{#if expanded && children}
|
|
392
|
+
<div class="children" transition:slideMotion|global={{ duration: 200 }} role="group">
|
|
393
|
+
{@render children?.()}
|
|
394
|
+
</div>
|
|
395
|
+
{/if}
|
|
396
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
disabled?: boolean | null | undefined;
|
|
5
|
+
icon?: Snippet;
|
|
6
|
+
label?: string | Snippet;
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
declare const TreeItem: import("svelte").Component<Props, {
|
|
10
|
+
collapse: () => boolean;
|
|
11
|
+
expand: () => boolean;
|
|
12
|
+
toggleExpanded: () => boolean;
|
|
13
|
+
blur: () => void;
|
|
14
|
+
focus: (options?: FocusOptions) => void;
|
|
15
|
+
click: () => void;
|
|
16
|
+
select: () => void;
|
|
17
|
+
selectParent: () => boolean;
|
|
18
|
+
selectPrevious: () => boolean;
|
|
19
|
+
selectNext: () => boolean;
|
|
20
|
+
}, "">;
|
|
21
|
+
type TreeItem = ReturnType<typeof TreeItem>;
|
|
22
|
+
export default TreeItem;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Mode = 'auto' | 'light' | 'dark';
|
|
2
|
+
type Params = {
|
|
3
|
+
atDocumentRoot?: boolean;
|
|
4
|
+
mode?: Mode;
|
|
5
|
+
};
|
|
6
|
+
/** @deprecated Use colorScheme action instead with sterling-svelte-themes 2.x */
|
|
7
|
+
export declare const applyLightDarkMode: (node: HTMLElement, params?: Params) => {
|
|
8
|
+
destroy(): void;
|
|
9
|
+
update(params?: Params): void;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { prefersColorSchemeDark } from '../mediaQueries/prefersColorSchemeDark';
|
|
2
|
+
const modes = ['light-mode', 'dark-mode'];
|
|
3
|
+
const addMode = (element, mode, prefersDark) => {
|
|
4
|
+
const darkMode = mode === 'dark' || (mode === 'auto' && prefersDark);
|
|
5
|
+
element.classList.remove(...modes);
|
|
6
|
+
element.classList.add(modes[darkMode ? 1 : 0]);
|
|
7
|
+
};
|
|
8
|
+
const clearModes = (element) => {
|
|
9
|
+
element.classList.remove(...modes);
|
|
10
|
+
};
|
|
11
|
+
/** @deprecated Use colorScheme action instead with sterling-svelte-themes 2.x */
|
|
12
|
+
export const applyLightDarkMode = (node, params) => {
|
|
13
|
+
let mode = params?.mode || 'auto';
|
|
14
|
+
let prefersDark = false;
|
|
15
|
+
let atDocumentRoot = params?.atDocumentRoot === true;
|
|
16
|
+
let target = atDocumentRoot ? document.documentElement : node;
|
|
17
|
+
const unsubscribe = prefersColorSchemeDark.subscribe((value) => {
|
|
18
|
+
prefersDark = value;
|
|
19
|
+
addMode(target, mode, prefersDark);
|
|
20
|
+
});
|
|
21
|
+
addMode(target, mode, prefersDark);
|
|
22
|
+
return {
|
|
23
|
+
destroy() {
|
|
24
|
+
unsubscribe();
|
|
25
|
+
},
|
|
26
|
+
update(params) {
|
|
27
|
+
// if changing the target, then clear from previous target
|
|
28
|
+
if (atDocumentRoot !== params?.atDocumentRoot) {
|
|
29
|
+
clearModes(target);
|
|
30
|
+
}
|
|
31
|
+
mode = params?.mode || 'auto';
|
|
32
|
+
atDocumentRoot = params?.atDocumentRoot === true;
|
|
33
|
+
target = atDocumentRoot ? document.documentElement : node;
|
|
34
|
+
addMode(target, mode, prefersDark);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type Params = {
|
|
2
|
+
/** Other elements should not raise the event if clicked. */
|
|
3
|
+
ignoreOthers?: HTMLElement[];
|
|
4
|
+
onclickoutside?: (mouseEvent: MouseEvent) => void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Raises the click_outside event when the user clicks outside the node.
|
|
8
|
+
* @param node The node to check and receive the event.
|
|
9
|
+
* @param params Additional parameters
|
|
10
|
+
*/
|
|
11
|
+
export declare const clickOutside: (node: HTMLElement, params?: Params) => {
|
|
12
|
+
update(params: Params): void;
|
|
13
|
+
destroy(): void;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raises the click_outside event when the user clicks outside the node.
|
|
3
|
+
* @param node The node to check and receive the event.
|
|
4
|
+
* @param params Additional parameters
|
|
5
|
+
*/
|
|
6
|
+
export const clickOutside = (node, params) => {
|
|
7
|
+
let ignoreOthers = params?.ignoreOthers;
|
|
8
|
+
const handleClick = (event) => {
|
|
9
|
+
const targetNode = event?.target;
|
|
10
|
+
if (targetNode &&
|
|
11
|
+
!node.contains(targetNode) &&
|
|
12
|
+
(!ignoreOthers || ignoreOthers.filter(Boolean).every((x) => !x.contains(targetNode)))) {
|
|
13
|
+
params?.onclickoutside?.(event);
|
|
14
|
+
node.dispatchEvent(new CustomEvent('click_outside', {
|
|
15
|
+
detail: { mouseEvent: event }
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
document.addEventListener('click', handleClick, true);
|
|
20
|
+
return {
|
|
21
|
+
update(params) {
|
|
22
|
+
ignoreOthers = params.ignoreOthers;
|
|
23
|
+
},
|
|
24
|
+
destroy() {
|
|
25
|
+
document.removeEventListener('click', handleClick, true);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
};
|