fluent-svelte-extra 1.0.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/.prettierignore +1 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/package.json +83 -0
- package/src/app.html +12 -0
- package/src/global.d.ts +1 -0
- package/src/lib/AutoSuggestBox/AutoSuggestBox.scss +44 -0
- package/src/lib/AutoSuggestBox/AutoSuggestBox.svelte +173 -0
- package/src/lib/Button/Button.scss +94 -0
- package/src/lib/Button/Button.svelte +48 -0
- package/src/lib/CalendarDatePicker/CalendarDatePicker.scss +15 -0
- package/src/lib/CalendarDatePicker/CalendarDatePicker.svelte +86 -0
- package/src/lib/CalendarView/CalendarView.scss +156 -0
- package/src/lib/CalendarView/CalendarView.svelte +753 -0
- package/src/lib/CalendarView/CalendarViewItem.scss +130 -0
- package/src/lib/CalendarView/CalendarViewItem.svelte +33 -0
- package/src/lib/Checkbox/Checkbox.scss +117 -0
- package/src/lib/Checkbox/Checkbox.svelte +81 -0
- package/src/lib/ComboBox/ComboBox.scss +152 -0
- package/src/lib/ComboBox/ComboBox.svelte +360 -0
- package/src/lib/ComboBox/ComboBoxItem.scss +80 -0
- package/src/lib/ComboBox/ComboBoxItem.svelte +30 -0
- package/src/lib/ContentDialog/ContentDialog.scss +68 -0
- package/src/lib/ContentDialog/ContentDialog.svelte +123 -0
- package/src/lib/ContextMenu/ContextMenu.scss +11 -0
- package/src/lib/ContextMenu/ContextMenu.svelte +104 -0
- package/src/lib/Expander/Expander.scss +134 -0
- package/src/lib/Expander/Expander.svelte +123 -0
- package/src/lib/Flipper/Flipper.svelte +49 -0
- package/src/lib/Flyout/FlyoutSurface.scss +14 -0
- package/src/lib/Flyout/FlyoutSurface.svelte +21 -0
- package/src/lib/Flyout/FlyoutWrapper.scss +81 -0
- package/src/lib/Flyout/FlyoutWrapper.svelte +126 -0
- package/src/lib/IconButton/IconButton.scss +31 -0
- package/src/lib/IconButton/IconButton.svelte +49 -0
- package/src/lib/InfoBadge/InfoBadge.scss +39 -0
- package/src/lib/InfoBadge/InfoBadge.svelte +81 -0
- package/src/lib/InfoBar/InfoBar.scss +122 -0
- package/src/lib/InfoBar/InfoBar.svelte +133 -0
- package/src/lib/ListItem/ListItem.scss +74 -0
- package/src/lib/ListItem/ListItem.svelte +88 -0
- package/src/lib/MenuBar/MenuBar.scss +10 -0
- package/src/lib/MenuBar/MenuBar.svelte +49 -0
- package/src/lib/MenuBar/MenuBarItem.scss +38 -0
- package/src/lib/MenuBar/MenuBarItem.svelte +135 -0
- package/src/lib/MenuBar/flyoutState.ts +5 -0
- package/src/lib/MenuFlyout/MenuFlyoutDivider.scss +7 -0
- package/src/lib/MenuFlyout/MenuFlyoutDivider.svelte +14 -0
- package/src/lib/MenuFlyout/MenuFlyoutItem.scss +147 -0
- package/src/lib/MenuFlyout/MenuFlyoutItem.svelte +239 -0
- package/src/lib/MenuFlyout/MenuFlyoutSurface.scss +42 -0
- package/src/lib/MenuFlyout/MenuFlyoutSurface.svelte +28 -0
- package/src/lib/MenuFlyout/MenuFlyoutWrapper.scss +64 -0
- package/src/lib/MenuFlyout/MenuFlyoutWrapper.svelte +114 -0
- package/src/lib/NavigationView/NavigationView.scss +0 -0
- package/src/lib/NavigationView/NavigationView.svelte +82 -0
- package/src/lib/NumberBox/NumberBox.scss +31 -0
- package/src/lib/NumberBox/NumberBox.svelte +267 -0
- package/src/lib/PersonPicture/PersonPicture.scss +35 -0
- package/src/lib/PersonPicture/PersonPicture.svelte +62 -0
- package/src/lib/ProgressBar/ProgressBar.scss +83 -0
- package/src/lib/ProgressBar/ProgressBar.svelte +60 -0
- package/src/lib/ProgressRing/ProgressRing.scss +37 -0
- package/src/lib/ProgressRing/ProgressRing.svelte +73 -0
- package/src/lib/RadioButton/RadioButton.scss +114 -0
- package/src/lib/RadioButton/RadioButton.svelte +67 -0
- package/src/lib/RangeSlider/RangeSlider.svelte +91 -0
- package/src/lib/ScrollView/ScrollView.svelte +9 -0
- package/src/lib/Slider/Slider.scss +263 -0
- package/src/lib/Slider/Slider.svelte +261 -0
- package/src/lib/TextBlock/TextBlock.scss +62 -0
- package/src/lib/TextBlock/TextBlock.svelte +70 -0
- package/src/lib/TextBox/TextBox.scss +108 -0
- package/src/lib/TextBox/TextBox.svelte +225 -0
- package/src/lib/TextBox/TextBoxButton.scss +34 -0
- package/src/lib/TextBox/TextBoxButton.svelte +27 -0
- package/src/lib/ToggleSwitch/ToggleSwitch.scss +118 -0
- package/src/lib/ToggleSwitch/ToggleSwitch.svelte +55 -0
- package/src/lib/Tooltip/TooltipSurface.scss +16 -0
- package/src/lib/Tooltip/TooltipSurface.svelte +27 -0
- package/src/lib/Tooltip/TooltipWrapper.scss +66 -0
- package/src/lib/Tooltip/TooltipWrapper.svelte +117 -0
- package/src/lib/_mixins.scss +130 -0
- package/src/lib/index.ts +33 -0
- package/src/lib/internal.ts +213 -0
- package/src/lib/svelte-jsx.d.ts +14 -0
- package/src/lib/theme.css +414 -0
- package/src/routes/__layout.svelte +48 -0
- package/src/routes/docs/__layout.svelte +122 -0
- package/src/routes/docs/components/button.md +43 -0
- package/src/routes/docs/components/calendarview.md +188 -0
- package/src/routes/docs/components/checkbox.md +87 -0
- package/src/routes/docs/components/contentdialog.md +155 -0
- package/src/routes/docs/components/expander.md +115 -0
- package/src/routes/docs/components/flyout.md +107 -0
- package/src/routes/docs/components/iconbutton.md +39 -0
- package/src/routes/docs/components/infobadge.md +54 -0
- package/src/routes/docs/components/infobar.md +102 -0
- package/src/routes/docs/components/listitem.md +87 -0
- package/src/routes/docs/components/personpicture.md +125 -0
- package/src/routes/docs/components/progressring.md +83 -0
- package/src/routes/docs/components/radiobutton.md +88 -0
- package/src/routes/docs/components/slider.md +165 -0
- package/src/routes/docs/components/textblock.md +46 -0
- package/src/routes/docs/components/textbox.md +124 -0
- package/src/routes/docs/components/toggleswitch.md +73 -0
- package/src/routes/docs/getting-started.md +116 -0
- package/src/routes/docs/index.md +37 -0
- package/src/routes/docs/internals/index.md +0 -0
- package/src/routes/index.svelte +121 -0
- package/src/routes/test/__layout-test.svelte +1 -0
- package/src/routes/test/index.svelte +757 -0
- package/src/routes/test/nav.svelte +7 -0
- package/src/site/data/docs.ts +176 -0
- package/src/site/data/home.ts +12 -0
- package/src/site/lib/APIDocs/APIDocs.svelte +178 -0
- package/src/site/lib/APIDocs/ParsedComponent.d.ts +85 -0
- package/src/site/lib/CopyBox/CopyBox.svelte +23 -0
- package/src/site/lib/Example/Example.scss +33 -0
- package/src/site/lib/Example/Example.svelte +18 -0
- package/src/site/lib/HeroCard/HeroCard.scss +24 -0
- package/src/site/lib/HeroCard/HeroCard.svelte +36 -0
- package/src/site/lib/Metadata/Metadata.svelte +14 -0
- package/src/site/lib/Navbar/Navbar.scss +92 -0
- package/src/site/lib/Navbar/Navbar.svelte +47 -0
- package/src/site/lib/PageSection/PageSection.scss +57 -0
- package/src/site/lib/PageSection/PageSection.svelte +10 -0
- package/src/site/lib/Showcase/Showcase.scss +53 -0
- package/src/site/lib/Showcase/Showcase.svelte +67 -0
- package/src/site/lib/Toc/Toc.scss +18 -0
- package/src/site/lib/Toc/Toc.svelte +59 -0
- package/src/site/lib/TreeView/TreeView.svelte +89 -0
- package/src/site/lib/index.ts +9 -0
- package/src/site/styles/_markdown.scss +260 -0
- package/src/site/styles/_mixins.scss +319 -0
- package/src/site/styles/global.scss +40 -0
- package/src/site/styles/pages/docs.scss +74 -0
- package/src/site/styles/pages/home.scss +134 -0
- package/static/bloom-mica-dark.png +0 -0
- package/static/bloom-mica-light.png +0 -0
- package/static/logo.svg +11 -0
- package/svelte.config.js +57 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher, tick } from "svelte";
|
|
3
|
+
import { get_current_component, onMount } from "svelte/internal";
|
|
4
|
+
|
|
5
|
+
import { createEventForwarder, externalMouseEvents, uid } from "$lib/internal";
|
|
6
|
+
|
|
7
|
+
import ComboBoxItem from "./ComboBoxItem.svelte";
|
|
8
|
+
import Button from "../Button/Button.svelte";
|
|
9
|
+
import TextBox from "../TextBox/TextBox.svelte";
|
|
10
|
+
import TextBoxButton from "../TextBox/TextBoxButton.svelte";
|
|
11
|
+
|
|
12
|
+
interface Item {
|
|
13
|
+
name: string;
|
|
14
|
+
value: any;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Determines which specified item is selected. Correspond's to a given item's `value` key. */
|
|
19
|
+
export let value: any = undefined;
|
|
20
|
+
|
|
21
|
+
/** Current value of the ComboBox's search box. Only applicable if `searchable` is set to `true`. */
|
|
22
|
+
export let searchValue: any = undefined;
|
|
23
|
+
|
|
24
|
+
/** The initial placeholder text displayed if no item is currently selected. */
|
|
25
|
+
export let placeholder = "";
|
|
26
|
+
|
|
27
|
+
/** Array of objects representing the dropdown items. */
|
|
28
|
+
export let items: Item[] = [];
|
|
29
|
+
|
|
30
|
+
/** Determines if the ComboBox can be searched. */
|
|
31
|
+
export let editable = false;
|
|
32
|
+
|
|
33
|
+
/** Specifies whether the combobox is disabled. */
|
|
34
|
+
export let disabled = false;
|
|
35
|
+
|
|
36
|
+
/** The current visibility state of the dropdown menu. */
|
|
37
|
+
export let open = false;
|
|
38
|
+
|
|
39
|
+
/** Specifies a custom class name for the outer combobox container. */
|
|
40
|
+
let className = "";
|
|
41
|
+
export { className as class };
|
|
42
|
+
|
|
43
|
+
/** Obtains a bound DOM reference to the ComboBox's value input element. */
|
|
44
|
+
export let inputElement: HTMLInputElement = null;
|
|
45
|
+
|
|
46
|
+
/** Obtains a bound DOM reference to the ComboBox's searchbox input element. Only applicable if `searchable` is set to `true`. */
|
|
47
|
+
export let searchInputElement: HTMLInputElement = null;
|
|
48
|
+
|
|
49
|
+
/** Obtains a bound DOM reference to the ComboBox's outer container element. */
|
|
50
|
+
export let containerElement: HTMLDivElement = null;
|
|
51
|
+
|
|
52
|
+
/** Obtains a bound DOM reference to the ComboBox's menu dropdown list element. */
|
|
53
|
+
export let menuElement: HTMLUListElement = null;
|
|
54
|
+
|
|
55
|
+
/** Obtains a bound DOM reference to the ComboBox's trigger button element. */
|
|
56
|
+
export let buttonElement: HTMLButtonElement = null;
|
|
57
|
+
|
|
58
|
+
const forwardEvents = createEventForwarder(get_current_component(), [
|
|
59
|
+
"open",
|
|
60
|
+
"close",
|
|
61
|
+
"select",
|
|
62
|
+
"change",
|
|
63
|
+
"input",
|
|
64
|
+
"beforeinput",
|
|
65
|
+
"keydown"
|
|
66
|
+
]);
|
|
67
|
+
const dispatch = createEventDispatcher();
|
|
68
|
+
const buttonId = uid("fds-combo-box-button-");
|
|
69
|
+
const dropdownId = uid("fds-combo-box-dropdown-");
|
|
70
|
+
|
|
71
|
+
$: selectableItems = items.filter(item => !item.disabled);
|
|
72
|
+
$: selection = items.find(i => i.value === value);
|
|
73
|
+
$: if (menuElement && menuElement.children.length > 0 && !editable) {
|
|
74
|
+
if (selection) {
|
|
75
|
+
(<HTMLLIElement>menuElement.children[items.indexOf(selection)]).focus();
|
|
76
|
+
} else {
|
|
77
|
+
(<HTMLLIElement>menuElement.children[0]).focus();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
$: if (items.length > 0) {
|
|
81
|
+
if (open) {
|
|
82
|
+
dispatch("open");
|
|
83
|
+
} else {
|
|
84
|
+
dispatch("close");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
$: dispatch("select", selection);
|
|
88
|
+
$: menuGrowDirection =
|
|
89
|
+
!selection || items[items.indexOf(selection)] === items[Math.floor(items.length / 2)]
|
|
90
|
+
? "center"
|
|
91
|
+
: items.indexOf(selection) < items.indexOf(items[Math.floor(items.length / 2)])
|
|
92
|
+
? "top"
|
|
93
|
+
: "bottom";
|
|
94
|
+
|
|
95
|
+
let inputFocused = false;
|
|
96
|
+
let itemHeight = 36;
|
|
97
|
+
const maxItems = 14; // 504 (`max-block-size` in ComboBox.scss) / 36 (itemHeight)
|
|
98
|
+
let menuOffset =
|
|
99
|
+
itemHeight *
|
|
100
|
+
-(selection
|
|
101
|
+
? items.indexOf(selection)
|
|
102
|
+
: Math.floor(items.length > maxItems ? maxItems / 2 : items.length / 2));
|
|
103
|
+
|
|
104
|
+
onMount(() => {
|
|
105
|
+
if (!searchValue) searchValue = value;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
function updateOffset(target: HTMLElement) {
|
|
109
|
+
const { top: containerTop } = containerElement.getBoundingClientRect();
|
|
110
|
+
const { top: targetTop } = target.getBoundingClientRect();
|
|
111
|
+
|
|
112
|
+
menuOffset += containerTop - targetTop;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function selectItem(item: Item) {
|
|
116
|
+
if (item.disabled) return;
|
|
117
|
+
|
|
118
|
+
value = item.value;
|
|
119
|
+
searchValue = item.name;
|
|
120
|
+
open = false;
|
|
121
|
+
if (containerElement && !editable) (<HTMLElement>containerElement.children[0]).focus();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function openMenu() {
|
|
125
|
+
open = !open;
|
|
126
|
+
await tick();
|
|
127
|
+
if (editable && searchInputElement) searchInputElement.focus();
|
|
128
|
+
if (menuElement && selection)
|
|
129
|
+
updateOffset(<HTMLElement>menuElement.children[items.indexOf(selection)]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function handleKeyboardNavigation(event: KeyboardEvent | CustomEvent) {
|
|
133
|
+
const { key } = <KeyboardEvent>event;
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
|
|
136
|
+
const editableClosed = editable && !open;
|
|
137
|
+
|
|
138
|
+
// Conditions for closing the menu.
|
|
139
|
+
if (key === "Tab" || key === "Esc" || key === "Escape") open = false;
|
|
140
|
+
|
|
141
|
+
// Oh boy, here we go...
|
|
142
|
+
if (
|
|
143
|
+
key === "ArrowDown" &&
|
|
144
|
+
!editableClosed &&
|
|
145
|
+
!(items.indexOf(selection) >= items.length - 1)
|
|
146
|
+
) {
|
|
147
|
+
value = selectableItems[selectableItems.indexOf(selection) + 1].value; // If down arrow is pressed, check current selection and move to next non-disabled item.
|
|
148
|
+
searchValue = selectableItems[selectableItems.indexOf(selection) + 1].name;
|
|
149
|
+
} else if (key === "ArrowUp" && !editableClosed && !(items.indexOf(selection) <= 0)) {
|
|
150
|
+
value = selectableItems[selectableItems.indexOf(selection) - 1].value; // Do the same with up arrow.
|
|
151
|
+
searchValue = selectableItems[selectableItems.indexOf(selection) - 1].name;
|
|
152
|
+
} else if (key === "Home") {
|
|
153
|
+
value = selectableItems[0].value; // If home is pressed, move to first non-disabled item.
|
|
154
|
+
searchValue = selectableItems[0].name;
|
|
155
|
+
} else if (key === "End") {
|
|
156
|
+
value = selectableItems[selectableItems.length - 1].value; // If end is pressed, move to last non-disabled item.
|
|
157
|
+
searchValue = selectableItems[selectableItems.length - 1].name;
|
|
158
|
+
} else if (open && (key === "Enter" || key === " ")) {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
selectItem(selection); // Select item when the enter/space key is pressed and the menu is open
|
|
161
|
+
} else if (searchInputElement && document.activeElement !== searchInputElement) {
|
|
162
|
+
searchInputElement.focus(); // If the input element has lost focus, regain it.
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Prevent the browser's default scrolling behavior for these keys
|
|
166
|
+
if (key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End")
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
|
|
169
|
+
// Keybindings for opening the menu when in editable mode using arrow keys
|
|
170
|
+
if (key === "ArrowDown" || (key === "ArrowUp" && editable)) {
|
|
171
|
+
if (open) {
|
|
172
|
+
await tick();
|
|
173
|
+
searchInputElement?.select(); // Select text when an item is chosen.
|
|
174
|
+
} else {
|
|
175
|
+
open = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function handleInputFocus() {
|
|
181
|
+
searchInputElement.select();
|
|
182
|
+
inputFocused = true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleInputBlur() {
|
|
186
|
+
inputFocused = false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function handleInput(event: InputEvent | CustomEvent) {
|
|
190
|
+
const match = selectableItems.find(i =>
|
|
191
|
+
i.name.toLowerCase().startsWith(searchValue.toLowerCase())
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (!match) value = null;
|
|
195
|
+
|
|
196
|
+
if (match && (<InputEvent>event).inputType === "insertText" && searchValue.trim() !== "") {
|
|
197
|
+
searchInputElement.value = match.name;
|
|
198
|
+
searchInputElement.setSelectionRange(searchValue.length, match.name.length);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (match && !match.disabled) value = match.value;
|
|
202
|
+
searchValue = searchInputElement.value;
|
|
203
|
+
}
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<!--
|
|
207
|
+
@component
|
|
208
|
+
Use a combo box (also known as a drop-down list) to present a list of items that a user can select from. A combo box starts in a compact state and expands to show a list of selectable items.
|
|
209
|
+
|
|
210
|
+
When the combo box is closed, it either displays the current selection or is empty if there is no selected item. When the user expands the combo box, it displays the list of selectable items.
|
|
211
|
+
[Docs](https://fluent-svelte.vercel.app/docs/components/combobox)
|
|
212
|
+
|
|
213
|
+
- Usage:
|
|
214
|
+
```tsx
|
|
215
|
+
<ComboBox items={[
|
|
216
|
+
{ name: "Item 0", value: 0 },
|
|
217
|
+
{ name: "Item 1", value: 1 },
|
|
218
|
+
{ name: "Item 2", value: 2 },
|
|
219
|
+
]} />
|
|
220
|
+
```
|
|
221
|
+
-->
|
|
222
|
+
<div
|
|
223
|
+
use:forwardEvents
|
|
224
|
+
use:externalMouseEvents={{ type: "mousedown" }}
|
|
225
|
+
class="combo-box {className}"
|
|
226
|
+
class:disabled
|
|
227
|
+
class:editable
|
|
228
|
+
class:open
|
|
229
|
+
on:outermousedown={() => {
|
|
230
|
+
if (open) open = false;
|
|
231
|
+
}}
|
|
232
|
+
bind:this={containerElement}
|
|
233
|
+
{...$$restProps}
|
|
234
|
+
>
|
|
235
|
+
{#if editable}
|
|
236
|
+
<TextBox
|
|
237
|
+
clearButton={false}
|
|
238
|
+
class="combo-box-text-box"
|
|
239
|
+
role="combobox"
|
|
240
|
+
aria-activedescendant={inputFocused}
|
|
241
|
+
aria-autocomplete="both"
|
|
242
|
+
aria-controls={dropdownId}
|
|
243
|
+
aria-expanded={open}
|
|
244
|
+
aria-haspopup={open ? "listbox" : undefined}
|
|
245
|
+
bind:value={searchValue}
|
|
246
|
+
bind:inputElement={searchInputElement}
|
|
247
|
+
on:keydown={handleKeyboardNavigation}
|
|
248
|
+
on:input={handleInput}
|
|
249
|
+
on:focus={handleInputFocus}
|
|
250
|
+
on:blur={handleInputBlur}
|
|
251
|
+
on:change
|
|
252
|
+
on:input
|
|
253
|
+
on:beforeinput
|
|
254
|
+
on:keydown
|
|
255
|
+
{placeholder}
|
|
256
|
+
{disabled}
|
|
257
|
+
>
|
|
258
|
+
<TextBoxButton
|
|
259
|
+
aria-expanded={open}
|
|
260
|
+
aria-label="Open dropdown"
|
|
261
|
+
aria-controls={dropdownId}
|
|
262
|
+
class="combo-box-dropdown-button"
|
|
263
|
+
on:click={openMenu}
|
|
264
|
+
bind:element={buttonElement}
|
|
265
|
+
slot="buttons"
|
|
266
|
+
>
|
|
267
|
+
<svg
|
|
268
|
+
aria-hidden="true"
|
|
269
|
+
class="combo-box-icon"
|
|
270
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
271
|
+
width="48"
|
|
272
|
+
height="48"
|
|
273
|
+
viewBox="0 0 48 48"
|
|
274
|
+
>
|
|
275
|
+
<path
|
|
276
|
+
fill="currentColor"
|
|
277
|
+
d="M8.36612 16.1161C7.87796 16.6043 7.87796 17.3957 8.36612 17.8839L23.1161 32.6339C23.6043 33.122 24.3957 33.122 24.8839 32.6339L39.6339 17.8839C40.122 17.3957 40.122 16.6043 39.6339 16.1161C39.1457 15.628 38.3543 15.628 37.8661 16.1161L24 29.9822L10.1339 16.1161C9.64573 15.628 8.85427 15.628 8.36612 16.1161Z"
|
|
278
|
+
/>
|
|
279
|
+
</svg>
|
|
280
|
+
</TextBoxButton>
|
|
281
|
+
</TextBox>
|
|
282
|
+
{:else}
|
|
283
|
+
<Button
|
|
284
|
+
type="button"
|
|
285
|
+
class="combo-box-button"
|
|
286
|
+
id={buttonId}
|
|
287
|
+
aria-labelledby={buttonId}
|
|
288
|
+
aria-haspopup={open ? "listbox" : undefined}
|
|
289
|
+
aria-controls={dropdownId}
|
|
290
|
+
on:keydown={handleKeyboardNavigation}
|
|
291
|
+
on:keydown
|
|
292
|
+
on:click={openMenu}
|
|
293
|
+
bind:element={buttonElement}
|
|
294
|
+
{disabled}
|
|
295
|
+
>
|
|
296
|
+
<span class="combo-box-label" class:placeholder={!selection}>
|
|
297
|
+
{selection?.name || placeholder}
|
|
298
|
+
</span>
|
|
299
|
+
<svg
|
|
300
|
+
aria-hidden="true"
|
|
301
|
+
class="combo-box-icon"
|
|
302
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
303
|
+
width="48"
|
|
304
|
+
height="48"
|
|
305
|
+
viewBox="0 0 48 48"
|
|
306
|
+
>
|
|
307
|
+
<path
|
|
308
|
+
fill="currentColor"
|
|
309
|
+
d="M8.36612 16.1161C7.87796 16.6043 7.87796 17.3957 8.36612 17.8839L23.1161 32.6339C23.6043 33.122 24.3957 33.122 24.8839 32.6339L39.6339 17.8839C40.122 17.3957 40.122 16.6043 39.6339 16.1161C39.1457 15.628 38.3543 15.628 37.8661 16.1161L24 29.9822L10.1339 16.1161C9.64573 15.628 8.85427 15.628 8.36612 16.1161Z"
|
|
310
|
+
/>
|
|
311
|
+
</svg>
|
|
312
|
+
</Button>
|
|
313
|
+
{/if}
|
|
314
|
+
{#if !disabled && items.length > 0}
|
|
315
|
+
{#if open}
|
|
316
|
+
<ul
|
|
317
|
+
bind:this={menuElement}
|
|
318
|
+
on:blur={() => (open = false)}
|
|
319
|
+
id={dropdownId}
|
|
320
|
+
aria-labelledby={buttonId}
|
|
321
|
+
aria-activedescendant={editable
|
|
322
|
+
? undefined
|
|
323
|
+
: `${dropdownId}-item-${items.indexOf(selection)}`}
|
|
324
|
+
role="listbox"
|
|
325
|
+
class="combo-box-dropdown direction-{!editable
|
|
326
|
+
? menuGrowDirection ?? 'center'
|
|
327
|
+
: 'top'}"
|
|
328
|
+
style="--fds-menu-offset: {menuOffset}px;"
|
|
329
|
+
>
|
|
330
|
+
{#each items as item, i}
|
|
331
|
+
<ComboBoxItem
|
|
332
|
+
role="option"
|
|
333
|
+
selected={item.value === value}
|
|
334
|
+
disabled={item.disabled}
|
|
335
|
+
id="{dropdownId}-item-{i}"
|
|
336
|
+
on:keydown={handleKeyboardNavigation}
|
|
337
|
+
on:click={() => selectItem(item)}
|
|
338
|
+
>
|
|
339
|
+
{item.name}
|
|
340
|
+
</ComboBoxItem>
|
|
341
|
+
{/each}
|
|
342
|
+
</ul>
|
|
343
|
+
{/if}
|
|
344
|
+
|
|
345
|
+
<input
|
|
346
|
+
type="hidden"
|
|
347
|
+
aria-hidden="true"
|
|
348
|
+
bind:this={inputElement}
|
|
349
|
+
bind:value
|
|
350
|
+
on:change
|
|
351
|
+
on:input
|
|
352
|
+
on:beforeinput
|
|
353
|
+
/>
|
|
354
|
+
<slot />
|
|
355
|
+
{/if}
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
<style lang="scss">
|
|
359
|
+
@use "./ComboBox";
|
|
360
|
+
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
@use "../mixins" as *;
|
|
2
|
+
|
|
3
|
+
.combo-box-item {
|
|
4
|
+
@include flex($align: center);
|
|
5
|
+
@include typography-body;
|
|
6
|
+
|
|
7
|
+
position: relative;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
flex: 0 0 auto;
|
|
10
|
+
margin: 4px;
|
|
11
|
+
padding: 0 11px;
|
|
12
|
+
border-radius: var(--control-corner-radius);
|
|
13
|
+
outline: none;
|
|
14
|
+
background-color: var(--subtle-fill-transparent);
|
|
15
|
+
color: var(--text-primary);
|
|
16
|
+
text-decoration: none;
|
|
17
|
+
cursor: default;
|
|
18
|
+
user-select: none;
|
|
19
|
+
block-size: 32px;
|
|
20
|
+
|
|
21
|
+
&::before {
|
|
22
|
+
content: "";
|
|
23
|
+
position: absolute;
|
|
24
|
+
border-radius: 3px;
|
|
25
|
+
background-color: var(--accent-default);
|
|
26
|
+
transition: transform var(--control-fast-duration) var(--control-fast-out-slow-in-easing);
|
|
27
|
+
opacity: 0;
|
|
28
|
+
inset-inline-start: 0;
|
|
29
|
+
inline-size: 3px;
|
|
30
|
+
block-size: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:focus-visible {
|
|
34
|
+
box-shadow: var(--focus-stroke);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:hover,
|
|
38
|
+
&.selected {
|
|
39
|
+
background-color: var(--subtle-fill-secondary);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&:active {
|
|
43
|
+
background-color: var(--subtle-fill-tertiary);
|
|
44
|
+
color: var(--text-secondary);
|
|
45
|
+
|
|
46
|
+
&::before {
|
|
47
|
+
transform: scaleY(0.625);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.disabled {
|
|
52
|
+
background-color: var(--subtle-fill-transparent);
|
|
53
|
+
color: var(--text-disabled);
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
&.selected {
|
|
56
|
+
background-color: var(--subtle-fill-secondary);
|
|
57
|
+
&::before {
|
|
58
|
+
background-color: var(--accent-disabled);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&.selected::before {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
block-size: 16px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
> span {
|
|
69
|
+
flex: 1 1 auto;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
max-inline-size: 100%;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
> :global(svg) {
|
|
77
|
+
@include icon($size: 16px);
|
|
78
|
+
margin-inline-end: 16px;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventForwarder } from "$lib/internal";
|
|
3
|
+
import { get_current_component } from "svelte/internal";
|
|
4
|
+
|
|
5
|
+
export let selected = false;
|
|
6
|
+
export let disabled = false;
|
|
7
|
+
|
|
8
|
+
let className = "";
|
|
9
|
+
export { className as class };
|
|
10
|
+
|
|
11
|
+
const forwardEvents = createEventForwarder(get_current_component());
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<li
|
|
15
|
+
use:forwardEvents
|
|
16
|
+
tabindex="0"
|
|
17
|
+
class="combo-box-item {className}"
|
|
18
|
+
class:selected
|
|
19
|
+
class:disabled
|
|
20
|
+
{...$$restProps}
|
|
21
|
+
>
|
|
22
|
+
<slot name="icon" />
|
|
23
|
+
<span>
|
|
24
|
+
<slot />
|
|
25
|
+
</span>
|
|
26
|
+
</li>
|
|
27
|
+
|
|
28
|
+
<style lang="scss">
|
|
29
|
+
@use "./ComboBoxItem";
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@use "../mixins" as *;
|
|
2
|
+
|
|
3
|
+
.content-dialog {
|
|
4
|
+
position: fixed;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
animation: dialog-inner var(--control-fast-duration) var(--control-fast-out-slow-in-easing);
|
|
7
|
+
max-inline-size: calc(100% - 24px);
|
|
8
|
+
border-radius: var(--overlay-corner-radius);
|
|
9
|
+
background-color: var(--solid-background-base);
|
|
10
|
+
background-clip: padding-box;
|
|
11
|
+
box-shadow: var(--dialog-shadow);
|
|
12
|
+
border: 1px solid var(--surface-stroke-default);
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
&.size- {
|
|
15
|
+
&min {
|
|
16
|
+
inline-size: 320px;
|
|
17
|
+
}
|
|
18
|
+
&standard {
|
|
19
|
+
inline-size: 448px;
|
|
20
|
+
}
|
|
21
|
+
&max {
|
|
22
|
+
inline-size: 540px;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&-smoke {
|
|
27
|
+
@include flex($direction: column, $align: center, $justify: center);
|
|
28
|
+
position: fixed;
|
|
29
|
+
inset-inline-start: 0;
|
|
30
|
+
inset-block-start: 0;
|
|
31
|
+
z-index: 101;
|
|
32
|
+
inline-size: 100%;
|
|
33
|
+
block-size: 100%;
|
|
34
|
+
&.darken {
|
|
35
|
+
background-color: var(--smoke-background-default);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:global(.content-dialog-title) {
|
|
40
|
+
display: block;
|
|
41
|
+
margin-bottom: 12px;
|
|
42
|
+
color: var(--text-primary);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&-body,
|
|
46
|
+
&-footer {
|
|
47
|
+
padding: 24px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&-body {
|
|
51
|
+
@include typography-body;
|
|
52
|
+
background-color: var(--layer-background-default);
|
|
53
|
+
color: var(--text-primary);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&-footer {
|
|
57
|
+
display: grid;
|
|
58
|
+
grid-auto-rows: 1fr;
|
|
59
|
+
grid-auto-flow: column;
|
|
60
|
+
grid-gap: 8px;
|
|
61
|
+
border-block-start: 1px solid var(--card-stroke-default);
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
> :global(.button:only-child) {
|
|
64
|
+
inline-size: 50%;
|
|
65
|
+
justify-self: end;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher } from "svelte";
|
|
3
|
+
import { get_current_component } from "svelte/internal";
|
|
4
|
+
import { fade, scale } from "svelte/transition";
|
|
5
|
+
import { circOut } from "svelte/easing";
|
|
6
|
+
import { uid, focusTrap, getCSSDuration, createEventForwarder } from "$lib/internal";
|
|
7
|
+
|
|
8
|
+
import TextBlock from "../TextBlock/TextBlock.svelte";
|
|
9
|
+
|
|
10
|
+
/** Determines whether the dialog is open or not. */
|
|
11
|
+
export let open: boolean = false;
|
|
12
|
+
|
|
13
|
+
/** Title text displayed as the dialog header. */
|
|
14
|
+
export let title: string = "";
|
|
15
|
+
|
|
16
|
+
/** Specifies the visual size of the dialog. */
|
|
17
|
+
export let size: "standard" | "max" | "min" = "standard";
|
|
18
|
+
|
|
19
|
+
/** Determines whether the dialog can be conventially closed using the escape key. */
|
|
20
|
+
export let closable = true;
|
|
21
|
+
|
|
22
|
+
/** Determines the node the dialog should be appended to. */
|
|
23
|
+
export let append: HTMLElement = undefined;
|
|
24
|
+
|
|
25
|
+
/** Determines if the dialog should darken the contents behind it. */
|
|
26
|
+
export let darken = true;
|
|
27
|
+
|
|
28
|
+
/** Determines if keyboard focus should be locked to the dialog's contents. */
|
|
29
|
+
export let trapFocus = true;
|
|
30
|
+
|
|
31
|
+
/** Specifies a custom class name for the dialog. */
|
|
32
|
+
let className = "";
|
|
33
|
+
export { className as class };
|
|
34
|
+
|
|
35
|
+
/** Obtains a bound DOM reference to the inner dialog element. */
|
|
36
|
+
export let element: HTMLElement = null;
|
|
37
|
+
|
|
38
|
+
/** Obtains a bound DOM reference to the dialog's backdrop container element. */
|
|
39
|
+
export let backdropElement: HTMLElement = null;
|
|
40
|
+
|
|
41
|
+
/** Obtains a bound DOM reference to the dialog's inner body element. */
|
|
42
|
+
export let bodyElement: HTMLElement = null;
|
|
43
|
+
|
|
44
|
+
/** Obtains a bound DOM reference to the dialog's footer element. */
|
|
45
|
+
export let footerElement: HTMLElement = null;
|
|
46
|
+
|
|
47
|
+
const forwardEvents = createEventForwarder(get_current_component(), [
|
|
48
|
+
"open",
|
|
49
|
+
"close",
|
|
50
|
+
"backdropclick",
|
|
51
|
+
"backdropmousedown"
|
|
52
|
+
]);
|
|
53
|
+
const dispatch = createEventDispatcher();
|
|
54
|
+
const titleId = uid("fds-dialog-title-");
|
|
55
|
+
const bodyId = uid("fds-dialog-body-");
|
|
56
|
+
|
|
57
|
+
$: if (!open) dispatch("close");
|
|
58
|
+
$: _focusTrap = trapFocus ? focusTrap : () => {};
|
|
59
|
+
|
|
60
|
+
function mountDialog(node: HTMLDivElement) {
|
|
61
|
+
dispatch("open");
|
|
62
|
+
if (append) append.appendChild(node);
|
|
63
|
+
node.focus();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function close() {
|
|
67
|
+
open = false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleEscapeKey({ key }: KeyboardEvent) {
|
|
71
|
+
if (key === "Escape" && open && closable) close();
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<svelte:window on:keydown={handleEscapeKey} />
|
|
76
|
+
|
|
77
|
+
{#if open}
|
|
78
|
+
<div
|
|
79
|
+
class="content-dialog-smoke"
|
|
80
|
+
class:darken
|
|
81
|
+
on:click|self={e => dispatch("backdropclick", e)}
|
|
82
|
+
on:mousedown|self={e => dispatch("backdropmousedown", e)}
|
|
83
|
+
transition:fade|local={{ duration: getCSSDuration("--fds-control-faster-duration") }}
|
|
84
|
+
use:mountDialog
|
|
85
|
+
use:_focusTrap
|
|
86
|
+
bind:this={backdropElement}
|
|
87
|
+
>
|
|
88
|
+
<div
|
|
89
|
+
use:forwardEvents
|
|
90
|
+
class="content-dialog size-{size} {className}"
|
|
91
|
+
role="dialog"
|
|
92
|
+
aria-modal="true"
|
|
93
|
+
aria-labelledby={title && titleId}
|
|
94
|
+
aria-describedby={bodyId}
|
|
95
|
+
bind:this={element}
|
|
96
|
+
transition:scale|local={{
|
|
97
|
+
duration: getCSSDuration("--fds-control-fast-duration"),
|
|
98
|
+
start: 1.05,
|
|
99
|
+
easing: circOut
|
|
100
|
+
}}
|
|
101
|
+
{...$$restProps}
|
|
102
|
+
>
|
|
103
|
+
<div class="content-dialog-body" id={bodyId} bind:this={bodyElement}>
|
|
104
|
+
{#if title}
|
|
105
|
+
<TextBlock variant="subtitle" class="content-dialog-title" id={titleId}>
|
|
106
|
+
{title}
|
|
107
|
+
</TextBlock>
|
|
108
|
+
{/if}
|
|
109
|
+
<slot />
|
|
110
|
+
</div>
|
|
111
|
+
{#if $$slots.footer}
|
|
112
|
+
<footer class="content-dialog-footer" bind:this={footerElement}>
|
|
113
|
+
<slot name="footer" />
|
|
114
|
+
</footer>
|
|
115
|
+
{/if}
|
|
116
|
+
</div>
|
|
117
|
+
<slot name="outer" />
|
|
118
|
+
</div>
|
|
119
|
+
{/if}
|
|
120
|
+
|
|
121
|
+
<style lang="scss">
|
|
122
|
+
@use "./ContentDialog";
|
|
123
|
+
</style>
|