@sepale/components 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +65 -3
- package/dist/components/Accordion.svelte +48 -0
- package/dist/components/Accordion.svelte.d.ts +24 -0
- package/dist/components/Alert.svelte +107 -0
- package/dist/components/Alert.svelte.d.ts +40 -0
- package/dist/components/Avatar.svelte +75 -0
- package/dist/components/Avatar.svelte.d.ts +37 -0
- package/dist/components/Badge.svelte +46 -0
- package/dist/components/Badge.svelte.d.ts +22 -0
- package/dist/components/Breadcrumbs.svelte +36 -0
- package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
- package/dist/components/Bubble.svelte +43 -0
- package/dist/components/Bubble.svelte.d.ts +23 -0
- package/dist/components/Button.svelte +132 -0
- package/dist/components/Button.svelte.d.ts +40 -0
- package/dist/components/Calendar.svelte +193 -0
- package/dist/components/Calendar.svelte.d.ts +60 -0
- package/dist/components/Card.svelte +69 -0
- package/dist/components/Card.svelte.d.ts +37 -0
- package/dist/components/Carousel.svelte +550 -0
- package/dist/components/Carousel.svelte.d.ts +124 -0
- package/dist/components/Checkbox.svelte +48 -0
- package/dist/components/Checkbox.svelte.d.ts +26 -0
- package/dist/components/Chip.svelte +60 -0
- package/dist/components/Chip.svelte.d.ts +26 -0
- package/dist/components/ChipsInput.svelte +158 -0
- package/dist/components/ChipsInput.svelte.d.ts +64 -0
- package/dist/components/Collapse.svelte +101 -0
- package/dist/components/Collapse.svelte.d.ts +46 -0
- package/dist/components/ColorPicker.svelte +135 -0
- package/dist/components/ColorPicker.svelte.d.ts +44 -0
- package/dist/components/Combobox.svelte +496 -0
- package/dist/components/Combobox.svelte.d.ts +96 -0
- package/dist/components/DataControl.svelte +754 -0
- package/dist/components/DataControl.svelte.d.ts +143 -0
- package/dist/components/DataHandler.svelte +67 -0
- package/dist/components/DataHandler.svelte.d.ts +44 -0
- package/dist/components/DatePicker.svelte +185 -0
- package/dist/components/DatePicker.svelte.d.ts +56 -0
- package/dist/components/Dialog.svelte +194 -0
- package/dist/components/Dialog.svelte.d.ts +51 -0
- package/dist/components/Divider.svelte +54 -0
- package/dist/components/Divider.svelte.d.ts +27 -0
- package/dist/components/Dock.svelte +34 -0
- package/dist/components/Dock.svelte.d.ts +18 -0
- package/dist/components/Drawer.svelte +174 -0
- package/dist/components/Drawer.svelte.d.ts +51 -0
- package/dist/components/Dropdown.svelte +183 -0
- package/dist/components/Dropdown.svelte.d.ts +52 -0
- package/dist/components/Editable.svelte +292 -0
- package/dist/components/Editable.svelte.d.ts +113 -0
- package/dist/components/EmptyState.svelte +64 -0
- package/dist/components/EmptyState.svelte.d.ts +29 -0
- package/dist/components/Fieldset.svelte +42 -0
- package/dist/components/Fieldset.svelte.d.ts +20 -0
- package/dist/components/FileDropzone.svelte +154 -0
- package/dist/components/FileDropzone.svelte.d.ts +45 -0
- package/dist/components/FileExplorer.svelte +643 -0
- package/dist/components/FileExplorer.svelte.d.ts +114 -0
- package/dist/components/FileInput.svelte +125 -0
- package/dist/components/FileInput.svelte.d.ts +51 -0
- package/dist/components/FloatingLabel.svelte +38 -0
- package/dist/components/FloatingLabel.svelte.d.ts +24 -0
- package/dist/components/Footer.svelte +68 -0
- package/dist/components/Footer.svelte.d.ts +37 -0
- package/dist/components/Grid.svelte +122 -0
- package/dist/components/Grid.svelte.d.ts +72 -0
- package/dist/components/Hero.svelte +62 -0
- package/dist/components/Hero.svelte.d.ts +33 -0
- package/dist/components/Input.svelte +184 -0
- package/dist/components/Input.svelte.d.ts +65 -0
- package/dist/components/Join.svelte +38 -0
- package/dist/components/Join.svelte.d.ts +21 -0
- package/dist/components/Kanban.svelte +284 -0
- package/dist/components/Kanban.svelte.d.ts +93 -0
- package/dist/components/Label.svelte +32 -0
- package/dist/components/Label.svelte.d.ts +21 -0
- package/dist/components/LaptopFrame.svelte +44 -0
- package/dist/components/LaptopFrame.svelte.d.ts +21 -0
- package/dist/components/List.svelte +95 -0
- package/dist/components/List.svelte.d.ts +69 -0
- package/dist/components/Loading.svelte +37 -0
- package/dist/components/Loading.svelte.d.ts +17 -0
- package/dist/components/Menu.svelte +144 -0
- package/dist/components/Menu.svelte.d.ts +53 -0
- package/dist/components/MobileFrame.svelte +41 -0
- package/dist/components/MobileFrame.svelte.d.ts +21 -0
- package/dist/components/Navbar.svelte +49 -0
- package/dist/components/Navbar.svelte.d.ts +25 -0
- package/dist/components/NumberInput.svelte +255 -0
- package/dist/components/NumberInput.svelte.d.ts +68 -0
- package/dist/components/Pagination.svelte +169 -0
- package/dist/components/Pagination.svelte.d.ts +50 -0
- package/dist/components/Progress.svelte +65 -0
- package/dist/components/Progress.svelte.d.ts +28 -0
- package/dist/components/RadialProgress.svelte +48 -0
- package/dist/components/RadialProgress.svelte.d.ts +20 -0
- package/dist/components/Radio.svelte +50 -0
- package/dist/components/Radio.svelte.d.ts +28 -0
- package/dist/components/Range.svelte +53 -0
- package/dist/components/Range.svelte.d.ts +26 -0
- package/dist/components/Rating.svelte +90 -0
- package/dist/components/Rating.svelte.d.ts +41 -0
- package/dist/components/Select.svelte +338 -0
- package/dist/components/Select.svelte.d.ts +74 -0
- package/dist/components/Sidebar.svelte +100 -0
- package/dist/components/Sidebar.svelte.d.ts +54 -0
- package/dist/components/Skeleton.svelte +40 -0
- package/dist/components/Skeleton.svelte.d.ts +18 -0
- package/dist/components/Stat.svelte +71 -0
- package/dist/components/Stat.svelte.d.ts +35 -0
- package/dist/components/Stats.svelte +38 -0
- package/dist/components/Stats.svelte.d.ts +22 -0
- package/dist/components/Steps.svelte +204 -0
- package/dist/components/Steps.svelte.d.ts +66 -0
- package/dist/components/Subtitle.svelte +44 -0
- package/dist/components/Subtitle.svelte.d.ts +26 -0
- package/dist/components/Swatch.svelte +61 -0
- package/dist/components/Swatch.svelte.d.ts +27 -0
- package/dist/components/Switch.svelte +105 -0
- package/dist/components/Switch.svelte.d.ts +44 -0
- package/dist/components/Table.svelte +243 -0
- package/dist/components/Table.svelte.d.ts +121 -0
- package/dist/components/Tabs.svelte +142 -0
- package/dist/components/Tabs.svelte.d.ts +80 -0
- package/dist/components/Text.svelte +43 -0
- package/dist/components/Text.svelte.d.ts +25 -0
- package/dist/components/Textarea.svelte +76 -0
- package/dist/components/Textarea.svelte.d.ts +39 -0
- package/dist/components/Title.svelte +46 -0
- package/dist/components/Title.svelte.d.ts +29 -0
- package/dist/components/Toast.svelte +154 -0
- package/dist/components/Toast.svelte.d.ts +49 -0
- package/dist/components/Toggle.svelte +48 -0
- package/dist/components/Toggle.svelte.d.ts +25 -0
- package/dist/components/Tooltip.svelte +70 -0
- package/dist/components/Tooltip.svelte.d.ts +34 -0
- package/dist/components/WindowFrame.svelte +53 -0
- package/dist/components/WindowFrame.svelte.d.ts +24 -0
- package/dist/components/accordion-context.d.ts +8 -0
- package/dist/components/accordion-context.js +25 -0
- package/dist/components/color.d.ts +39 -0
- package/dist/components/color.js +92 -0
- package/dist/components/data-handler.svelte.d.ts +362 -0
- package/dist/components/data-handler.svelte.js +690 -0
- package/dist/components/file-icons.d.ts +25 -0
- package/dist/components/file-icons.js +46 -0
- package/dist/family-types.d.ts +45 -0
- package/dist/family-types.js +73 -0
- package/dist/generated/classes.d.ts +17 -0
- package/dist/generated/classes.js +2511 -0
- package/dist/generated/components.d.ts +39 -0
- package/dist/generated/components.js +28666 -0
- package/dist/generated/registry.d.ts +35 -0
- package/dist/generated/registry.js +2658 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +99 -0
- package/dist/sepale-types.d.ts +8 -0
- package/dist/sepale-types.js +200 -0
- package/dist/theme.d.ts +123 -0
- package/dist/theme.js +226 -0
- package/dist/variants.d.ts +124 -0
- package/dist/variants.js +70 -0
- package/dist/vendor/air-datepicker/LICENSE.md +21 -0
- package/dist/vendor/air-datepicker/README.md +19 -0
- package/dist/vendor/air-datepicker/buttonPresets.d.ts +13 -0
- package/dist/vendor/air-datepicker/buttonPresets.js +12 -0
- package/dist/vendor/air-datepicker/consts.d.ts +18 -0
- package/dist/vendor/air-datepicker/consts.js +35 -0
- package/dist/vendor/air-datepicker/datepicker.d.ts +289 -0
- package/dist/vendor/air-datepicker/datepicker.js +1435 -0
- package/dist/vendor/air-datepicker/datepickerBody.d.ts +46 -0
- package/dist/vendor/air-datepicker/datepickerBody.js +386 -0
- package/dist/vendor/air-datepicker/datepickerButtons.d.ts +25 -0
- package/dist/vendor/air-datepicker/datepickerButtons.js +85 -0
- package/dist/vendor/air-datepicker/datepickerCell.d.ts +35 -0
- package/dist/vendor/air-datepicker/datepickerCell.js +297 -0
- package/dist/vendor/air-datepicker/datepickerKeyboard.d.ts +26 -0
- package/dist/vendor/air-datepicker/datepickerKeyboard.js +207 -0
- package/dist/vendor/air-datepicker/datepickerNav.d.ts +31 -0
- package/dist/vendor/air-datepicker/datepickerNav.js +200 -0
- package/dist/vendor/air-datepicker/datepickerTime.d.ts +60 -0
- package/dist/vendor/air-datepicker/datepickerTime.js +276 -0
- package/dist/vendor/air-datepicker/defaults.d.ts +65 -0
- package/dist/vendor/air-datepicker/defaults.js +79 -0
- package/dist/vendor/air-datepicker/index.d.ts +168 -0
- package/dist/vendor/air-datepicker/index.js +3 -0
- package/dist/vendor/air-datepicker/locale/ar.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/ar.js +14 -0
- package/dist/vendor/air-datepicker/locale/bg.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/bg.js +14 -0
- package/dist/vendor/air-datepicker/locale/ca.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/ca.js +14 -0
- package/dist/vendor/air-datepicker/locale/cs.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/cs.js +14 -0
- package/dist/vendor/air-datepicker/locale/da.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/da.js +14 -0
- package/dist/vendor/air-datepicker/locale/de.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/de.js +14 -0
- package/dist/vendor/air-datepicker/locale/el.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/el.js +14 -0
- package/dist/vendor/air-datepicker/locale/en.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/en.js +14 -0
- package/dist/vendor/air-datepicker/locale/es.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/es.js +14 -0
- package/dist/vendor/air-datepicker/locale/eu.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/eu.js +14 -0
- package/dist/vendor/air-datepicker/locale/fi.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/fi.js +14 -0
- package/dist/vendor/air-datepicker/locale/fr.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/fr.js +14 -0
- package/dist/vendor/air-datepicker/locale/hr.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/hr.js +14 -0
- package/dist/vendor/air-datepicker/locale/hu.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/hu.js +14 -0
- package/dist/vendor/air-datepicker/locale/id.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/id.js +14 -0
- package/dist/vendor/air-datepicker/locale/it.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/it.js +14 -0
- package/dist/vendor/air-datepicker/locale/ja.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/ja.js +14 -0
- package/dist/vendor/air-datepicker/locale/ko.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/ko.js +14 -0
- package/dist/vendor/air-datepicker/locale/nb.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/nb.js +14 -0
- package/dist/vendor/air-datepicker/locale/nl.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/nl.js +14 -0
- package/dist/vendor/air-datepicker/locale/pl.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/pl.js +14 -0
- package/dist/vendor/air-datepicker/locale/pt-BR.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/pt-BR.js +14 -0
- package/dist/vendor/air-datepicker/locale/pt.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/pt.js +14 -0
- package/dist/vendor/air-datepicker/locale/ro.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/ro.js +14 -0
- package/dist/vendor/air-datepicker/locale/ru.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/ru.js +14 -0
- package/dist/vendor/air-datepicker/locale/si.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/si.js +14 -0
- package/dist/vendor/air-datepicker/locale/sk.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/sk.js +14 -0
- package/dist/vendor/air-datepicker/locale/sl.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/sl.js +14 -0
- package/dist/vendor/air-datepicker/locale/sv.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/sv.js +14 -0
- package/dist/vendor/air-datepicker/locale/th.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/th.js +14 -0
- package/dist/vendor/air-datepicker/locale/tr.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/tr.js +16 -0
- package/dist/vendor/air-datepicker/locale/uk.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/uk.js +14 -0
- package/dist/vendor/air-datepicker/locale/zh.d.ts +4 -0
- package/dist/vendor/air-datepicker/locale/zh.js +14 -0
- package/dist/vendor/air-datepicker/typedefs.d.ts +3 -0
- package/dist/vendor/air-datepicker/typedefs.js +15 -0
- package/dist/vendor/air-datepicker/utils.d.ts +155 -0
- package/dist/vendor/air-datepicker/utils.js +395 -0
- package/dist/vendor/air-datepicker/withEvents.d.ts +1 -0
- package/dist/vendor/air-datepicker/withEvents.js +39 -0
- package/package.json +44 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** What .hero is made of, classified. The props follow the
|
|
3
|
+
families the project registers (variants.ts, ADR 0015). */
|
|
4
|
+
export const PREFIX = 'hero';
|
|
5
|
+
|
|
6
|
+
export const AXES = {
|
|
7
|
+
color: ['primary', 'secondary', 'accent', 'neutral', 'info', 'success', 'warning', 'error'],
|
|
8
|
+
size: ['xs', 'sm', 'md', 'lg', 'xl']
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export const FLAGS = {
|
|
12
|
+
center: 'Centre the text; best without media.',
|
|
13
|
+
reverse: 'Put the media first.'
|
|
14
|
+
} as const;
|
|
15
|
+
export const PARTS = ['content', 'title', 'body', 'actions', 'media'] as const;
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
/**
|
|
20
|
+
* The large opening block of a page: a title, a sentence, a call to action,
|
|
21
|
+
* and optionally an image beside them — stacked when there is no room.
|
|
22
|
+
*/
|
|
23
|
+
import type { Snippet } from 'svelte';
|
|
24
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
25
|
+
import { applyAxes, cx, type AxisProps } from '../variants.js';
|
|
26
|
+
|
|
27
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLAttributes<HTMLElement>, 'color' | 'title'> {
|
|
28
|
+
title?: string;
|
|
29
|
+
/** The heading level of the title. */
|
|
30
|
+
level?: 1 | 2 | 3;
|
|
31
|
+
actions?: Snippet;
|
|
32
|
+
/** An image, a video, an illustration. */
|
|
33
|
+
media?: Snippet;
|
|
34
|
+
class?: string;
|
|
35
|
+
/** The sentence under the title. */
|
|
36
|
+
children?: Snippet;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The axes are deliberately NOT destructured: they travel in `axes` and are
|
|
40
|
+
// resolved by name, so an axis a family drops changes nothing down here.
|
|
41
|
+
let {
|
|
42
|
+
title,
|
|
43
|
+
level = 1,
|
|
44
|
+
actions,
|
|
45
|
+
media,
|
|
46
|
+
class: klass = '',
|
|
47
|
+
children,
|
|
48
|
+
...axes
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
let applied = $derived(applyAxes(PREFIX, AXES, FLAGS, axes));
|
|
52
|
+
let classes = $derived(cx(PREFIX, applied.classes, klass));
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<section {...applied.rest} class={classes}>
|
|
56
|
+
<div class="hero-content">
|
|
57
|
+
{#if title}<svelte:element this={`h${level}`} class="hero-title">{title}</svelte:element>{/if}
|
|
58
|
+
{#if children}<p class="hero-body">{@render children()}</p>{/if}
|
|
59
|
+
{#if actions}<div class="hero-actions">{@render actions()}</div>{/if}
|
|
60
|
+
</div>
|
|
61
|
+
{#if media}<div class="hero-media">{@render media()}</div>{/if}
|
|
62
|
+
</section>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** What .hero is made of, classified. The props follow the
|
|
2
|
+
families the project registers (variants.ts, ADR 0015). */
|
|
3
|
+
export declare const PREFIX = "hero";
|
|
4
|
+
export declare const AXES: {
|
|
5
|
+
readonly color: readonly ["primary", "secondary", "accent", "neutral", "info", "success", "warning", "error"];
|
|
6
|
+
readonly size: readonly ["xs", "sm", "md", "lg", "xl"];
|
|
7
|
+
};
|
|
8
|
+
export declare const FLAGS: {
|
|
9
|
+
readonly center: "Centre the text; best without media.";
|
|
10
|
+
readonly reverse: "Put the media first.";
|
|
11
|
+
};
|
|
12
|
+
export declare const PARTS: readonly ["content", "title", "body", "actions", "media"];
|
|
13
|
+
/**
|
|
14
|
+
* The large opening block of a page: a title, a sentence, a call to action,
|
|
15
|
+
* and optionally an image beside them — stacked when there is no room.
|
|
16
|
+
*/
|
|
17
|
+
import type { Snippet } from 'svelte';
|
|
18
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
19
|
+
import { type AxisProps } from '../variants.js';
|
|
20
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLAttributes<HTMLElement>, 'color' | 'title'> {
|
|
21
|
+
title?: string;
|
|
22
|
+
/** The heading level of the title. */
|
|
23
|
+
level?: 1 | 2 | 3;
|
|
24
|
+
actions?: Snippet;
|
|
25
|
+
/** An image, a video, an illustration. */
|
|
26
|
+
media?: Snippet;
|
|
27
|
+
class?: string;
|
|
28
|
+
/** The sentence under the title. */
|
|
29
|
+
children?: Snippet;
|
|
30
|
+
}
|
|
31
|
+
declare const Hero: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
type Hero = ReturnType<typeof Hero>;
|
|
33
|
+
export default Hero;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** What .input is made of, classified. The props follow the
|
|
3
|
+
families the project registers (variants.ts, ADR 0015). */
|
|
4
|
+
export const PREFIX = 'input';
|
|
5
|
+
|
|
6
|
+
export const AXES = {
|
|
7
|
+
color: ['primary', 'secondary', 'accent', 'neutral', 'info', 'success', 'warning', 'error'],
|
|
8
|
+
size: ['xs', 'sm', 'md', 'lg', 'xl'],
|
|
9
|
+
variant: ['ghost']
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export const FLAGS = {
|
|
13
|
+
nospinner: 'Hide the step arrows of a number field.'
|
|
14
|
+
} as const;
|
|
15
|
+
export const PARTS = [] as const;
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
/**
|
|
21
|
+
* A text field. Plain by default — one `<input class="input">` — and a
|
|
22
|
+
* frame around the field (`<label class="input">`) as soon as it needs one:
|
|
23
|
+
* something drawn before or after the text (`start`, `end`). Suggestions
|
|
24
|
+
* are a Combobox's job.
|
|
25
|
+
*
|
|
26
|
+
* Validation stays the browser's: `required`, `pattern`, `min`… are plain
|
|
27
|
+
* attributes, and the message shown is the browser's own `validationMessage`,
|
|
28
|
+
* in the visitor's language. `error` forces one from outside.
|
|
29
|
+
*/
|
|
30
|
+
import type { Snippet } from 'svelte';
|
|
31
|
+
import type { ClassValue, HTMLInputAttributes } from 'svelte/elements';
|
|
32
|
+
import { applyAxes, cx, type AxisProps } from '../variants.js';
|
|
33
|
+
|
|
34
|
+
// `size` is ours — the HTML attribute of the same name is a character count
|
|
35
|
+
// our CSS overrides anyway.
|
|
36
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLInputAttributes, 'size' | 'color'> {
|
|
37
|
+
/**
|
|
38
|
+
* Colour the field by its own validity and reveal `hint` once the
|
|
39
|
+
* browser marks it invalid. The CSS drives this off `:user-invalid`,
|
|
40
|
+
* so it only fires after the visitor has actually had a go.
|
|
41
|
+
*/
|
|
42
|
+
validator?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Shown under the field when it is invalid. It has to be the field's
|
|
45
|
+
* next sibling for the CSS to reach it, which is the whole reason this
|
|
46
|
+
* is a prop and not something you write yourself. Without it, the
|
|
47
|
+
* message is the browser's own.
|
|
48
|
+
*/
|
|
49
|
+
hint?: Snippet;
|
|
50
|
+
/**
|
|
51
|
+
* An error from outside (the server said no): marks the field invalid and
|
|
52
|
+
* shows this, for as long as it is given. The parent owns it — it knows
|
|
53
|
+
* the value and is the one able to test it — so the field never clears it.
|
|
54
|
+
*/
|
|
55
|
+
error?: string | null;
|
|
56
|
+
/** The message of the last check, `false` when the field passed. Bindable. */
|
|
57
|
+
invalid?: string | false;
|
|
58
|
+
/** Drawn inside the frame, before the text: an icon, a label, a unit. */
|
|
59
|
+
start?: Snippet;
|
|
60
|
+
/** Drawn inside the frame, after the text. */
|
|
61
|
+
end?: Snippet;
|
|
62
|
+
/** The field grows with its text. A number adds that many characters of room. */
|
|
63
|
+
autowidth?: boolean | number;
|
|
64
|
+
/** The `<input>` itself. Bindable. */
|
|
65
|
+
element?: HTMLInputElement;
|
|
66
|
+
class?: string;
|
|
67
|
+
value?: any;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The axes are deliberately NOT destructured: they travel in `axes` and are
|
|
71
|
+
// resolved by name, so an axis a family drops changes nothing down here.
|
|
72
|
+
let {
|
|
73
|
+
validator = false,
|
|
74
|
+
hint,
|
|
75
|
+
error = null,
|
|
76
|
+
invalid = $bindable(false),
|
|
77
|
+
start,
|
|
78
|
+
end,
|
|
79
|
+
autowidth = false,
|
|
80
|
+
element = $bindable(),
|
|
81
|
+
value = $bindable(''),
|
|
82
|
+
class: klass = '',
|
|
83
|
+
disabled = false,
|
|
84
|
+
autocomplete,
|
|
85
|
+
oninput,
|
|
86
|
+
onfocus,
|
|
87
|
+
onblur,
|
|
88
|
+
onkeydown,
|
|
89
|
+
...axes
|
|
90
|
+
}: Props = $props();
|
|
91
|
+
|
|
92
|
+
let applied = $derived(applyAxes(PREFIX, AXES, FLAGS, axes));
|
|
93
|
+
|
|
94
|
+
/* ── Validity ─────────────────────────────────────────────────────── */
|
|
95
|
+
|
|
96
|
+
/** What is shown under the field. A disabled field shows nothing. */
|
|
97
|
+
let message = $derived(disabled ? '' : error || (typeof invalid === 'string' ? invalid : ''));
|
|
98
|
+
|
|
99
|
+
/* An error from outside is the field's validity too: a form holding it
|
|
100
|
+
does not submit. */
|
|
101
|
+
$effect(() => {
|
|
102
|
+
element?.setCustomValidity(disabled || !error ? '' : error);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let classes = $derived(
|
|
106
|
+
cx(PREFIX, applied.classes, (validator || !!message) && 'validator', klass)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
/** Check the field now and show the browser's message if it fails. */
|
|
110
|
+
export function isValid(): boolean {
|
|
111
|
+
if (!element || disabled) return !error;
|
|
112
|
+
invalid = element.validity.valid ? false : element.validationMessage;
|
|
113
|
+
return !error && !invalid;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function focus() {
|
|
117
|
+
element?.focus();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Empty the field. */
|
|
121
|
+
export function clear() {
|
|
122
|
+
value = '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ── Width ────────────────────────────────────────────────────────── */
|
|
126
|
+
|
|
127
|
+
let width = $derived(
|
|
128
|
+
autowidth
|
|
129
|
+
? `${Math.max(`${value ?? ''}`.length, 5) + (typeof autowidth === 'number' ? autowidth : 0) + 2}ch`
|
|
130
|
+
: undefined
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
/* ── Events ───────────────────────────────────────────────────────── */
|
|
134
|
+
|
|
135
|
+
function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
136
|
+
// A field shown invalid is re-checked as it is corrected.
|
|
137
|
+
if (invalid) invalid = e.currentTarget.validity.valid ? false : e.currentTarget.validationMessage;
|
|
138
|
+
oninput?.(e);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function handleBlur(e: FocusEvent & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
142
|
+
if (validator) isValid();
|
|
143
|
+
onblur?.(e);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let framed = $derived(!!(start || end));
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
{#snippet field(cls: ClassValue | undefined, style: string | undefined)}
|
|
150
|
+
<!-- Ours first: what the caller writes in the spread still wins. -->
|
|
151
|
+
<input
|
|
152
|
+
aria-invalid={message ? 'true' : undefined}
|
|
153
|
+
{...applied.rest}
|
|
154
|
+
bind:this={element}
|
|
155
|
+
bind:value
|
|
156
|
+
class={cls}
|
|
157
|
+
style:width={style}
|
|
158
|
+
{disabled}
|
|
159
|
+
{autocomplete}
|
|
160
|
+
oninput={handleInput}
|
|
161
|
+
{onfocus}
|
|
162
|
+
onblur={handleBlur}
|
|
163
|
+
{onkeydown}
|
|
164
|
+
/>
|
|
165
|
+
{/snippet}
|
|
166
|
+
|
|
167
|
+
{#if framed}
|
|
168
|
+
<label class={classes} style:width>
|
|
169
|
+
{@render start?.()}
|
|
170
|
+
{@render field(undefined, undefined)}
|
|
171
|
+
{@render end?.()}
|
|
172
|
+
</label>
|
|
173
|
+
{:else}
|
|
174
|
+
{@render field(classes, width)}
|
|
175
|
+
{/if}
|
|
176
|
+
<!-- An error from outside first, then the caller's hint, then the browser's
|
|
177
|
+
own message. -->
|
|
178
|
+
{#if error && !disabled}
|
|
179
|
+
<p class="validator-hint">{error}</p>
|
|
180
|
+
{:else if hint}
|
|
181
|
+
<p class="validator-hint">{@render hint()}</p>
|
|
182
|
+
{:else if message}
|
|
183
|
+
<p class="validator-hint">{message}</p>
|
|
184
|
+
{/if}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** What .input is made of, classified. The props follow the
|
|
2
|
+
families the project registers (variants.ts, ADR 0015). */
|
|
3
|
+
export declare const PREFIX = "input";
|
|
4
|
+
export declare const AXES: {
|
|
5
|
+
readonly color: readonly ["primary", "secondary", "accent", "neutral", "info", "success", "warning", "error"];
|
|
6
|
+
readonly size: readonly ["xs", "sm", "md", "lg", "xl"];
|
|
7
|
+
readonly variant: readonly ["ghost"];
|
|
8
|
+
};
|
|
9
|
+
export declare const FLAGS: {
|
|
10
|
+
readonly nospinner: "Hide the step arrows of a number field.";
|
|
11
|
+
};
|
|
12
|
+
export declare const PARTS: readonly [];
|
|
13
|
+
/**
|
|
14
|
+
* A text field. Plain by default — one `<input class="input">` — and a
|
|
15
|
+
* frame around the field (`<label class="input">`) as soon as it needs one:
|
|
16
|
+
* something drawn before or after the text (`start`, `end`). Suggestions
|
|
17
|
+
* are a Combobox's job.
|
|
18
|
+
*
|
|
19
|
+
* Validation stays the browser's: `required`, `pattern`, `min`… are plain
|
|
20
|
+
* attributes, and the message shown is the browser's own `validationMessage`,
|
|
21
|
+
* in the visitor's language. `error` forces one from outside.
|
|
22
|
+
*/
|
|
23
|
+
import type { Snippet } from 'svelte';
|
|
24
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
25
|
+
import { type AxisProps } from '../variants.js';
|
|
26
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLInputAttributes, 'size' | 'color'> {
|
|
27
|
+
/**
|
|
28
|
+
* Colour the field by its own validity and reveal `hint` once the
|
|
29
|
+
* browser marks it invalid. The CSS drives this off `:user-invalid`,
|
|
30
|
+
* so it only fires after the visitor has actually had a go.
|
|
31
|
+
*/
|
|
32
|
+
validator?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Shown under the field when it is invalid. It has to be the field's
|
|
35
|
+
* next sibling for the CSS to reach it, which is the whole reason this
|
|
36
|
+
* is a prop and not something you write yourself. Without it, the
|
|
37
|
+
* message is the browser's own.
|
|
38
|
+
*/
|
|
39
|
+
hint?: Snippet;
|
|
40
|
+
/**
|
|
41
|
+
* An error from outside (the server said no): marks the field invalid and
|
|
42
|
+
* shows this, for as long as it is given. The parent owns it — it knows
|
|
43
|
+
* the value and is the one able to test it — so the field never clears it.
|
|
44
|
+
*/
|
|
45
|
+
error?: string | null;
|
|
46
|
+
/** The message of the last check, `false` when the field passed. Bindable. */
|
|
47
|
+
invalid?: string | false;
|
|
48
|
+
/** Drawn inside the frame, before the text: an icon, a label, a unit. */
|
|
49
|
+
start?: Snippet;
|
|
50
|
+
/** Drawn inside the frame, after the text. */
|
|
51
|
+
end?: Snippet;
|
|
52
|
+
/** The field grows with its text. A number adds that many characters of room. */
|
|
53
|
+
autowidth?: boolean | number;
|
|
54
|
+
/** The `<input>` itself. Bindable. */
|
|
55
|
+
element?: HTMLInputElement;
|
|
56
|
+
class?: string;
|
|
57
|
+
value?: any;
|
|
58
|
+
}
|
|
59
|
+
declare const Input: import("svelte").Component<Props, {
|
|
60
|
+
isValid: () => boolean;
|
|
61
|
+
focus: () => void;
|
|
62
|
+
clear: () => void;
|
|
63
|
+
}, "value" | "invalid" | "element">;
|
|
64
|
+
type Input = ReturnType<typeof Input>;
|
|
65
|
+
export default Input;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** What .join is made of, classified. The props follow the
|
|
3
|
+
families the project registers (variants.ts, ADR 0015).
|
|
4
|
+
|
|
5
|
+
Each child must carry `join-item` for the seams to land; that class is
|
|
6
|
+
a part of this component, not a component of its own. */
|
|
7
|
+
export const PREFIX = 'join';
|
|
8
|
+
|
|
9
|
+
export const AXES = {
|
|
10
|
+
/* Which way the seams run. */
|
|
11
|
+
direction: ['horizontal', 'vertical']
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const FLAGS = {} as const;
|
|
15
|
+
export const PARTS = ['item'] as const;
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import type { Snippet } from 'svelte';
|
|
20
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
21
|
+
import { applyAxes, cx, type AxisProps } from '../variants.js';
|
|
22
|
+
|
|
23
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, HTMLAttributes<HTMLDivElement> {
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
class: klass = '',
|
|
30
|
+
children,
|
|
31
|
+
...axes
|
|
32
|
+
}: Props = $props();
|
|
33
|
+
|
|
34
|
+
let applied = $derived(applyAxes(PREFIX, AXES, FLAGS, axes));
|
|
35
|
+
let classes = $derived(cx(PREFIX, applied.classes, klass));
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<div {...applied.rest} class={classes}>{@render children?.()}</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** What .join is made of, classified. The props follow the
|
|
2
|
+
families the project registers (variants.ts, ADR 0015).
|
|
3
|
+
|
|
4
|
+
Each child must carry `join-item` for the seams to land; that class is
|
|
5
|
+
a part of this component, not a component of its own. */
|
|
6
|
+
export declare const PREFIX = "join";
|
|
7
|
+
export declare const AXES: {
|
|
8
|
+
readonly direction: readonly ["horizontal", "vertical"];
|
|
9
|
+
};
|
|
10
|
+
export declare const FLAGS: {};
|
|
11
|
+
export declare const PARTS: readonly ["item"];
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
14
|
+
import { type AxisProps } from '../variants.js';
|
|
15
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
class?: string;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
}
|
|
19
|
+
declare const Join: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type Join = ReturnType<typeof Join>;
|
|
21
|
+
export default Join;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** What .kanban is made of, classified. The props follow the
|
|
3
|
+
families the project registers (variants.ts, ADR 0015). */
|
|
4
|
+
export const PREFIX = 'kanban';
|
|
5
|
+
|
|
6
|
+
export const AXES = {
|
|
7
|
+
size: ['xs', 'sm', 'md', 'lg', 'xl'],
|
|
8
|
+
/* Space between the columns. */
|
|
9
|
+
gap: ['none', 'tight', 'loose']
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export const FLAGS = {
|
|
13
|
+
loading: 'Fade the columns while new items are on their way.'
|
|
14
|
+
} as const;
|
|
15
|
+
export const PARTS = [
|
|
16
|
+
'column',
|
|
17
|
+
'column-over',
|
|
18
|
+
'column-head',
|
|
19
|
+
'column-title',
|
|
20
|
+
'column-count',
|
|
21
|
+
'column-body',
|
|
22
|
+
'column-foot',
|
|
23
|
+
'column-empty',
|
|
24
|
+
'item',
|
|
25
|
+
'item-selected',
|
|
26
|
+
'item-dirty',
|
|
27
|
+
'item-dragged',
|
|
28
|
+
'marker'
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
export type KanbanColumn = {
|
|
32
|
+
id: string;
|
|
33
|
+
/** Defaults to the id. */
|
|
34
|
+
title?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type KanbanMove<Item> = {
|
|
38
|
+
item: Item;
|
|
39
|
+
/** The column it leaves. */
|
|
40
|
+
from: string;
|
|
41
|
+
/** The column it lands in: `from` when it only changed places. */
|
|
42
|
+
to: string;
|
|
43
|
+
/** Its place in `to`, counted without the item itself. */
|
|
44
|
+
index: number;
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<script lang="ts" generics="Item">
|
|
49
|
+
/**
|
|
50
|
+
* Items in columns, one column per state they can be in. Like a table or a
|
|
51
|
+
* grid it knows nothing of where its items come from, and it moves nothing:
|
|
52
|
+
* dragging a card, or Shift + an arrow key on it, calls `onMove`, and the
|
|
53
|
+
* items only change when the caller changes them. Without `onMove` the board
|
|
54
|
+
* is read-only.
|
|
55
|
+
*
|
|
56
|
+
* The drag is the browser's own, which brings the drag image, Escape to
|
|
57
|
+
* cancel and the scrolling at the edges. An item naming a column that is not
|
|
58
|
+
* in `columns` is not drawn.
|
|
59
|
+
*/
|
|
60
|
+
import { tick, type Snippet } from 'svelte';
|
|
61
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
62
|
+
import { applyAxes, cx, type AxisProps } from '../variants.js';
|
|
63
|
+
|
|
64
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, HTMLAttributes<HTMLDivElement> {
|
|
65
|
+
/** The items: your data, as is, each column keeping their order. */
|
|
66
|
+
items?: Item[];
|
|
67
|
+
/** The columns, in order. */
|
|
68
|
+
columns: KanbanColumn[];
|
|
69
|
+
/** The id of the column an item belongs to. */
|
|
70
|
+
columnOf: (item: Item) => string;
|
|
71
|
+
/** How one item is drawn, given its place in its column. */
|
|
72
|
+
item: Snippet<[Item, number]>;
|
|
73
|
+
/** An item's identity, for `selected`, `dirty` and keyed rendering. Defaults to its index. */
|
|
74
|
+
key?: (item: Item, index: number) => string;
|
|
75
|
+
/** Replaces a column's title and count. */
|
|
76
|
+
head?: Snippet<[KanbanColumn, Item[]]>;
|
|
77
|
+
/** Drawn under a column's items: an add button, usually. */
|
|
78
|
+
foot?: Snippet<[KanbanColumn]>;
|
|
79
|
+
/** Drawn in a column with no items. */
|
|
80
|
+
empty?: Snippet<[KanbanColumn]>;
|
|
81
|
+
/** Keys of the items to draw as selected. */
|
|
82
|
+
selected?: Iterable<string>;
|
|
83
|
+
/** Keys of the items to draw as changed and not yet saved. */
|
|
84
|
+
dirty?: Iterable<string>;
|
|
85
|
+
/** An item was dropped somewhere new. Given, it makes the items movable. */
|
|
86
|
+
onMove?: (move: KanbanMove<Item>) => void;
|
|
87
|
+
class?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let {
|
|
91
|
+
items = [],
|
|
92
|
+
columns,
|
|
93
|
+
columnOf,
|
|
94
|
+
item,
|
|
95
|
+
key = (_item: Item, i: number) => String(i),
|
|
96
|
+
head,
|
|
97
|
+
foot,
|
|
98
|
+
empty,
|
|
99
|
+
selected,
|
|
100
|
+
dirty,
|
|
101
|
+
onMove,
|
|
102
|
+
class: klass = '',
|
|
103
|
+
...axes
|
|
104
|
+
}: Props = $props();
|
|
105
|
+
|
|
106
|
+
let applied = $derived(applyAxes(PREFIX, AXES, FLAGS, axes));
|
|
107
|
+
let classes = $derived(cx(PREFIX, applied.classes, klass));
|
|
108
|
+
|
|
109
|
+
let selectedSet = $derived(new Set(selected ?? []));
|
|
110
|
+
let dirtySet = $derived(new Set(dirty ?? []));
|
|
111
|
+
|
|
112
|
+
type Entry = { item: Item; key: string };
|
|
113
|
+
|
|
114
|
+
let lanes = $derived.by(() => {
|
|
115
|
+
const byColumn = new Map<string, Entry[]>(columns.map((c) => [c.id, []]));
|
|
116
|
+
items.forEach((it, i) => byColumn.get(columnOf(it))?.push({ item: it, key: key(it, i) }));
|
|
117
|
+
return columns.map((column) => ({ column, entries: byColumn.get(column.id)! }));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
let root: HTMLDivElement | undefined = $state();
|
|
121
|
+
|
|
122
|
+
/* What is in flight. Read by the drag handlers, so it is set at once; what
|
|
123
|
+
the page shows follows a frame later, because restyling the source during
|
|
124
|
+
`dragstart` would end up in the drag image. */
|
|
125
|
+
let flight: { entry: Entry; from: string } | null = null;
|
|
126
|
+
let draggedKey = $state<string | null>(null);
|
|
127
|
+
let over = $state<{ column: string; index: number } | null>(null);
|
|
128
|
+
|
|
129
|
+
/* Where the drop would land, or null when it would change nothing. */
|
|
130
|
+
let marker = $derived.by(() => {
|
|
131
|
+
if (!over || draggedKey === null) return null;
|
|
132
|
+
const entries = lanes.find((lane) => lane.column.id === over!.column)?.entries ?? [];
|
|
133
|
+
if (entries.findIndex((e) => e.key === draggedKey) === over.index) return null;
|
|
134
|
+
const before = entries.filter((e) => e.key !== draggedKey)[over.index];
|
|
135
|
+
return { column: over.column, before: before?.key ?? null };
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function dragstart(e: DragEvent, entry: Entry, from: string) {
|
|
139
|
+
flight = { entry, from };
|
|
140
|
+
if (e.dataTransfer) {
|
|
141
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
142
|
+
/* Firefox starts no drag without data. */
|
|
143
|
+
e.dataTransfer.setData('text/plain', entry.key);
|
|
144
|
+
}
|
|
145
|
+
requestAnimationFrame(() => {
|
|
146
|
+
if (flight) draggedKey = entry.key;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* The place under the pointer, counted without the item in flight. */
|
|
151
|
+
function placeAt(e: DragEvent & { currentTarget: HTMLElement }) {
|
|
152
|
+
const others = [...e.currentTarget.querySelectorAll<HTMLElement>('[data-kanban-key]')].filter(
|
|
153
|
+
(el) => el.dataset.kanbanKey !== flight!.entry.key
|
|
154
|
+
);
|
|
155
|
+
const next = others.findIndex((el) => {
|
|
156
|
+
const box = el.getBoundingClientRect();
|
|
157
|
+
return e.clientY < box.top + box.height / 2;
|
|
158
|
+
});
|
|
159
|
+
return next < 0 ? others.length : next;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function dragover(e: DragEvent & { currentTarget: HTMLElement }, column: string) {
|
|
163
|
+
/* Anything else dragged over the board is not ours to accept. */
|
|
164
|
+
if (!flight) return;
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
|
|
167
|
+
const index = placeAt(e);
|
|
168
|
+
if (over?.column !== column || over.index !== index) over = { column, index };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* By the pointer, not `relatedTarget`, which not every browser fills in. */
|
|
172
|
+
function dragleave(e: DragEvent & { currentTarget: HTMLElement }) {
|
|
173
|
+
const box = e.currentTarget.getBoundingClientRect();
|
|
174
|
+
const inside =
|
|
175
|
+
e.clientX > box.left && e.clientX < box.right && e.clientY > box.top && e.clientY < box.bottom;
|
|
176
|
+
if (!inside) over = null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* The place is read again rather than taken from `over`: a drop must not
|
|
180
|
+
depend on the last dragover having been drawn. */
|
|
181
|
+
function drop(e: DragEvent & { currentTarget: HTMLElement }, column: string) {
|
|
182
|
+
if (!flight) return;
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
const index = placeAt(e);
|
|
185
|
+
const { entry, from } = flight;
|
|
186
|
+
const stays =
|
|
187
|
+
from === column &&
|
|
188
|
+
lanes.find((lane) => lane.column.id === column)?.entries.findIndex((other) => other.key === entry.key) === index;
|
|
189
|
+
if (!stays) onMove?.({ item: entry.item, from, to: column, index });
|
|
190
|
+
dragend();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function dragend() {
|
|
194
|
+
flight = null;
|
|
195
|
+
draggedKey = null;
|
|
196
|
+
over = null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Shift + an arrow: the same move as a drop, then the focus follows the
|
|
200
|
+
item, whose element the move may have replaced. */
|
|
201
|
+
function keydown(
|
|
202
|
+
e: KeyboardEvent & { currentTarget: HTMLElement },
|
|
203
|
+
entry: Entry,
|
|
204
|
+
laneIndex: number,
|
|
205
|
+
index: number
|
|
206
|
+
) {
|
|
207
|
+
if (!onMove || !e.shiftKey || e.target !== e.currentTarget) return;
|
|
208
|
+
const lane = lanes[laneIndex]!;
|
|
209
|
+
const rtl = getComputedStyle(e.currentTarget).direction === 'rtl';
|
|
210
|
+
let to = laneIndex;
|
|
211
|
+
let place = index;
|
|
212
|
+
if (e.key === 'ArrowUp') place = index - 1;
|
|
213
|
+
else if (e.key === 'ArrowDown') place = index + 1;
|
|
214
|
+
else if (e.key === 'ArrowLeft') to = laneIndex + (rtl ? 1 : -1);
|
|
215
|
+
else if (e.key === 'ArrowRight') to = laneIndex + (rtl ? -1 : 1);
|
|
216
|
+
else return;
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
const target = lanes[to];
|
|
219
|
+
if (!target || place < 0) return;
|
|
220
|
+
if (to === laneIndex && place >= lane.entries.length) return;
|
|
221
|
+
onMove({
|
|
222
|
+
item: entry.item,
|
|
223
|
+
from: lane.column.id,
|
|
224
|
+
to: target.column.id,
|
|
225
|
+
index: Math.min(place, target.entries.length)
|
|
226
|
+
});
|
|
227
|
+
tick().then(() =>
|
|
228
|
+
root?.querySelector<HTMLElement>(`[data-kanban-key="${CSS.escape(entry.key)}"]`)?.focus()
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<div {...applied.rest} bind:this={root} class={classes}>
|
|
234
|
+
{#each lanes as lane, laneIndex (lane.column.id)}
|
|
235
|
+
{@const title = lane.column.title ?? lane.column.id}
|
|
236
|
+
<section
|
|
237
|
+
class={cx(`${PREFIX}-column`, over?.column === lane.column.id && `${PREFIX}-column-over`)}
|
|
238
|
+
aria-label={title}
|
|
239
|
+
ondragover={(e) => dragover(e, lane.column.id)}
|
|
240
|
+
ondragleave={dragleave}
|
|
241
|
+
ondrop={(e) => drop(e, lane.column.id)}
|
|
242
|
+
>
|
|
243
|
+
<header class={`${PREFIX}-column-head`}>
|
|
244
|
+
{#if head}
|
|
245
|
+
{@render head(lane.column, lane.entries.map((e) => e.item))}
|
|
246
|
+
{:else}
|
|
247
|
+
<span class={`${PREFIX}-column-title`}>{title}</span>
|
|
248
|
+
<span class={`${PREFIX}-column-count`}>{lane.entries.length}</span>
|
|
249
|
+
{/if}
|
|
250
|
+
</header>
|
|
251
|
+
<ul class={`${PREFIX}-column-body`}>
|
|
252
|
+
{#each lane.entries as entry, i (entry.key)}
|
|
253
|
+
{#if marker?.column === lane.column.id && marker.before === entry.key}
|
|
254
|
+
<li class={`${PREFIX}-marker`} aria-hidden="true"></li>
|
|
255
|
+
{/if}
|
|
256
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
257
|
+
<li
|
|
258
|
+
class={cx(
|
|
259
|
+
`${PREFIX}-item`,
|
|
260
|
+
selectedSet.has(entry.key) && `${PREFIX}-item-selected`,
|
|
261
|
+
dirtySet.has(entry.key) && `${PREFIX}-item-dirty`,
|
|
262
|
+
draggedKey === entry.key && `${PREFIX}-item-dragged`
|
|
263
|
+
)}
|
|
264
|
+
data-kanban-key={entry.key}
|
|
265
|
+
draggable={onMove ? 'true' : undefined}
|
|
266
|
+
tabindex={onMove ? 0 : undefined}
|
|
267
|
+
aria-keyshortcuts={onMove ? 'Shift+ArrowUp Shift+ArrowDown Shift+ArrowLeft Shift+ArrowRight' : undefined}
|
|
268
|
+
ondragstart={(e) => dragstart(e, entry, lane.column.id)}
|
|
269
|
+
ondragend={dragend}
|
|
270
|
+
onkeydown={(e) => keydown(e, entry, laneIndex, i)}
|
|
271
|
+
>
|
|
272
|
+
{@render item(entry.item, i)}
|
|
273
|
+
</li>
|
|
274
|
+
{:else}
|
|
275
|
+
{#if empty && !marker}<li class={`${PREFIX}-column-empty`}>{@render empty(lane.column)}</li>{/if}
|
|
276
|
+
{/each}
|
|
277
|
+
{#if marker?.column === lane.column.id && marker.before === null}
|
|
278
|
+
<li class={`${PREFIX}-marker`} aria-hidden="true"></li>
|
|
279
|
+
{/if}
|
|
280
|
+
</ul>
|
|
281
|
+
{#if foot}<footer class={`${PREFIX}-column-foot`}>{@render foot(lane.column)}</footer>{/if}
|
|
282
|
+
</section>
|
|
283
|
+
{/each}
|
|
284
|
+
</div>
|