@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,754 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** What .data-control is made of, classified. The props follow the
|
|
3
|
+
families the project registers (variants.ts, ADR 0015).
|
|
4
|
+
|
|
5
|
+
The control draws its own rows: every class below the root that is
|
|
6
|
+
not an axis is a PART. The buttons, fields, chips and pagination in
|
|
7
|
+
them are the kit's own components. */
|
|
8
|
+
export const PREFIX = 'data-control';
|
|
9
|
+
|
|
10
|
+
export const AXES = {
|
|
11
|
+
size: ['xs', 'sm', 'md', 'lg', 'xl'],
|
|
12
|
+
/* A card around the controls and the view. */
|
|
13
|
+
variant: ['boxed']
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const FLAGS = {} as const;
|
|
17
|
+
export const PARTS = [
|
|
18
|
+
'bar',
|
|
19
|
+
'bar-left',
|
|
20
|
+
'bar-right',
|
|
21
|
+
'search',
|
|
22
|
+
'count',
|
|
23
|
+
'filters',
|
|
24
|
+
'filter',
|
|
25
|
+
'filter-remove',
|
|
26
|
+
'editor',
|
|
27
|
+
'field',
|
|
28
|
+
'field-label',
|
|
29
|
+
'more',
|
|
30
|
+
'actions-start',
|
|
31
|
+
'actions-end',
|
|
32
|
+
'actions',
|
|
33
|
+
'page-options',
|
|
34
|
+
'pagination',
|
|
35
|
+
'range',
|
|
36
|
+
'body', 'total'] as const;
|
|
37
|
+
|
|
38
|
+
/** Every text the control writes. */
|
|
39
|
+
export interface DataControlLabels {
|
|
40
|
+
filters: string;
|
|
41
|
+
sort: string;
|
|
42
|
+
noSort: string;
|
|
43
|
+
search: string;
|
|
44
|
+
perPage: (n: number) => string;
|
|
45
|
+
selected: (n: number) => string;
|
|
46
|
+
range: (from: number, to: number, total: number) => string;
|
|
47
|
+
results: (total: number) => string;
|
|
48
|
+
save: string;
|
|
49
|
+
undo: string;
|
|
50
|
+
redo: string;
|
|
51
|
+
reset: string;
|
|
52
|
+
delete: string;
|
|
53
|
+
add: string;
|
|
54
|
+
export: string;
|
|
55
|
+
removeFilter: (title: string) => string;
|
|
56
|
+
yes: string;
|
|
57
|
+
no: string;
|
|
58
|
+
any: string;
|
|
59
|
+
confirmDeleteTitle: (n: number) => string;
|
|
60
|
+
confirmDeleteText: string;
|
|
61
|
+
cancel: string;
|
|
62
|
+
confirmDiscardTitle: (n: number) => string;
|
|
63
|
+
confirmDiscardText: string;
|
|
64
|
+
discard: string;
|
|
65
|
+
stay: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const LABELS: DataControlLabels = {
|
|
69
|
+
filters: 'Filters',
|
|
70
|
+
sort: 'Sort',
|
|
71
|
+
noSort: 'No sort',
|
|
72
|
+
search: 'Search',
|
|
73
|
+
perPage: (n) => `${n} per page`,
|
|
74
|
+
selected: (n) => `${n} selected`,
|
|
75
|
+
range: (from, to, total) => `${from}–${to} / ${total}`,
|
|
76
|
+
results: (total) => `${total} result${total > 1 ? 's' : ''}`,
|
|
77
|
+
save: 'Save',
|
|
78
|
+
undo: 'Undo',
|
|
79
|
+
redo: 'Redo',
|
|
80
|
+
reset: 'Discard changes',
|
|
81
|
+
delete: 'Delete',
|
|
82
|
+
add: 'Add',
|
|
83
|
+
export: 'Export',
|
|
84
|
+
removeFilter: (title) => `Remove the ${title} filter`,
|
|
85
|
+
yes: 'Yes',
|
|
86
|
+
no: 'No',
|
|
87
|
+
any: 'any',
|
|
88
|
+
confirmDeleteTitle: (n) => `Delete ${n} row${n > 1 ? 's' : ''}?`,
|
|
89
|
+
confirmDeleteText: 'This cannot be undone.',
|
|
90
|
+
cancel: 'Cancel',
|
|
91
|
+
confirmDiscardTitle: (n) => `${n} unsaved change${n > 1 ? 's' : ''}`,
|
|
92
|
+
confirmDiscardText: 'Loading other rows drops them. Save them first?',
|
|
93
|
+
discard: 'Discard',
|
|
94
|
+
stay: 'Stay'
|
|
95
|
+
};
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<script lang="ts" generics="F extends readonly Filter[], Model extends Record<string, any>, Id">
|
|
99
|
+
/**
|
|
100
|
+
* The controls of a data handler, around the view that shows its rows:
|
|
101
|
+
* filters, sort, search, page size, pagination, and actions on the
|
|
102
|
+
* selection or on the whole resource. Every one of them is optional, and
|
|
103
|
+
* an action only appears when the handler can do it (`update`, `delete`).
|
|
104
|
+
*
|
|
105
|
+
* Deleting and dropping unsaved changes are confirmed in a Dialog, unless
|
|
106
|
+
* the handler was given its own `confirmDelete` / `confirmDiscard`.
|
|
107
|
+
*/
|
|
108
|
+
import type { Snippet } from 'svelte';
|
|
109
|
+
import { slide } from 'svelte/transition';
|
|
110
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
111
|
+
import { applyAxes, cx, type AxisProps } from '../variants.js';
|
|
112
|
+
import type { DataHandlerState, DiscardChoice, EntitySuggestion, EntityValue, Filter, HandledFilter, Sort } from './data-handler.svelte.js';
|
|
113
|
+
import Button from './Button.svelte';
|
|
114
|
+
import Checkbox from './Checkbox.svelte';
|
|
115
|
+
import Chip from './Chip.svelte';
|
|
116
|
+
import Dialog from './Dialog.svelte';
|
|
117
|
+
import DatePicker from './DatePicker.svelte';
|
|
118
|
+
import Dropdown from './Dropdown.svelte';
|
|
119
|
+
import Input from './Input.svelte';
|
|
120
|
+
import Combobox from './Combobox.svelte';
|
|
121
|
+
import Menu from './Menu.svelte';
|
|
122
|
+
import Pagination from './Pagination.svelte';
|
|
123
|
+
import Select from './Select.svelte';
|
|
124
|
+
import Switch from './Switch.svelte';
|
|
125
|
+
|
|
126
|
+
type Handler = DataHandlerState<F, Model, Id>;
|
|
127
|
+
type ExportOptions = Parameters<Handler['export']>[0];
|
|
128
|
+
|
|
129
|
+
interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
130
|
+
/** The handler whose rows and requests these controls drive. */
|
|
131
|
+
handler: Handler;
|
|
132
|
+
/** The view: a Table, a Grid, a List — given the handler. */
|
|
133
|
+
children?: Snippet<[Handler]>;
|
|
134
|
+
/** Offer the handler's filters. Default true. */
|
|
135
|
+
filters?: boolean;
|
|
136
|
+
/** Draw every filter as a field in the bar, and the page size and the sort
|
|
137
|
+
with the pagination, instead of a menu, chips and a select in the bar. */
|
|
138
|
+
fields?: boolean;
|
|
139
|
+
/** Offer the handler's sorts. Default true. */
|
|
140
|
+
sorts?: boolean;
|
|
141
|
+
/** Draw the filter, sort and save buttons as icons alone, their text as a title, and the menus and filters small. The sort in use and the number of results go with the filters. */
|
|
142
|
+
compact?: boolean;
|
|
143
|
+
/** Draw a search field. Default true. */
|
|
144
|
+
search?: boolean;
|
|
145
|
+
/** Milliseconds without typing before the search is fetched. Default 300. */
|
|
146
|
+
searchDelay?: number;
|
|
147
|
+
/** The page sizes to choose from, or `false` for none. */
|
|
148
|
+
limits?: number[] | false;
|
|
149
|
+
/** Where the pagination goes. Default `bottom`. */
|
|
150
|
+
pagination?: 'top' | 'bottom' | 'both' | 'none';
|
|
151
|
+
/** Draw save, undo, redo and discard, when the handler can update. Default true. */
|
|
152
|
+
editing?: boolean;
|
|
153
|
+
/** Draw delete, when the handler can delete. Default true. */
|
|
154
|
+
removable?: boolean;
|
|
155
|
+
/** Draw export: `true`, or the options the export runs with. */
|
|
156
|
+
exportable?: boolean | ExportOptions;
|
|
157
|
+
/** Draw an add button calling this. */
|
|
158
|
+
onAdd?: () => void;
|
|
159
|
+
/** Confirm deletions and discarded changes in a dialog. Default true. */
|
|
160
|
+
confirm?: boolean;
|
|
161
|
+
/** How an entity is drawn, in its filter's chip and suggestions. Default: its text. */
|
|
162
|
+
entity?: Snippet<[HandledFilter, EntitySuggestion]>;
|
|
163
|
+
/** Drawn first in the bar, before the filters. */
|
|
164
|
+
start?: Snippet<[Handler]>;
|
|
165
|
+
/** Drawn at the start of the bar, after the filters and the sort. */
|
|
166
|
+
leftActions?: Snippet<[Handler]>;
|
|
167
|
+
/** Drawn at the end of the bar, after the built-in actions. */
|
|
168
|
+
rightActions?: Snippet<[Handler]>;
|
|
169
|
+
/** Drawn above the view (and above a top pagination). */
|
|
170
|
+
header?: Snippet<[Handler]>;
|
|
171
|
+
/** Drawn below the view (and below a bottom pagination). */
|
|
172
|
+
footer?: Snippet<[Handler]>;
|
|
173
|
+
/** The texts, for another language. */
|
|
174
|
+
labels?: Partial<DataControlLabels>;
|
|
175
|
+
class?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let {
|
|
179
|
+
handler,
|
|
180
|
+
children,
|
|
181
|
+
filters = true,
|
|
182
|
+
fields = false,
|
|
183
|
+
compact = false,
|
|
184
|
+
sorts = true,
|
|
185
|
+
search = true,
|
|
186
|
+
searchDelay = 300,
|
|
187
|
+
limits = [5, 10, 25, 50, 100],
|
|
188
|
+
pagination = 'bottom',
|
|
189
|
+
editing = true,
|
|
190
|
+
removable = true,
|
|
191
|
+
exportable = false,
|
|
192
|
+
onAdd,
|
|
193
|
+
confirm = true,
|
|
194
|
+
entity,
|
|
195
|
+
start,
|
|
196
|
+
leftActions,
|
|
197
|
+
rightActions,
|
|
198
|
+
header,
|
|
199
|
+
footer,
|
|
200
|
+
labels,
|
|
201
|
+
class: klass = '',
|
|
202
|
+
...axes
|
|
203
|
+
}: Props = $props();
|
|
204
|
+
|
|
205
|
+
let applied = $derived(applyAxes(PREFIX, AXES, FLAGS, axes));
|
|
206
|
+
let classes = $derived(cx(PREFIX, applied.classes, klass));
|
|
207
|
+
|
|
208
|
+
let t = $derived({ ...LABELS, ...labels });
|
|
209
|
+
const uid = $props.id();
|
|
210
|
+
|
|
211
|
+
/* ── Confirmations ── lent to the handler while it has none of its own. */
|
|
212
|
+
|
|
213
|
+
type Asking =
|
|
214
|
+
| { kind: 'delete'; count: number; resolve: (ok: boolean) => void }
|
|
215
|
+
| { kind: 'discard'; count: number; resolve: (choice: DiscardChoice) => void };
|
|
216
|
+
let asking: Asking | undefined = $state();
|
|
217
|
+
let dialogOpen = $state(false);
|
|
218
|
+
|
|
219
|
+
$effect(() => {
|
|
220
|
+
if (!confirm) return;
|
|
221
|
+
const o = handler.options;
|
|
222
|
+
const ownDelete = o.confirmDelete;
|
|
223
|
+
const ownDiscard = o.confirmDiscard;
|
|
224
|
+
o.confirmDelete ??= (ids) =>
|
|
225
|
+
new Promise((resolve) => {
|
|
226
|
+
asking = { kind: 'delete', count: ids.length, resolve };
|
|
227
|
+
dialogOpen = true;
|
|
228
|
+
});
|
|
229
|
+
o.confirmDiscard ??= (count) =>
|
|
230
|
+
new Promise((resolve) => {
|
|
231
|
+
asking = { kind: 'discard', count, resolve };
|
|
232
|
+
dialogOpen = true;
|
|
233
|
+
});
|
|
234
|
+
return () => {
|
|
235
|
+
o.confirmDelete = ownDelete;
|
|
236
|
+
o.confirmDiscard = ownDiscard;
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
function answered(value: string) {
|
|
241
|
+
const a = asking;
|
|
242
|
+
asking = undefined;
|
|
243
|
+
if (!a) return;
|
|
244
|
+
if (a.kind === 'delete') a.resolve(value === 'delete');
|
|
245
|
+
else a.resolve(value === 'save' || value === 'discard' ? value : 'stay');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* ── Filters ── */
|
|
249
|
+
|
|
250
|
+
/** The filter being edited: its chip holds its field. */
|
|
251
|
+
let openFilter: string | undefined = $state();
|
|
252
|
+
|
|
253
|
+
/* A press anywhere but on the chip being edited ends the editing. */
|
|
254
|
+
function pressed(e: PointerEvent) {
|
|
255
|
+
if (openFilter && !(e.target as Element).closest?.(`[data-filter="${openFilter}"]`)) openFilter = undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* A chip turning into its field hands it the focus, and opens what can be opened. */
|
|
259
|
+
function takeFocus(node: HTMLElement, on: boolean) {
|
|
260
|
+
if (!on) return;
|
|
261
|
+
const field = node.querySelector<HTMLElement>('input, select, [tabindex]');
|
|
262
|
+
field?.focus();
|
|
263
|
+
if (field instanceof HTMLSelectElement) {
|
|
264
|
+
try {
|
|
265
|
+
field.showPicker();
|
|
266
|
+
} catch {
|
|
267
|
+
/* Not every browser opens it from code: the focus is enough. */
|
|
268
|
+
}
|
|
269
|
+
} else if (field && !(field instanceof HTMLInputElement)) field.click();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const valueOf = (f: HandledFilter) => (handler.filterValues as Record<string, unknown>)[f.id];
|
|
273
|
+
|
|
274
|
+
function summary(f: HandledFilter): string {
|
|
275
|
+
const v = valueOf(f);
|
|
276
|
+
if (v === undefined || v === null || v === '') return t.any;
|
|
277
|
+
switch (f.type) {
|
|
278
|
+
case 'date':
|
|
279
|
+
return (v as Date).toLocaleDateString();
|
|
280
|
+
case 'boolean':
|
|
281
|
+
return v ? t.yes : t.no;
|
|
282
|
+
case 'entity': {
|
|
283
|
+
const e = v as EntityValue;
|
|
284
|
+
return f.show ? f.show(e) : String(e.name ?? e.id);
|
|
285
|
+
}
|
|
286
|
+
case 'select': {
|
|
287
|
+
const chosen = v as string[];
|
|
288
|
+
if (!chosen.length) return t.any;
|
|
289
|
+
const names = chosen.map((c) => f.options.find((o) => o.value === c)?.name ?? c);
|
|
290
|
+
return names.length > 2 ? `${names.slice(0, 2).join(', ')} +${names.length - 2}` : names.join(', ');
|
|
291
|
+
}
|
|
292
|
+
default:
|
|
293
|
+
return String(v);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function add(f: HandledFilter) {
|
|
298
|
+
handler.showFilter(f.id);
|
|
299
|
+
/* A boolean chip is its own checkbox: on the page it holds yes or no, off it, either. */
|
|
300
|
+
if (f.type === 'boolean') return set(f, true);
|
|
301
|
+
openFilter = f.id;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* A value chosen once and for all ends the editing; one being typed or ticked does not. */
|
|
305
|
+
function set(f: HandledFilter, value: unknown, delay = 0) {
|
|
306
|
+
if (!delay && value !== undefined && !(f.type === 'select' && f.multiple)) openFilter = undefined;
|
|
307
|
+
return handler.changeFilter(f.id, value as never, delay);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const dateValue = (v: unknown) => (v instanceof Date ? v.toISOString().slice(0, 10) : '');
|
|
311
|
+
|
|
312
|
+
/* ── The page ── */
|
|
313
|
+
|
|
314
|
+
let from = $derived(handler.total ? (handler.page - 1) * handler.limit + 1 : 0);
|
|
315
|
+
let to = $derived(Math.min(handler.page * handler.limit, handler.total ?? 0));
|
|
316
|
+
|
|
317
|
+
/* With `fields`: the search and this many filters show; the others open from a button. */
|
|
318
|
+
const FIELDS_SHOWN = 3;
|
|
319
|
+
const fieldFilters = $derived(handler.filters.filter((f) => !f.immutable));
|
|
320
|
+
const moreFilters = $derived(fieldFilters.slice(FIELDS_SHOWN));
|
|
321
|
+
const moreActive = $derived(moreFilters.filter((f) => valueOf(f) !== undefined && valueOf(f) !== null && valueOf(f) !== '').length);
|
|
322
|
+
// svelte-ignore state_referenced_locally
|
|
323
|
+
let showMore = $state(moreFilters.some((f) => valueOf(f) !== undefined));
|
|
324
|
+
|
|
325
|
+
const hasActions = $derived(
|
|
326
|
+
!!leftActions ||
|
|
327
|
+
!!rightActions ||
|
|
328
|
+
!!onAdd ||
|
|
329
|
+
!!exportable ||
|
|
330
|
+
(editing && handler.canUpdate) ||
|
|
331
|
+
(removable && handler.canDelete)
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const sortKey = (s: Sort) => `${s.id}-${s.value}`;
|
|
335
|
+
|
|
336
|
+
/* The rows of the two menus, each found back by its place: two may read the same. */
|
|
337
|
+
const filterNodes = $derived(handler.availableFilters.map((f) => ({ label: f.title })));
|
|
338
|
+
const sortNodes = $derived([
|
|
339
|
+
{ label: t.noSort, active: !handler.sort },
|
|
340
|
+
...handler.sorts.map((s) => ({
|
|
341
|
+
label: s.name,
|
|
342
|
+
active: !!handler.sort && sortKey(handler.sort) === sortKey(s)
|
|
343
|
+
}))
|
|
344
|
+
]);
|
|
345
|
+
</script>
|
|
346
|
+
|
|
347
|
+
{#snippet icon(path: string)}
|
|
348
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d={path} /></svg>
|
|
349
|
+
{/snippet}
|
|
350
|
+
|
|
351
|
+
{#snippet sortMenu(close: () => void)}
|
|
352
|
+
<Menu
|
|
353
|
+
size={compact ? 'sm' : undefined}
|
|
354
|
+
items={sortNodes}
|
|
355
|
+
onSelect={(node) => {
|
|
356
|
+
close();
|
|
357
|
+
handler.changeSort(handler.sorts[sortNodes.indexOf(node as (typeof sortNodes)[number]) - 1]);
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
{/snippet}
|
|
361
|
+
|
|
362
|
+
{#snippet pager()}
|
|
363
|
+
{#if handler.total}
|
|
364
|
+
<div class={`${PREFIX}-pagination`}>
|
|
365
|
+
<span class={`${PREFIX}-range`}>{t.range(from, to, handler.total)}</span>
|
|
366
|
+
{#if (handler.pages ?? 1) > 1}
|
|
367
|
+
<Pagination count={handler.pages ?? 1} page={handler.page} onPage={(p) => handler.changePage(p)} />
|
|
368
|
+
{/if}
|
|
369
|
+
{#if fields}
|
|
370
|
+
<div class={`${PREFIX}-page-options`}>
|
|
371
|
+
{#if sorts && handler.sorts.length}
|
|
372
|
+
<Select
|
|
373
|
+
aria-label={t.sort}
|
|
374
|
+
nullable
|
|
375
|
+
placeholder={t.sort}
|
|
376
|
+
options={handler.sorts.map((x) => ({ value: sortKey(x), name: x.name }))}
|
|
377
|
+
value={handler.sort ? sortKey(handler.sort) : undefined}
|
|
378
|
+
onValue={(v) => handler.changeSort(handler.sorts.find((x) => sortKey(x) === v))}
|
|
379
|
+
/>
|
|
380
|
+
{/if}
|
|
381
|
+
{#if limits && limits.length}
|
|
382
|
+
<Select
|
|
383
|
+
aria-label={t.perPage(handler.limit)}
|
|
384
|
+
options={limits.map((n) => ({ value: String(n), name: t.perPage(n) }))}
|
|
385
|
+
value={String(handler.limit)}
|
|
386
|
+
onValue={(v) => handler.changeLimit(Number(v))}
|
|
387
|
+
/>
|
|
388
|
+
{/if}
|
|
389
|
+
</div>
|
|
390
|
+
{/if}
|
|
391
|
+
</div>
|
|
392
|
+
{/if}
|
|
393
|
+
{/snippet}
|
|
394
|
+
|
|
395
|
+
{#snippet editor(f: HandledFilter, inline: boolean)}
|
|
396
|
+
{@const fieldSize = compact && !inline ? 'xs' : undefined}
|
|
397
|
+
<div class={inline ? `${PREFIX}-field` : `${PREFIX}-editor`} use:takeFocus={!inline}>
|
|
398
|
+
{#if inline}<span class={`${PREFIX}-field-label`}>{f.title}</span>{/if}
|
|
399
|
+
{#if f.type === 'text'}
|
|
400
|
+
<Input
|
|
401
|
+
size={fieldSize}
|
|
402
|
+
aria-label={f.title}
|
|
403
|
+
value={(valueOf(f) as string) ?? ''}
|
|
404
|
+
oninput={(e) => set(f, e.currentTarget.value || undefined, 400)}
|
|
405
|
+
/>
|
|
406
|
+
{:else if f.type === 'number'}
|
|
407
|
+
<Input
|
|
408
|
+
size={fieldSize}
|
|
409
|
+
type="number"
|
|
410
|
+
placeholder={undefined}
|
|
411
|
+
aria-label={f.title}
|
|
412
|
+
min={f.min}
|
|
413
|
+
max={f.max}
|
|
414
|
+
step={f.step}
|
|
415
|
+
value={valueOf(f) ?? ''}
|
|
416
|
+
oninput={(e) => {
|
|
417
|
+
const n = e.currentTarget.valueAsNumber;
|
|
418
|
+
set(f, Number.isNaN(n) ? undefined : n, 400);
|
|
419
|
+
}}
|
|
420
|
+
/>
|
|
421
|
+
{:else if f.type === 'date'}
|
|
422
|
+
<DatePicker
|
|
423
|
+
size={fieldSize}
|
|
424
|
+
value={(valueOf(f) as Date | undefined) ?? null}
|
|
425
|
+
onChange={(d) => set(f, d instanceof Date ? d : undefined)}
|
|
426
|
+
/>
|
|
427
|
+
{:else if f.type === 'boolean'}
|
|
428
|
+
<Select
|
|
429
|
+
size={fieldSize}
|
|
430
|
+
options={[{ value: 'yes', name: t.yes }, { value: 'no', name: t.no }]}
|
|
431
|
+
nullable
|
|
432
|
+
placeholder={t.any}
|
|
433
|
+
aria-label={f.title}
|
|
434
|
+
value={valueOf(f) === undefined || valueOf(f) === null ? undefined : valueOf(f) ? 'yes' : 'no'}
|
|
435
|
+
onValue={(v) => set(f, v ? v === 'yes' : undefined)}
|
|
436
|
+
/>
|
|
437
|
+
{:else if f.type === 'select' && inline && f.switch && !f.multiple}
|
|
438
|
+
<Switch
|
|
439
|
+
size="sm"
|
|
440
|
+
label={f.title}
|
|
441
|
+
options={[{ value: '', label: t.any }, ...f.options.map((o) => ({ value: String(o.value), label: o.name }))]}
|
|
442
|
+
value={(valueOf(f) as string[] | undefined)?.[0] ?? ''}
|
|
443
|
+
onChange={(v) => set(f, v ? [v] : undefined)}
|
|
444
|
+
/>
|
|
445
|
+
{:else if f.type === 'select'}
|
|
446
|
+
<Select
|
|
447
|
+
size={fieldSize}
|
|
448
|
+
options={f.options}
|
|
449
|
+
multiple={f.multiple}
|
|
450
|
+
nullable={f.nullable ?? true}
|
|
451
|
+
placeholder={t.any}
|
|
452
|
+
aria-label={f.title}
|
|
453
|
+
value={f.multiple ? ((valueOf(f) as string[]) ?? []) : ((valueOf(f) as string[] | undefined)?.[0])}
|
|
454
|
+
onValue={(v) => {
|
|
455
|
+
const list = Array.isArray(v) ? v : v ? [v] : [];
|
|
456
|
+
set(f, list.length ? list : undefined);
|
|
457
|
+
}}
|
|
458
|
+
/>
|
|
459
|
+
{:else if f.type === 'entity'}
|
|
460
|
+
<Combobox
|
|
461
|
+
size={fieldSize}
|
|
462
|
+
placeholder={t.any}
|
|
463
|
+
aria-label={f.title}
|
|
464
|
+
value={valueOf(f)}
|
|
465
|
+
options={(q: string) => Promise.resolve(f.items(q))}
|
|
466
|
+
show={(s: EntitySuggestion) => (f.show ? f.show(s) : String(s.name ?? s.id))}
|
|
467
|
+
wait={f.wait}
|
|
468
|
+
eager
|
|
469
|
+
onSelect={(s: EntitySuggestion | undefined) => set(f, s ? ({ id: String(s.id), name: s.name } satisfies EntityValue) : undefined)}
|
|
470
|
+
>
|
|
471
|
+
{#snippet option(s: EntitySuggestion)}
|
|
472
|
+
{#if entity}{@render entity(f, s)}{:else}{f.show ? f.show(s) : String(s.name ?? s.id)}{/if}
|
|
473
|
+
{/snippet}
|
|
474
|
+
</Combobox>
|
|
475
|
+
{/if}
|
|
476
|
+
</div>
|
|
477
|
+
{/snippet}
|
|
478
|
+
|
|
479
|
+
{#snippet actions()}
|
|
480
|
+
{#if exportable && !fields}
|
|
481
|
+
<Button
|
|
482
|
+
variant="soft"
|
|
483
|
+
shape="square"
|
|
484
|
+
title={t.export}
|
|
485
|
+
aria-label={t.export}
|
|
486
|
+
disabled={!handler.total}
|
|
487
|
+
onclick={() => handler.export(typeof exportable === 'object' ? exportable : {})}
|
|
488
|
+
>
|
|
489
|
+
{@render icon('M12 3v12M7 10l5 5 5-5M5 21h14')}
|
|
490
|
+
</Button>
|
|
491
|
+
{/if}
|
|
492
|
+
|
|
493
|
+
{#if limits && limits.length && !fields}
|
|
494
|
+
<Select
|
|
495
|
+
aria-label={t.perPage(handler.limit)}
|
|
496
|
+
options={limits.map((n) => ({ value: String(n), name: t.perPage(n) }))}
|
|
497
|
+
value={String(handler.limit)}
|
|
498
|
+
onValue={(v) => handler.changeLimit(Number(v))}
|
|
499
|
+
/>
|
|
500
|
+
{/if}
|
|
501
|
+
|
|
502
|
+
{#if removable && handler.canDelete && handler.selection.length}
|
|
503
|
+
<span class={`${PREFIX}-count`}>{t.selected(handler.selection.length)}</span>
|
|
504
|
+
{/if}
|
|
505
|
+
|
|
506
|
+
{#if editing && handler.canUpdate}
|
|
507
|
+
<Button variant="ghost" shape="square" title={t.undo} aria-label={t.undo} disabled={!handler.canUndo} onclick={() => handler.undo()}>
|
|
508
|
+
{@render icon('M9 14 4 9l5-5M4 9h11a5 5 0 0 1 0 10h-3')}
|
|
509
|
+
</Button>
|
|
510
|
+
<Button variant="ghost" shape="square" title={t.redo} aria-label={t.redo} disabled={!handler.canRedo} onclick={() => handler.redo()}>
|
|
511
|
+
{@render icon('m15 14 5-5-5-5M20 9H9a5 5 0 0 0 0 10h3')}
|
|
512
|
+
</Button>
|
|
513
|
+
<Button variant="ghost" shape="square" title={t.reset} aria-label={t.reset} disabled={!handler.dirty} onclick={() => handler.reset()}>
|
|
514
|
+
{@render icon('M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5')}
|
|
515
|
+
</Button>
|
|
516
|
+
<Button color="primary" shape={compact ? 'square' : undefined} title={t.save} aria-label={t.save} disabled={!handler.dirty} onclick={() => handler.save()}>
|
|
517
|
+
{#if compact}
|
|
518
|
+
{@render icon('M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zM17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7M7 3v4a1 1 0 0 0 1 1h7')}
|
|
519
|
+
{:else}
|
|
520
|
+
{t.save}
|
|
521
|
+
{/if}
|
|
522
|
+
</Button>
|
|
523
|
+
{/if}
|
|
524
|
+
|
|
525
|
+
{#if removable && handler.canDelete}
|
|
526
|
+
<Button color="error" variant="soft" shape="square" title={t.delete} aria-label={t.delete} disabled={!handler.selection.length} onclick={() => handler.delete()}>
|
|
527
|
+
{@render icon('M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6')}
|
|
528
|
+
</Button>
|
|
529
|
+
{/if}
|
|
530
|
+
|
|
531
|
+
{#if onAdd}
|
|
532
|
+
<Button variant="soft" shape="square" title={t.add} aria-label={t.add} onclick={() => onAdd()}>
|
|
533
|
+
{@render icon('M12 5v14M5 12h14')}
|
|
534
|
+
</Button>
|
|
535
|
+
{/if}
|
|
536
|
+
|
|
537
|
+
{@render rightActions?.(handler)}
|
|
538
|
+
{/snippet}
|
|
539
|
+
|
|
540
|
+
<svelte:window onpointerdown={pressed} />
|
|
541
|
+
|
|
542
|
+
<div {...applied.rest} class={classes}>
|
|
543
|
+
<div class={`${PREFIX}-bar`}>
|
|
544
|
+
<div class={`${PREFIX}-bar-left`}>
|
|
545
|
+
{@render start?.(handler)}
|
|
546
|
+
|
|
547
|
+
{#if filters && !fields && handler.availableFilters.length}
|
|
548
|
+
<Dropdown>
|
|
549
|
+
{#snippet trigger({ props })}
|
|
550
|
+
<Button {...props} variant="ghost" shape={compact ? 'square' : undefined} title={t.filters} aria-label={t.filters}>
|
|
551
|
+
{@render icon('M3 5h18M6 12h12M10 19h4')}
|
|
552
|
+
{#if !compact}{t.filters}{/if}
|
|
553
|
+
</Button>
|
|
554
|
+
{/snippet}
|
|
555
|
+
{#snippet children({ close })}
|
|
556
|
+
<Menu
|
|
557
|
+
size={compact ? 'sm' : undefined}
|
|
558
|
+
items={filterNodes}
|
|
559
|
+
onSelect={(node) => {
|
|
560
|
+
const f = handler.availableFilters[filterNodes.indexOf(node as (typeof filterNodes)[number])];
|
|
561
|
+
close();
|
|
562
|
+
if (f) add(f);
|
|
563
|
+
}}
|
|
564
|
+
/>
|
|
565
|
+
{/snippet}
|
|
566
|
+
</Dropdown>
|
|
567
|
+
{/if}
|
|
568
|
+
|
|
569
|
+
{#if sorts && !fields && handler.sorts.length}
|
|
570
|
+
<Dropdown>
|
|
571
|
+
{#snippet trigger({ props })}
|
|
572
|
+
<Button {...props} variant="ghost" shape={compact ? 'square' : undefined} title={handler.sort?.name ?? t.sort} aria-label={t.sort}>
|
|
573
|
+
{@render icon('M7 4v16M3 16l4 4 4-4M17 20V4M13 8l4-4 4 4')}
|
|
574
|
+
{#if !compact}{handler.sort?.name ?? t.sort}{/if}
|
|
575
|
+
</Button>
|
|
576
|
+
{/snippet}
|
|
577
|
+
{#snippet children({ close })}{@render sortMenu(close)}{/snippet}
|
|
578
|
+
</Dropdown>
|
|
579
|
+
{/if}
|
|
580
|
+
|
|
581
|
+
{#if !fields}{@render leftActions?.(handler)}{/if}
|
|
582
|
+
|
|
583
|
+
{#if search && fields}
|
|
584
|
+
<div class={`${PREFIX}-field ${PREFIX}-field-search`}>
|
|
585
|
+
<span class={`${PREFIX}-field-label`}>{t.search}</span>
|
|
586
|
+
<Input
|
|
587
|
+
class={`${PREFIX}-search`}
|
|
588
|
+
type="search"
|
|
589
|
+
aria-label={t.search}
|
|
590
|
+
value={handler.search}
|
|
591
|
+
oninput={(e) => handler.changeSearch(e.currentTarget.value, searchDelay)}
|
|
592
|
+
>
|
|
593
|
+
{#snippet start()}{@render icon('M21 21l-4.3-4.3M11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14z')}{/snippet}
|
|
594
|
+
</Input>
|
|
595
|
+
</div>
|
|
596
|
+
{:else if search}
|
|
597
|
+
<Input
|
|
598
|
+
class={`${PREFIX}-search`}
|
|
599
|
+
type="search"
|
|
600
|
+
placeholder={t.search}
|
|
601
|
+
aria-label={t.search}
|
|
602
|
+
value={handler.search}
|
|
603
|
+
oninput={(e) => handler.changeSearch(e.currentTarget.value, searchDelay)}
|
|
604
|
+
>
|
|
605
|
+
{#snippet start()}{@render icon('M21 21l-4.3-4.3M11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14z')}{/snippet}
|
|
606
|
+
</Input>
|
|
607
|
+
{/if}
|
|
608
|
+
|
|
609
|
+
{#if filters && fields}
|
|
610
|
+
{#each fieldFilters.slice(0, FIELDS_SHOWN) as f (f.id)}
|
|
611
|
+
{@render editor(f, true)}
|
|
612
|
+
{/each}
|
|
613
|
+
{#if moreFilters.length}
|
|
614
|
+
<Button variant={showMore ? 'soft' : 'ghost'} aria-expanded={showMore} onclick={() => (showMore = !showMore)}>
|
|
615
|
+
{@render icon('M3 5h18M6 12h12M10 19h4')}
|
|
616
|
+
{t.filters}{#if moreActive} ({moreActive}){/if}
|
|
617
|
+
</Button>
|
|
618
|
+
{/if}
|
|
619
|
+
{#if showMore}
|
|
620
|
+
<div class={`${PREFIX}-more`} transition:slide={{ duration: 220 }}>
|
|
621
|
+
{#each moreFilters as f (f.id)}
|
|
622
|
+
{@render editor(f, true)}
|
|
623
|
+
{/each}
|
|
624
|
+
</div>
|
|
625
|
+
{/if}
|
|
626
|
+
{/if}
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
{#if !fields}
|
|
630
|
+
<div class={`${PREFIX}-bar-right`}>
|
|
631
|
+
{@render actions()}
|
|
632
|
+
</div>
|
|
633
|
+
{/if}
|
|
634
|
+
</div>
|
|
635
|
+
|
|
636
|
+
{#if fields && hasActions}
|
|
637
|
+
<div class={`${PREFIX}-actions`}>
|
|
638
|
+
<div class={`${PREFIX}-actions-start`}>
|
|
639
|
+
{#if exportable}
|
|
640
|
+
<Button
|
|
641
|
+
variant="ghost"
|
|
642
|
+
shape="square"
|
|
643
|
+
title={t.export}
|
|
644
|
+
aria-label={t.export}
|
|
645
|
+
disabled={!handler.total}
|
|
646
|
+
onclick={() => handler.export(typeof exportable === 'object' ? exportable : {})}
|
|
647
|
+
>
|
|
648
|
+
{@render icon('M12 3v12M7 10l5 5 5-5M5 21h14')}
|
|
649
|
+
</Button>
|
|
650
|
+
|
|
651
|
+
{/if}
|
|
652
|
+
{@render leftActions?.(handler)}
|
|
653
|
+
</div>
|
|
654
|
+
<div class={`${PREFIX}-actions-end`}>
|
|
655
|
+
{@render actions()}
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
{/if}
|
|
659
|
+
|
|
660
|
+
{#if !fields && ((filters && handler.shownFilters.length) || (compact && (handler.sort || handler.total !== undefined)))}
|
|
661
|
+
<div class={`${PREFIX}-filters`}>
|
|
662
|
+
{#if compact && sorts && handler.sort}
|
|
663
|
+
<Dropdown>
|
|
664
|
+
{#snippet trigger({ props })}
|
|
665
|
+
<Chip variant="soft" color="primary" size="sm" class={`${PREFIX}-filter`}>
|
|
666
|
+
<button type="button" {...props} title={t.sort}>
|
|
667
|
+
{@render icon('M7 4v16M3 16l4 4 4-4M17 20V4M13 8l4-4 4 4')}
|
|
668
|
+
<strong>{handler.sort?.name}</strong>
|
|
669
|
+
</button>
|
|
670
|
+
<button
|
|
671
|
+
type="button"
|
|
672
|
+
class={`${PREFIX}-filter-remove`}
|
|
673
|
+
aria-label={t.noSort}
|
|
674
|
+
title={t.noSort}
|
|
675
|
+
onclick={() => handler.changeSort(undefined)}
|
|
676
|
+
>{@render icon('M18 6 6 18M6 6l12 12')}</button>
|
|
677
|
+
</Chip>
|
|
678
|
+
{/snippet}
|
|
679
|
+
{#snippet children({ close })}{@render sortMenu(close)}{/snippet}
|
|
680
|
+
</Dropdown>
|
|
681
|
+
{/if}
|
|
682
|
+
|
|
683
|
+
{#each filters ? handler.shownFilters : [] as f (f.id)}
|
|
684
|
+
<Chip
|
|
685
|
+
variant="soft"
|
|
686
|
+
color="primary"
|
|
687
|
+
size={compact ? 'sm' : undefined}
|
|
688
|
+
class={`${PREFIX}-filter`}
|
|
689
|
+
data-filter={f.id}
|
|
690
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
691
|
+
if (e.key === 'Escape' || (e.key === 'Enter' && (f.type === 'text' || f.type === 'number'))) openFilter = undefined;
|
|
692
|
+
}}
|
|
693
|
+
>
|
|
694
|
+
{#if f.type === 'boolean'}
|
|
695
|
+
<label for={`${uid}-${f.id}`}><strong>{f.title}</strong></label>
|
|
696
|
+
<Checkbox id={`${uid}-${f.id}`} size="sm" checked={valueOf(f) === true} onchange={(e) => set(f, e.currentTarget.checked)} />
|
|
697
|
+
{:else if openFilter === f.id}
|
|
698
|
+
<strong>{f.title}</strong>
|
|
699
|
+
{@render editor(f, false)}
|
|
700
|
+
{:else}
|
|
701
|
+
<button type="button" onclick={() => (openFilter = f.id)}>
|
|
702
|
+
<strong>{f.title}</strong>
|
|
703
|
+
{#if f.type === 'entity' && entity && valueOf(f)}{@render entity(f, valueOf(f) as EntityValue)}{:else}{summary(f)}{/if}
|
|
704
|
+
</button>
|
|
705
|
+
{/if}
|
|
706
|
+
<button
|
|
707
|
+
type="button"
|
|
708
|
+
class={`${PREFIX}-filter-remove`}
|
|
709
|
+
aria-label={t.removeFilter(f.title)}
|
|
710
|
+
onclick={() => {
|
|
711
|
+
openFilter = undefined;
|
|
712
|
+
handler.removeFilter(f.id);
|
|
713
|
+
}}
|
|
714
|
+
>{@render icon('M18 6 6 18M6 6l12 12')}</button>
|
|
715
|
+
</Chip>
|
|
716
|
+
{/each}
|
|
717
|
+
|
|
718
|
+
{#if compact && handler.total !== undefined}
|
|
719
|
+
<span class={`${PREFIX}-total`}>{t.results(handler.total)}</span>
|
|
720
|
+
{/if}
|
|
721
|
+
</div>
|
|
722
|
+
{/if}
|
|
723
|
+
|
|
724
|
+
{@render header?.(handler)}
|
|
725
|
+
|
|
726
|
+
{#if pagination === 'top' || pagination === 'both'}{@render pager()}{/if}
|
|
727
|
+
|
|
728
|
+
<div class={`${PREFIX}-body`}>{@render children?.(handler)}</div>
|
|
729
|
+
|
|
730
|
+
{#if pagination === 'bottom' || pagination === 'both'}{@render pager()}{/if}
|
|
731
|
+
|
|
732
|
+
{@render footer?.(handler)}
|
|
733
|
+
|
|
734
|
+
{#if confirm}
|
|
735
|
+
<Dialog
|
|
736
|
+
bind:open={dialogOpen}
|
|
737
|
+
size="sm"
|
|
738
|
+
title={asking?.kind === 'delete' ? t.confirmDeleteTitle(asking.count) : asking ? t.confirmDiscardTitle(asking.count) : ''}
|
|
739
|
+
onClose={answered}
|
|
740
|
+
>
|
|
741
|
+
{asking?.kind === 'delete' ? t.confirmDeleteText : t.confirmDiscardText}
|
|
742
|
+
{#snippet actions()}
|
|
743
|
+
{#if asking?.kind === 'delete'}
|
|
744
|
+
<Button value="cancel">{t.cancel}</Button>
|
|
745
|
+
<Button value="delete" color="error">{t.delete}</Button>
|
|
746
|
+
{:else}
|
|
747
|
+
<Button value="stay" variant="ghost">{t.stay}</Button>
|
|
748
|
+
<Button value="discard">{t.discard}</Button>
|
|
749
|
+
<Button value="save" color="primary">{t.save}</Button>
|
|
750
|
+
{/if}
|
|
751
|
+
{/snippet}
|
|
752
|
+
</Dialog>
|
|
753
|
+
{/if}
|
|
754
|
+
</div>
|