@lavalogic/scoria 0.40.0 → 0.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -58
- package/dist/Components/AccordionGroup.svelte +46 -46
- package/dist/Components/AccordionGroupButton.svelte +76 -76
- package/dist/Components/Action.svelte +136 -136
- package/dist/Components/AlertModal.svelte +195 -195
- package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +729 -729
- package/dist/Components/Base/BaseModal.svelte +125 -125
- package/dist/Components/Base/ContextWrapper.svelte +72 -72
- package/dist/Components/Base/Pagination.svelte +198 -198
- package/dist/Components/Base/Snippets.svelte +36 -36
- package/dist/Components/BigRadioSet.svelte +38 -38
- package/dist/Components/Bubble.svelte +101 -101
- package/dist/Components/Button.svelte +209 -209
- package/dist/Components/Checkbox.svelte +86 -86
- package/dist/Components/CollapsibleCard.svelte +78 -78
- package/dist/Components/Contexts/ContextMenu.svelte +164 -164
- package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
- package/dist/Components/Contexts/ContextMenuOption.svelte +60 -60
- package/dist/Components/Contexts/Toast.svelte +86 -86
- package/dist/Components/Contexts/ToastContainer.svelte +65 -65
- package/dist/Components/Contexts/Tooltip.svelte +37 -37
- package/dist/Components/DataMatrixIcon.svelte +112 -112
- package/dist/Components/DateInput.svelte +148 -148
- package/dist/Components/DatePicker.svelte +300 -300
- package/dist/Components/DesktopModal.svelte +90 -90
- package/dist/Components/Dial.svelte +204 -204
- package/dist/Components/DimensionInput.svelte +170 -170
- package/dist/Components/DragMenu/DragMenuHolder.svelte +119 -119
- package/dist/Components/DragMenu/DraggableCard.svelte +77 -77
- package/dist/Components/FileCard.svelte +24 -24
- package/dist/Components/FileUpload.svelte +236 -236
- package/dist/Components/GridInput.svelte +196 -196
- package/dist/Components/GridInputSet.svelte +29 -29
- package/dist/Components/HorizontalTabGroup.svelte +82 -82
- package/dist/Components/HorizontalTabGroupButton.svelte +44 -44
- package/dist/Components/Icon.svelte +128 -128
- package/dist/Components/Icons.js +357 -357
- package/dist/Components/InfoAlert.svelte +35 -35
- package/dist/Components/LoadingAnimation.svelte +29 -29
- package/dist/Components/LoadingModal.svelte +45 -45
- package/dist/Components/LoadingOverlay.svelte +25 -25
- package/dist/Components/Modal.svelte +258 -258
- package/dist/Components/MultiSelect.svelte +220 -220
- package/dist/Components/Nav/Nav.svelte +110 -110
- package/dist/Components/Nav/NavServiceMenuList.svelte +43 -43
- package/dist/Components/Nav/NavTab.svelte +62 -62
- package/dist/Components/Nav/ServiceMenuItem.svelte +112 -112
- package/dist/Components/Nav/ServicesNav.svelte +80 -80
- package/dist/Components/NumberInput.svelte +157 -157
- package/dist/Components/PageHeading.svelte +25 -25
- package/dist/Components/PasswordInput.svelte +88 -88
- package/dist/Components/Portal.svelte +36 -36
- package/dist/Components/ProgressBubble.svelte +107 -107
- package/dist/Components/QuerySelect.svelte +366 -366
- package/dist/Components/SingleSelect.svelte +250 -250
- package/dist/Components/StepButton.svelte +70 -70
- package/dist/Components/StepForm.svelte +140 -140
- package/dist/Components/Switch.svelte +69 -69
- package/dist/Components/Table/ARCHITECTURE.md +298 -298
- package/dist/Components/Table/Body/QuickSearchCell.svelte +679 -679
- package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +51 -51
- package/dist/Components/Table/Body/Rows/ColumnList.svelte +59 -59
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +454 -454
- package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +79 -79
- package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +185 -185
- package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +124 -124
- package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +131 -120
- package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +234 -234
- package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +105 -105
- package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +87 -87
- package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +126 -126
- package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +159 -159
- package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +246 -246
- package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +216 -216
- package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +252 -252
- package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +179 -179
- package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +221 -221
- package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +197 -197
- package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +190 -190
- package/dist/Components/Table/Body/Rows/TableRow.svelte +290 -290
- package/dist/Components/Table/Body/TableBody.svelte +155 -155
- package/dist/Components/Table/ColumnFactory.svelte.js +1 -0
- package/dist/Components/Table/Headers/HighlightAllHeader.svelte +41 -41
- package/dist/Components/Table/Headers/SelectAllHeader.svelte +30 -30
- package/dist/Components/Table/Headers/TableHead.svelte +415 -415
- package/dist/Components/Table/Misc/ColumnPanel.svelte +39 -39
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte +39 -39
- package/dist/Components/Table/Misc/FilterInput.svelte +641 -641
- package/dist/Components/Table/Misc/FilterPanel.svelte +190 -190
- package/dist/Components/Table/Misc/FilterPanelModal.svelte +31 -31
- package/dist/Components/Table/Misc/ShareViewModal.svelte +291 -291
- package/dist/Components/Table/Misc/SidePanel.svelte +35 -35
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte +644 -644
- package/dist/Components/Table/Misc/TableFooter.svelte +340 -340
- package/dist/Components/Table/Misc/TableHorizontalBar.svelte +140 -140
- package/dist/Components/Table/Misc/TableSidebar.svelte +200 -200
- package/dist/Components/Table/Misc/TableViewDropdown.svelte +105 -105
- package/dist/Components/Table/Misc/ToolboxPanel.svelte +100 -100
- package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +107 -107
- package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +192 -192
- package/dist/Components/Table/NestedTable.svelte +130 -130
- package/dist/Components/Table/Table.svelte +692 -692
- package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.d.ts +5 -0
- package/dist/Components/Table/Types/Columns/Definitions/Expand/ExpandDef.svelte.js +3 -0
- package/dist/Components/Table/Types/Public/ColumnOptions.d.ts +10 -0
- package/dist/Components/Test/TestContextConsumer.svelte +13 -13
- package/dist/Components/TextArea.svelte +130 -130
- package/dist/Components/TextInput.svelte +176 -176
- package/dist/Components/ThreeStateRadio.svelte +131 -131
- package/dist/Components/Touch/BoolCard.svelte +45 -45
- package/dist/Components/Touch/DateModal.svelte +209 -209
- package/dist/Components/Touch/EditCard.svelte +59 -59
- package/dist/Components/Touch/InfoCard.svelte +31 -31
- package/dist/Components/Touch/InfoTextCard.svelte +28 -28
- package/dist/Components/Touch/ModalExplanation.svelte +17 -17
- package/dist/Components/Touch/NumberKeyboard.svelte +225 -225
- package/dist/Components/Touch/NumberModal.svelte +79 -79
- package/dist/Components/Touch/PlusMinusCard.svelte +46 -46
- package/dist/Components/Touch/SelectModal.svelte +127 -127
- package/dist/Components/Touch/SummaryCard.svelte +93 -93
- package/dist/Components/Touch/TextAreaModal.svelte +95 -95
- package/dist/Components/Touch/TextModal.svelte +106 -106
- package/dist/Components/Touch/TouchCard.svelte +33 -33
- package/dist/Components/Touch/TouchModal.svelte +274 -274
- package/dist/Components/Touch/UtilityButton.svelte +109 -109
- package/dist/Components/VerticalTabGroup.svelte +79 -79
- package/dist/Components/VerticalTabGroupButton.svelte +58 -58
- package/dist/Helpers/Datamatrix.d.ts +19 -19
- package/dist/Helpers/Datamatrix.js +291 -291
- package/dist/Types/Examples/ExampleModal.svelte +35 -35
- package/dist/scss/_basics.scss +12 -12
- package/dist/scss/_colours.scss +134 -134
- package/dist/scss/_mixins.scss +3392 -3392
- package/dist/scss/_motion.scss +57 -57
- package/dist/scss/_sizing.scss +85 -85
- package/dist/scss/_transitions.scss +8 -8
- package/package.json +1 -1
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import { Colour } from '../../scss/colours.js';
|
|
5
|
-
import { Size } from '../../Types/Internal/Size.js';
|
|
6
|
-
import Icon from '../Icon.svelte';
|
|
7
|
-
import { Dimensions } from './Dimensions.js';
|
|
8
|
-
import type { NavServiceMenuListProps } from './NavServiceMenuListProps.js';
|
|
9
|
-
import ServiceMenuItem from './ServiceMenuItem.svelte';
|
|
10
|
-
|
|
11
|
-
const { title, items, icon }: NavServiceMenuListProps = $props();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<div
|
|
15
|
-
class="wrapper"
|
|
16
|
-
style="--icon-column-width: {Dimensions.smallPanelWidth}rem"
|
|
17
|
-
role="group"
|
|
18
|
-
aria-label={title}
|
|
19
|
-
>
|
|
20
|
-
<h3>
|
|
21
|
-
<div class="icon">
|
|
22
|
-
{#if icon}
|
|
23
|
-
<Icon
|
|
24
|
-
size={Size.Large}
|
|
25
|
-
colour={Colour['$font-primary']}
|
|
26
|
-
{...icon}
|
|
27
|
-
/>
|
|
28
|
-
{/if}
|
|
29
|
-
</div>
|
|
30
|
-
{title}
|
|
31
|
-
</h3>
|
|
32
|
-
|
|
33
|
-
<div
|
|
34
|
-
class="items"
|
|
35
|
-
role="list"
|
|
36
|
-
>
|
|
37
|
-
{#each items as { item } (item.label)}
|
|
38
|
-
<ServiceMenuItem {item} />
|
|
39
|
-
{/each}
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { Colour } from '../../scss/colours.js';
|
|
5
|
+
import { Size } from '../../Types/Internal/Size.js';
|
|
6
|
+
import Icon from '../Icon.svelte';
|
|
7
|
+
import { Dimensions } from './Dimensions.js';
|
|
8
|
+
import type { NavServiceMenuListProps } from './NavServiceMenuListProps.js';
|
|
9
|
+
import ServiceMenuItem from './ServiceMenuItem.svelte';
|
|
10
|
+
|
|
11
|
+
const { title, items, icon }: NavServiceMenuListProps = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
class="wrapper"
|
|
16
|
+
style="--icon-column-width: {Dimensions.smallPanelWidth}rem"
|
|
17
|
+
role="group"
|
|
18
|
+
aria-label={title}
|
|
19
|
+
>
|
|
20
|
+
<h3>
|
|
21
|
+
<div class="icon">
|
|
22
|
+
{#if icon}
|
|
23
|
+
<Icon
|
|
24
|
+
size={Size.Large}
|
|
25
|
+
colour={Colour['$font-primary']}
|
|
26
|
+
{...icon}
|
|
27
|
+
/>
|
|
28
|
+
{/if}
|
|
29
|
+
</div>
|
|
30
|
+
{title}
|
|
31
|
+
</h3>
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
class="items"
|
|
35
|
+
role="list"
|
|
36
|
+
>
|
|
37
|
+
{#each items as { item } (item.label)}
|
|
38
|
+
<ServiceMenuItem {item} />
|
|
39
|
+
{/each}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
43
|
<style>h3 {
|
|
44
44
|
font-size: 1.5rem;
|
|
45
45
|
display: flex;
|
|
@@ -63,4 +63,4 @@ h3 .icon {
|
|
|
63
63
|
.wrapper .items {
|
|
64
64
|
flex-shrink: 1;
|
|
65
65
|
overflow: auto;
|
|
66
|
-
}</style>
|
|
66
|
+
}</style>
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script
|
|
4
|
-
lang="ts"
|
|
5
|
-
generics="Args"
|
|
6
|
-
>
|
|
7
|
-
import { useAsyncFlag } from '../../Helpers/AsyncFlag.svelte.js';
|
|
8
|
-
import { Colour } from '../../scss/colours.js';
|
|
9
|
-
import { Size } from '../../Types/Internal/Size.js';
|
|
10
|
-
import Icon from '../Icon.svelte';
|
|
11
|
-
import type { NavTabProps } from './NavTabProps.js';
|
|
12
|
-
|
|
13
|
-
// `Args` defaults to `unknown` on the props interface, but we still
|
|
14
|
-
// pass the generic explicitly so consumers using the component as
|
|
15
|
-
// `<NavTab>` inside a typed snippet receive the same `Args` rune.
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
|
|
17
|
-
let { option, selected }: NavTabProps<Args> = $props();
|
|
18
|
-
|
|
19
|
-
const isValid = useAsyncFlag(() => option.isValid, true);
|
|
20
|
-
const disabled = useAsyncFlag(() => option.disabled, false);
|
|
21
|
-
const visible = useAsyncFlag(() => option.visible, true);
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<button
|
|
25
|
-
class:selected
|
|
26
|
-
onclick={() => {
|
|
27
|
-
option.onclick();
|
|
28
|
-
}}
|
|
29
|
-
type="button"
|
|
30
|
-
class="tab"
|
|
31
|
-
role="tab"
|
|
32
|
-
aria-selected={selected}
|
|
33
|
-
aria-label={option.label}
|
|
34
|
-
disabled={disabled.value}
|
|
35
|
-
class:invalid={!isValid.value}
|
|
36
|
-
class:hidden={!visible.value}
|
|
37
|
-
>
|
|
38
|
-
<Icon
|
|
39
|
-
name="nothing"
|
|
40
|
-
colour={Colour['$brand-primary']}
|
|
41
|
-
size={Size.MediumLarge}
|
|
42
|
-
{...option.iconLeft}
|
|
43
|
-
/>
|
|
44
|
-
|
|
45
|
-
{option.label}
|
|
46
|
-
{#if option.isValid === false}
|
|
47
|
-
<Icon
|
|
48
|
-
name="error"
|
|
49
|
-
colour={Colour['$error-border']}
|
|
50
|
-
size={Size.MediumLarge}
|
|
51
|
-
/>
|
|
52
|
-
{:else}
|
|
53
|
-
<Icon
|
|
54
|
-
name="nothing"
|
|
55
|
-
colour={Colour['$brand-primary']}
|
|
56
|
-
size={Size.MediumLarge}
|
|
57
|
-
{...option.iconRight}
|
|
58
|
-
/>
|
|
59
|
-
{/if}
|
|
60
|
-
</button>
|
|
61
|
-
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script
|
|
4
|
+
lang="ts"
|
|
5
|
+
generics="Args"
|
|
6
|
+
>
|
|
7
|
+
import { useAsyncFlag } from '../../Helpers/AsyncFlag.svelte.js';
|
|
8
|
+
import { Colour } from '../../scss/colours.js';
|
|
9
|
+
import { Size } from '../../Types/Internal/Size.js';
|
|
10
|
+
import Icon from '../Icon.svelte';
|
|
11
|
+
import type { NavTabProps } from './NavTabProps.js';
|
|
12
|
+
|
|
13
|
+
// `Args` defaults to `unknown` on the props interface, but we still
|
|
14
|
+
// pass the generic explicitly so consumers using the component as
|
|
15
|
+
// `<NavTab>` inside a typed snippet receive the same `Args` rune.
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
|
|
17
|
+
let { option, selected }: NavTabProps<Args> = $props();
|
|
18
|
+
|
|
19
|
+
const isValid = useAsyncFlag(() => option.isValid, true);
|
|
20
|
+
const disabled = useAsyncFlag(() => option.disabled, false);
|
|
21
|
+
const visible = useAsyncFlag(() => option.visible, true);
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<button
|
|
25
|
+
class:selected
|
|
26
|
+
onclick={() => {
|
|
27
|
+
option.onclick();
|
|
28
|
+
}}
|
|
29
|
+
type="button"
|
|
30
|
+
class="tab"
|
|
31
|
+
role="tab"
|
|
32
|
+
aria-selected={selected}
|
|
33
|
+
aria-label={option.label}
|
|
34
|
+
disabled={disabled.value}
|
|
35
|
+
class:invalid={!isValid.value}
|
|
36
|
+
class:hidden={!visible.value}
|
|
37
|
+
>
|
|
38
|
+
<Icon
|
|
39
|
+
name="nothing"
|
|
40
|
+
colour={Colour['$brand-primary']}
|
|
41
|
+
size={Size.MediumLarge}
|
|
42
|
+
{...option.iconLeft}
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
{option.label}
|
|
46
|
+
{#if option.isValid === false}
|
|
47
|
+
<Icon
|
|
48
|
+
name="error"
|
|
49
|
+
colour={Colour['$error-border']}
|
|
50
|
+
size={Size.MediumLarge}
|
|
51
|
+
/>
|
|
52
|
+
{:else}
|
|
53
|
+
<Icon
|
|
54
|
+
name="nothing"
|
|
55
|
+
colour={Colour['$brand-primary']}
|
|
56
|
+
size={Size.MediumLarge}
|
|
57
|
+
{...option.iconRight}
|
|
58
|
+
/>
|
|
59
|
+
{/if}
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
62
|
<style>button.tab.hidden {
|
|
63
63
|
display: none !important;
|
|
64
64
|
}
|
|
@@ -91,4 +91,4 @@
|
|
|
91
91
|
}
|
|
92
92
|
.tab.invalid {
|
|
93
93
|
color: #aa1414;
|
|
94
|
-
}</style>
|
|
94
|
+
}</style>
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import { dev } from '$app/environment';
|
|
5
|
-
import { resolve } from '$app/paths';
|
|
6
|
-
import { page } from '$app/state';
|
|
7
|
-
import { useAsyncFlag } from '../../Helpers/AsyncFlag.svelte.js';
|
|
8
|
-
import { Size } from '../../Types/Internal/Size.js';
|
|
9
|
-
import Icon from '../Icon.svelte';
|
|
10
|
-
import { Dimensions } from './Dimensions.js';
|
|
11
|
-
import type { SideMenuItemProps } from './SideMenuItemProps.js';
|
|
12
|
-
|
|
13
|
-
const { item, selected: maybeSelected }: SideMenuItemProps = $props();
|
|
14
|
-
|
|
15
|
-
// `page.url.pathname` carries the configured base path; `item.path`
|
|
16
|
-
// does not. Match with `includes` so the comparison holds whether or
|
|
17
|
-
// not a base path (e.g. a `URI_PREFIX` build arg) is set.
|
|
18
|
-
const selected = $derived(
|
|
19
|
-
maybeSelected ?? (item.path ? page.url.pathname.includes(item.path) : false)
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
const visible = useAsyncFlag(() => item.visible, true);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Constrain the rendered `href` to safe, relative paths only, then
|
|
26
|
-
* prefix the SvelteKit `base` path.
|
|
27
|
-
*
|
|
28
|
-
* `item.path` is typed as the SvelteKit `Pathname` brand at the
|
|
29
|
-
* boundary, but the property is still a `string` at runtime. To
|
|
30
|
-
* keep the trust boundary tight we reject anything that contains
|
|
31
|
-
* a URL scheme (anything matching `^[a-z][a-z0-9+.-]*:`) and any
|
|
32
|
-
* input that does not start with `/`. Disallowed values resolve
|
|
33
|
-
* to `#` so the link is harmless if a future consumer supplies an
|
|
34
|
-
* unsafe value.
|
|
35
|
-
*
|
|
36
|
-
* The validated path is run through `resolve` so the rendered `href`
|
|
37
|
-
* honours a configured base path (e.g. a `URI_PREFIX` build arg).
|
|
38
|
-
* `item.path` is the bare app-relative route; without the prefix the
|
|
39
|
-
* `<a href>` would point outside the deployed sub-path.
|
|
40
|
-
*/
|
|
41
|
-
function safeHref(path: string): string {
|
|
42
|
-
if (typeof path !== 'string' || path.length === 0) {
|
|
43
|
-
return '#';
|
|
44
|
-
}
|
|
45
|
-
if (!path.startsWith('/')) {
|
|
46
|
-
return '#';
|
|
47
|
-
}
|
|
48
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) {
|
|
49
|
-
return '#';
|
|
50
|
-
}
|
|
51
|
-
return resolve(path as '/');
|
|
52
|
-
}
|
|
53
|
-
</script>
|
|
54
|
-
|
|
55
|
-
{#if item.callback}
|
|
56
|
-
<button
|
|
57
|
-
onclick={() => {
|
|
58
|
-
item.callback();
|
|
59
|
-
}}
|
|
60
|
-
style="--iconWidth: {Dimensions.smallPanelWidth}rem"
|
|
61
|
-
class:selected
|
|
62
|
-
class:hidden={!visible.value}
|
|
63
|
-
>
|
|
64
|
-
<!-- <div class="selected-marker"></div> -->
|
|
65
|
-
<div class="icon-wrapper">
|
|
66
|
-
{#if item.iconName}
|
|
67
|
-
<Icon
|
|
68
|
-
name={item.iconName}
|
|
69
|
-
size={Size.Medium}
|
|
70
|
-
label={item.label}
|
|
71
|
-
/>
|
|
72
|
-
{/if}
|
|
73
|
-
</div>
|
|
74
|
-
<div class="spacer"></div>
|
|
75
|
-
<div class="text">
|
|
76
|
-
<div class="label">{item.label}</div>
|
|
77
|
-
{#if item.subtitle}
|
|
78
|
-
<div class="subtitle">{item.subtitle}</div>
|
|
79
|
-
{/if}
|
|
80
|
-
</div></button
|
|
81
|
-
>
|
|
82
|
-
{:else if item.path}
|
|
83
|
-
<a
|
|
84
|
-
href={safeHref(item.path)}
|
|
85
|
-
style="--iconWidth: {Dimensions.smallPanelWidth}rem"
|
|
86
|
-
aria-current={selected ? 'page' : undefined}
|
|
87
|
-
class:selected
|
|
88
|
-
class:hidden={!visible.value}
|
|
89
|
-
>
|
|
90
|
-
<!-- <div class="selected-marker"></div> -->
|
|
91
|
-
<div class="icon-wrapper">
|
|
92
|
-
{#if item.iconName}
|
|
93
|
-
<Icon
|
|
94
|
-
name={item.iconName}
|
|
95
|
-
size={Size.Medium}
|
|
96
|
-
label={item.label}
|
|
97
|
-
/>
|
|
98
|
-
{/if}
|
|
99
|
-
</div>
|
|
100
|
-
<div class="spacer"></div>
|
|
101
|
-
<div class="text">
|
|
102
|
-
<div class="label">{item.label}</div>
|
|
103
|
-
{#if item.subtitle}
|
|
104
|
-
<div class="subtitle">{item.subtitle}</div>
|
|
105
|
-
{/if}
|
|
106
|
-
</div></a
|
|
107
|
-
>
|
|
108
|
-
{:else if dev}
|
|
109
|
-
Error: Callback had neither callback nor path
|
|
110
|
-
{/if}
|
|
111
|
-
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { dev } from '$app/environment';
|
|
5
|
+
import { resolve } from '$app/paths';
|
|
6
|
+
import { page } from '$app/state';
|
|
7
|
+
import { useAsyncFlag } from '../../Helpers/AsyncFlag.svelte.js';
|
|
8
|
+
import { Size } from '../../Types/Internal/Size.js';
|
|
9
|
+
import Icon from '../Icon.svelte';
|
|
10
|
+
import { Dimensions } from './Dimensions.js';
|
|
11
|
+
import type { SideMenuItemProps } from './SideMenuItemProps.js';
|
|
12
|
+
|
|
13
|
+
const { item, selected: maybeSelected }: SideMenuItemProps = $props();
|
|
14
|
+
|
|
15
|
+
// `page.url.pathname` carries the configured base path; `item.path`
|
|
16
|
+
// does not. Match with `includes` so the comparison holds whether or
|
|
17
|
+
// not a base path (e.g. a `URI_PREFIX` build arg) is set.
|
|
18
|
+
const selected = $derived(
|
|
19
|
+
maybeSelected ?? (item.path ? page.url.pathname.includes(item.path) : false)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const visible = useAsyncFlag(() => item.visible, true);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Constrain the rendered `href` to safe, relative paths only, then
|
|
26
|
+
* prefix the SvelteKit `base` path.
|
|
27
|
+
*
|
|
28
|
+
* `item.path` is typed as the SvelteKit `Pathname` brand at the
|
|
29
|
+
* boundary, but the property is still a `string` at runtime. To
|
|
30
|
+
* keep the trust boundary tight we reject anything that contains
|
|
31
|
+
* a URL scheme (anything matching `^[a-z][a-z0-9+.-]*:`) and any
|
|
32
|
+
* input that does not start with `/`. Disallowed values resolve
|
|
33
|
+
* to `#` so the link is harmless if a future consumer supplies an
|
|
34
|
+
* unsafe value.
|
|
35
|
+
*
|
|
36
|
+
* The validated path is run through `resolve` so the rendered `href`
|
|
37
|
+
* honours a configured base path (e.g. a `URI_PREFIX` build arg).
|
|
38
|
+
* `item.path` is the bare app-relative route; without the prefix the
|
|
39
|
+
* `<a href>` would point outside the deployed sub-path.
|
|
40
|
+
*/
|
|
41
|
+
function safeHref(path: string): string {
|
|
42
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
43
|
+
return '#';
|
|
44
|
+
}
|
|
45
|
+
if (!path.startsWith('/')) {
|
|
46
|
+
return '#';
|
|
47
|
+
}
|
|
48
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) {
|
|
49
|
+
return '#';
|
|
50
|
+
}
|
|
51
|
+
return resolve(path as '/');
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if item.callback}
|
|
56
|
+
<button
|
|
57
|
+
onclick={() => {
|
|
58
|
+
item.callback();
|
|
59
|
+
}}
|
|
60
|
+
style="--iconWidth: {Dimensions.smallPanelWidth}rem"
|
|
61
|
+
class:selected
|
|
62
|
+
class:hidden={!visible.value}
|
|
63
|
+
>
|
|
64
|
+
<!-- <div class="selected-marker"></div> -->
|
|
65
|
+
<div class="icon-wrapper">
|
|
66
|
+
{#if item.iconName}
|
|
67
|
+
<Icon
|
|
68
|
+
name={item.iconName}
|
|
69
|
+
size={Size.Medium}
|
|
70
|
+
label={item.label}
|
|
71
|
+
/>
|
|
72
|
+
{/if}
|
|
73
|
+
</div>
|
|
74
|
+
<div class="spacer"></div>
|
|
75
|
+
<div class="text">
|
|
76
|
+
<div class="label">{item.label}</div>
|
|
77
|
+
{#if item.subtitle}
|
|
78
|
+
<div class="subtitle">{item.subtitle}</div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div></button
|
|
81
|
+
>
|
|
82
|
+
{:else if item.path}
|
|
83
|
+
<a
|
|
84
|
+
href={safeHref(item.path)}
|
|
85
|
+
style="--iconWidth: {Dimensions.smallPanelWidth}rem"
|
|
86
|
+
aria-current={selected ? 'page' : undefined}
|
|
87
|
+
class:selected
|
|
88
|
+
class:hidden={!visible.value}
|
|
89
|
+
>
|
|
90
|
+
<!-- <div class="selected-marker"></div> -->
|
|
91
|
+
<div class="icon-wrapper">
|
|
92
|
+
{#if item.iconName}
|
|
93
|
+
<Icon
|
|
94
|
+
name={item.iconName}
|
|
95
|
+
size={Size.Medium}
|
|
96
|
+
label={item.label}
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
<div class="spacer"></div>
|
|
101
|
+
<div class="text">
|
|
102
|
+
<div class="label">{item.label}</div>
|
|
103
|
+
{#if item.subtitle}
|
|
104
|
+
<div class="subtitle">{item.subtitle}</div>
|
|
105
|
+
{/if}
|
|
106
|
+
</div></a
|
|
107
|
+
>
|
|
108
|
+
{:else if dev}
|
|
109
|
+
Error: Callback had neither callback nor path
|
|
110
|
+
{/if}
|
|
111
|
+
|
|
112
112
|
<style>button.hidden,
|
|
113
113
|
a.hidden {
|
|
114
114
|
display: none !important;
|
|
@@ -174,4 +174,4 @@ a:hover {
|
|
|
174
174
|
button .spacer,
|
|
175
175
|
a .spacer {
|
|
176
176
|
width: 0.5rem;
|
|
177
|
-
}</style>
|
|
177
|
+
}</style>
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script
|
|
4
|
-
lang="ts"
|
|
5
|
-
generics="Args"
|
|
6
|
-
>
|
|
7
|
-
import { devCatch } from '../../Helpers/Helpers.svelte.js';
|
|
8
|
-
import type { TabOption } from '../../Types/Internal/TabOption.js';
|
|
9
|
-
import HorizontalTabGroup from '../HorizontalTabGroup.svelte';
|
|
10
|
-
import NavTab from './NavTab.svelte';
|
|
11
|
-
import type { ServicesNavProps } from './ServicesNavProps.js';
|
|
12
|
-
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
|
|
14
|
-
const { onclose, serviceTabs, selectedServiceTab }: ServicesNavProps<Args> = $props();
|
|
15
|
-
|
|
16
|
-
let dialogRef: HTMLDialogElement | undefined = $state.raw();
|
|
17
|
-
|
|
18
|
-
function fireClose(): void {
|
|
19
|
-
const result = onclose();
|
|
20
|
-
if (result instanceof Promise) {
|
|
21
|
-
result.catch((e: unknown) => {
|
|
22
|
-
devCatch(e);
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Open the dialog as soon as we mount; native showModal() gives us a
|
|
28
|
-
// real focus trap, Escape-to-close, and inert background without any
|
|
29
|
-
// manual aria-modal / tabindex bookkeeping.
|
|
30
|
-
$effect(() => {
|
|
31
|
-
const dialog = dialogRef;
|
|
32
|
-
if (dialog && !dialog.open) {
|
|
33
|
-
dialog.showModal();
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<dialog
|
|
39
|
-
bind:this={dialogRef}
|
|
40
|
-
aria-label="Services"
|
|
41
|
-
class="overlay"
|
|
42
|
-
oncancel={(e) => {
|
|
43
|
-
// Escape fires `cancel` first; prevent the native close so we
|
|
44
|
-
// fully delegate to the parent (it owns the visibility flag).
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
fireClose();
|
|
47
|
-
}}
|
|
48
|
-
onclick={(e) => {
|
|
49
|
-
// Native <dialog> click target is the dialog element itself when
|
|
50
|
-
// the user clicks the backdrop; clicks on inner content bubble
|
|
51
|
-
// up with a different target.
|
|
52
|
-
if (dialogRef === e.target) {
|
|
53
|
-
fireClose();
|
|
54
|
-
}
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
57
|
-
<div
|
|
58
|
-
role="navigation"
|
|
59
|
-
aria-label="Services tabs"
|
|
60
|
-
class="services"
|
|
61
|
-
>
|
|
62
|
-
<HorizontalTabGroup
|
|
63
|
-
name="nav-services"
|
|
64
|
-
tabs={serviceTabs}
|
|
65
|
-
selected={selectedServiceTab}
|
|
66
|
-
tabComponent={navTab}
|
|
67
|
-
noRadius
|
|
68
|
-
grow
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
</dialog>
|
|
72
|
-
|
|
73
|
-
{#snippet navTab(option: TabOption<Args>, selected: boolean)}
|
|
74
|
-
<NavTab
|
|
75
|
-
{option}
|
|
76
|
-
{selected}
|
|
77
|
-
/>
|
|
78
|
-
{/snippet}
|
|
79
|
-
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script
|
|
4
|
+
lang="ts"
|
|
5
|
+
generics="Args"
|
|
6
|
+
>
|
|
7
|
+
import { devCatch } from '../../Helpers/Helpers.svelte.js';
|
|
8
|
+
import type { TabOption } from '../../Types/Internal/TabOption.js';
|
|
9
|
+
import HorizontalTabGroup from '../HorizontalTabGroup.svelte';
|
|
10
|
+
import NavTab from './NavTab.svelte';
|
|
11
|
+
import type { ServicesNavProps } from './ServicesNavProps.js';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
|
|
14
|
+
const { onclose, serviceTabs, selectedServiceTab }: ServicesNavProps<Args> = $props();
|
|
15
|
+
|
|
16
|
+
let dialogRef: HTMLDialogElement | undefined = $state.raw();
|
|
17
|
+
|
|
18
|
+
function fireClose(): void {
|
|
19
|
+
const result = onclose();
|
|
20
|
+
if (result instanceof Promise) {
|
|
21
|
+
result.catch((e: unknown) => {
|
|
22
|
+
devCatch(e);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Open the dialog as soon as we mount; native showModal() gives us a
|
|
28
|
+
// real focus trap, Escape-to-close, and inert background without any
|
|
29
|
+
// manual aria-modal / tabindex bookkeeping.
|
|
30
|
+
$effect(() => {
|
|
31
|
+
const dialog = dialogRef;
|
|
32
|
+
if (dialog && !dialog.open) {
|
|
33
|
+
dialog.showModal();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<dialog
|
|
39
|
+
bind:this={dialogRef}
|
|
40
|
+
aria-label="Services"
|
|
41
|
+
class="overlay"
|
|
42
|
+
oncancel={(e) => {
|
|
43
|
+
// Escape fires `cancel` first; prevent the native close so we
|
|
44
|
+
// fully delegate to the parent (it owns the visibility flag).
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
fireClose();
|
|
47
|
+
}}
|
|
48
|
+
onclick={(e) => {
|
|
49
|
+
// Native <dialog> click target is the dialog element itself when
|
|
50
|
+
// the user clicks the backdrop; clicks on inner content bubble
|
|
51
|
+
// up with a different target.
|
|
52
|
+
if (dialogRef === e.target) {
|
|
53
|
+
fireClose();
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
role="navigation"
|
|
59
|
+
aria-label="Services tabs"
|
|
60
|
+
class="services"
|
|
61
|
+
>
|
|
62
|
+
<HorizontalTabGroup
|
|
63
|
+
name="nav-services"
|
|
64
|
+
tabs={serviceTabs}
|
|
65
|
+
selected={selectedServiceTab}
|
|
66
|
+
tabComponent={navTab}
|
|
67
|
+
noRadius
|
|
68
|
+
grow
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</dialog>
|
|
72
|
+
|
|
73
|
+
{#snippet navTab(option: TabOption<Args>, selected: boolean)}
|
|
74
|
+
<NavTab
|
|
75
|
+
{option}
|
|
76
|
+
{selected}
|
|
77
|
+
/>
|
|
78
|
+
{/snippet}
|
|
79
|
+
|
|
80
80
|
<style>dialog.overlay {
|
|
81
81
|
position: fixed;
|
|
82
82
|
top: calc(3.25rem + var(--env-border-height, 0));
|
|
@@ -115,4 +115,4 @@ div.services {
|
|
|
115
115
|
justify-content: stretch;
|
|
116
116
|
height: var(--services-nav-height, min(710px, 80%));
|
|
117
117
|
width: var(--services-nav-width, 800px);
|
|
118
|
-
}</style>
|
|
118
|
+
}</style>
|