@astronox/core 0.2.0 → 0.2.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 +9 -30
- package/package.json +7 -1
- package/src/admin/contracts.ts +28 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/module.ts +28 -0
- package/src/auth/contracts.ts +53 -0
- package/src/auth/firebase/client.ts +53 -0
- package/src/auth/firebase/contracts.ts +8 -0
- package/src/auth/firebase/index.ts +1 -0
- package/src/auth/index.ts +2 -0
- package/src/auth/module.ts +99 -0
- package/src/client/index.ts +3 -0
- package/src/client/preferences.ts +400 -0
- package/src/components/actions/Button/Button.astro +42 -6
- package/src/components/actions/Button/Button.css +19 -0
- package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +35 -0
- package/src/components/actions/MediaTrigger/MediaTrigger.astro +12 -4
- package/src/components/actions/MediaTrigger/MediaTrigger.css +1 -0
- package/src/components/content/CardBody/CardBody.astro +3 -0
- package/src/components/content/CardBody/CardBody.css +10 -0
- package/src/components/content/MarkdownContent/MarkdownContent.css +0 -1
- package/src/components/content/MarkdownContent/MarkdownNode.astro +1 -0
- package/src/components/content/MarkdownContent/markdownContentRenderer.ts +549 -0
- package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +403 -0
- package/src/components/forms/Checkbox/Checkbox.astro +66 -0
- package/src/components/forms/Checkbox/Checkbox.css +126 -0
- package/src/components/forms/ChoiceGroup/ChoiceGroup.astro +57 -0
- package/src/components/forms/ChoiceGroup/ChoiceGroup.css +108 -0
- package/src/components/forms/ChoiceGroup/contracts.ts +9 -0
- package/src/components/forms/DateTimeInput/DateTimeInput.astro +14 -0
- package/src/components/forms/FieldGroup/FieldGroup.astro +34 -0
- package/src/components/forms/FieldGroup/FieldGroup.css +30 -0
- package/src/components/forms/TextInput/TextInput.astro +7 -1
- package/src/components/forms/Textarea/Textarea.astro +32 -0
- package/src/components/forms/Textarea/Textarea.css +54 -0
- package/src/components/icons/BackIcon.astro +26 -0
- package/src/components/icons/CheckIcon.astro +22 -0
- package/src/components/icons/DeleteIcon.astro +22 -0
- package/src/components/icons/StatusIndicatorIcon.astro +16 -0
- package/src/components/index.ts +12 -0
- package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -17
- package/src/components/media/EmbedFrame/EmbedFrame.astro +1 -19
- package/src/components/media/EmbedFrame/EmbedFrame.css +0 -2
- package/src/components/navigation/SubNavigation/SubNavigation.css +0 -5
- package/src/composition/Grid/Grid.astro +9 -0
- package/src/composition/Grid/Grid.css +52 -0
- package/src/composition/GridItem/GridItem.astro +9 -0
- package/src/composition/GridItem/GridItem.css +12 -0
- package/src/composition/SurfaceGroup/SurfaceGroup.css +21 -7
- package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +3 -0
- package/src/data/firestore/client/contracts.ts +55 -0
- package/src/data/firestore/client/firebase.ts +12 -0
- package/src/data/firestore/client/index.ts +3 -0
- package/src/data/firestore/client/repository.ts +111 -0
- package/src/display/contracts.ts +41 -0
- package/src/display/index.ts +1 -0
- package/src/display/tableProgressive.ts +62 -0
- package/src/patterns/Combobox/Combobox.astro +23 -0
- package/src/patterns/Combobox/comboboxController.ts +72 -0
- package/src/patterns/Fullscreen/FullscreenControl.astro +4 -14
- package/src/patterns/Fullscreen/FullscreenControl.css +0 -26
- package/src/patterns/Modal/Modal.astro +1 -1
- package/src/patterns/Modal/Modal.css +38 -1
- package/src/patterns/Table/Table.astro +122 -7
- package/src/patterns/Table/Table.css +2 -1
- package/src/patterns/Table/TableBody.astro +11 -1
- package/src/patterns/Table/TableBody.css +6 -0
- package/src/patterns/Table/TableCell.css +6 -0
- package/src/patterns/Table/TableCellValue.astro +31 -0
- package/src/patterns/Table/TableFooter.astro +3 -1
- package/src/patterns/Table/TableHeader.astro +1 -0
- package/src/patterns/Table/TablePagination.astro +63 -47
- package/src/patterns/Table/TableSkeleton.astro +18 -22
- package/src/patterns/Table/TableSkeleton.css +30 -5
- package/src/patterns/Table/tableClientController.ts +253 -0
- package/src/patterns/Table/tableFilterController.ts +83 -33
- package/src/patterns/Table/tableLayout.ts +10 -3
- package/src/patterns/Table/tableProgressiveController.ts +615 -227
- package/src/patterns/Table/tableSortController.ts +25 -7
- package/src/styles/interaction.css +16 -0
- package/src/styles/style.css +4 -0
|
@@ -12,6 +12,11 @@ type SetComboboxValueOptions = {
|
|
|
12
12
|
emit?: boolean;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
export type ComboboxNumberBounds = {
|
|
16
|
+
min: number;
|
|
17
|
+
max: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
15
20
|
type NumberBounds = {
|
|
16
21
|
min: number;
|
|
17
22
|
max: number;
|
|
@@ -93,6 +98,17 @@ function normalizeNumber(value: number, bounds: NumberBounds): number {
|
|
|
93
98
|
return Math.min(bounds.max, Math.max(bounds.min, stepped));
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
function syncNumberOptionBounds(
|
|
102
|
+
root: HTMLElement,
|
|
103
|
+
bounds: NumberBounds,
|
|
104
|
+
): void {
|
|
105
|
+
for (const option of getOptions(root)) {
|
|
106
|
+
const value = Number(option.dataset.comboboxOption);
|
|
107
|
+
option.hidden =
|
|
108
|
+
!Number.isFinite(value) || value < bounds.min || value > bounds.max;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
export function setComboboxValues(
|
|
97
113
|
root: HTMLElement,
|
|
98
114
|
requestedValues: readonly string[],
|
|
@@ -186,6 +202,43 @@ export function setComboboxNumberValue(
|
|
|
186
202
|
}
|
|
187
203
|
}
|
|
188
204
|
|
|
205
|
+
export function setComboboxNumberBounds(
|
|
206
|
+
root: HTMLElement,
|
|
207
|
+
{ min, max }: ComboboxNumberBounds,
|
|
208
|
+
): void {
|
|
209
|
+
if (root.dataset.comboboxMode !== 'number') return;
|
|
210
|
+
|
|
211
|
+
const currentBounds = getNumberBounds(root);
|
|
212
|
+
const optionValues = getOptions(root)
|
|
213
|
+
.map((option) => Number(option.dataset.comboboxOption))
|
|
214
|
+
.filter(Number.isFinite);
|
|
215
|
+
const renderedValues = new Set(optionValues);
|
|
216
|
+
if (
|
|
217
|
+
!currentBounds ||
|
|
218
|
+
!Number.isInteger(min) ||
|
|
219
|
+
!Number.isInteger(max) ||
|
|
220
|
+
min > max ||
|
|
221
|
+
!renderedValues.has(min) ||
|
|
222
|
+
!renderedValues.has(max)
|
|
223
|
+
) {
|
|
224
|
+
throw new RangeError(
|
|
225
|
+
'Combobox number bounds must be ordered rendered values aligned to the configured step',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
root.dataset.comboboxNumberMin = String(min);
|
|
230
|
+
root.dataset.comboboxNumberMax = String(max);
|
|
231
|
+
const bounds = getNumberBounds(root);
|
|
232
|
+
const input = root.querySelector<HTMLInputElement>(
|
|
233
|
+
'[data-combobox-number-input]',
|
|
234
|
+
);
|
|
235
|
+
if (!bounds || !input) return;
|
|
236
|
+
|
|
237
|
+
syncNumberOptionBounds(root, bounds);
|
|
238
|
+
setComboboxNumberValue(root, Number(input.value));
|
|
239
|
+
root.dispatchEvent(new Event('combobox:number-bounds-change'));
|
|
240
|
+
}
|
|
241
|
+
|
|
189
242
|
export function initCombobox(root: HTMLElement): () => void {
|
|
190
243
|
if (root.getAttribute(initializedAttribute) === 'true') return noop;
|
|
191
244
|
|
|
@@ -343,6 +396,12 @@ export function initCombobox(root: HTMLElement): () => void {
|
|
|
343
396
|
: null;
|
|
344
397
|
|
|
345
398
|
const resetFilter = (): void => {
|
|
399
|
+
if (mode === 'number') {
|
|
400
|
+
const bounds = getNumberBounds(root);
|
|
401
|
+
if (bounds) syncNumberOptionBounds(root, bounds);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
346
405
|
if (search) search.value = '';
|
|
347
406
|
for (const option of getOptions(root)) option.hidden = false;
|
|
348
407
|
if (empty) empty.hidden = getOptions(root).length > 0;
|
|
@@ -736,6 +795,11 @@ export function initCombobox(root: HTMLElement): () => void {
|
|
|
736
795
|
close();
|
|
737
796
|
};
|
|
738
797
|
|
|
798
|
+
const handleNumberBoundsChange = (): void => {
|
|
799
|
+
setActiveOption(null);
|
|
800
|
+
schedulePanelPosition();
|
|
801
|
+
};
|
|
802
|
+
|
|
739
803
|
if (mode === 'options' && trigger && search) {
|
|
740
804
|
control.addEventListener('click', handleOptionsControlClick);
|
|
741
805
|
trigger.addEventListener('click', handleOptionsTriggerClick);
|
|
@@ -759,6 +823,10 @@ export function initCombobox(root: HTMLElement): () => void {
|
|
|
759
823
|
}
|
|
760
824
|
optionsRoot.addEventListener('click', handleOptionClick);
|
|
761
825
|
root.addEventListener('combobox:clear', handleClear);
|
|
826
|
+
root.addEventListener(
|
|
827
|
+
'combobox:number-bounds-change',
|
|
828
|
+
handleNumberBoundsChange,
|
|
829
|
+
);
|
|
762
830
|
|
|
763
831
|
return () => {
|
|
764
832
|
close();
|
|
@@ -778,6 +846,10 @@ export function initCombobox(root: HTMLElement): () => void {
|
|
|
778
846
|
}
|
|
779
847
|
optionsRoot.removeEventListener('click', handleOptionClick);
|
|
780
848
|
root.removeEventListener('combobox:clear', handleClear);
|
|
849
|
+
root.removeEventListener(
|
|
850
|
+
'combobox:number-bounds-change',
|
|
851
|
+
handleNumberBoundsChange,
|
|
852
|
+
);
|
|
781
853
|
panel.style.removeProperty('--combobox-panel-inline-start');
|
|
782
854
|
panel.style.removeProperty('--combobox-panel-block-start');
|
|
783
855
|
panel.style.removeProperty('--combobox-panel-inline-size');
|
|
@@ -6,8 +6,6 @@ import EnterFullscreenIcon from '../../components/icons/EnterFullscreenIcon.astr
|
|
|
6
6
|
import ExitFullscreenIcon from '../../components/icons/ExitFullscreenIcon.astro';
|
|
7
7
|
import ExternalLinkIcon from '../../components/icons/ExternalLinkIcon.astro';
|
|
8
8
|
|
|
9
|
-
type FullscreenControlPlacement = 'inline' | 'overlay';
|
|
10
|
-
type FullscreenOverlayOffset = 'default' | 'native-controls';
|
|
11
9
|
type FullscreenUnsupportedFallback = {
|
|
12
10
|
href: string;
|
|
13
11
|
label: string;
|
|
@@ -17,8 +15,6 @@ type FullscreenUnsupportedFallback = {
|
|
|
17
15
|
type Props = Omit<HTMLAttributes<'button'>, 'aria-controls' | 'type'> & {
|
|
18
16
|
enterLabel: string;
|
|
19
17
|
exitLabel: string;
|
|
20
|
-
overlayOffset?: FullscreenOverlayOffset;
|
|
21
|
-
placement?: FullscreenControlPlacement;
|
|
22
18
|
targetId: string;
|
|
23
19
|
tone?: Tone;
|
|
24
20
|
unsupportedFallback?: FullscreenUnsupportedFallback;
|
|
@@ -28,8 +24,6 @@ const {
|
|
|
28
24
|
class: className,
|
|
29
25
|
enterLabel,
|
|
30
26
|
exitLabel,
|
|
31
|
-
overlayOffset = 'default',
|
|
32
|
-
placement = 'inline',
|
|
33
27
|
targetId,
|
|
34
28
|
tone,
|
|
35
29
|
unsupportedFallback,
|
|
@@ -44,10 +38,9 @@ const resolvedClass = ['fullscreen-control', className]
|
|
|
44
38
|
{...attrs}
|
|
45
39
|
class={resolvedClass}
|
|
46
40
|
type="button"
|
|
47
|
-
borderless
|
|
48
|
-
full
|
|
41
|
+
borderless
|
|
42
|
+
full
|
|
49
43
|
iconOnly
|
|
50
|
-
size={placement === 'overlay' ? 'md' : undefined}
|
|
51
44
|
tone={tone}
|
|
52
45
|
aria-controls={targetId}
|
|
53
46
|
aria-label={enterLabel}
|
|
@@ -56,8 +49,6 @@ const resolvedClass = ['fullscreen-control', className]
|
|
|
56
49
|
data-fullscreen-control
|
|
57
50
|
data-fullscreen-enter-label={enterLabel}
|
|
58
51
|
data-fullscreen-exit-label={exitLabel}
|
|
59
|
-
data-fullscreen-overlay-offset={overlayOffset}
|
|
60
|
-
data-fullscreen-placement={placement}
|
|
61
52
|
data-fullscreen-target={targetId}
|
|
62
53
|
>
|
|
63
54
|
<span class="fullscreen-control__icon" data-fullscreen-enter-icon>
|
|
@@ -73,10 +64,9 @@ const resolvedClass = ['fullscreen-control', className]
|
|
|
73
64
|
<Button
|
|
74
65
|
href={unsupportedFallback.href}
|
|
75
66
|
target={unsupportedFallback.target ?? '_blank'}
|
|
76
|
-
borderless
|
|
77
|
-
full
|
|
67
|
+
borderless
|
|
68
|
+
full
|
|
78
69
|
iconOnly
|
|
79
|
-
size={placement === 'overlay' ? 'md' : undefined}
|
|
80
70
|
tone={tone}
|
|
81
71
|
aria-label={unsupportedFallback.label}
|
|
82
72
|
title={unsupportedFallback.label}
|
|
@@ -15,32 +15,6 @@
|
|
|
15
15
|
display: grid;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
.fullscreen-control[data-fullscreen-placement='overlay'].nox-button {
|
|
19
|
-
position: absolute;
|
|
20
|
-
z-index: 2;
|
|
21
|
-
display: none;
|
|
22
|
-
inset-inline-end: var(--nox-space-3);
|
|
23
|
-
inset-block-end: var(--nox-space-3);
|
|
24
|
-
inline-size: var(--nox-section-responsive-block-size);
|
|
25
|
-
block-size: var(--nox-section-responsive-block-size);
|
|
26
|
-
min-block-size: 0;
|
|
27
|
-
border-radius: var(--nox-radius-md);
|
|
28
|
-
box-shadow: var(--nox-shadow-surface);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
[data-fullscreen-region]:fullscreen
|
|
32
|
-
.fullscreen-control[data-fullscreen-placement='overlay'].nox-button {
|
|
33
|
-
display: inline-flex;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
[data-fullscreen-region]:fullscreen
|
|
37
|
-
.fullscreen-control[data-fullscreen-placement='overlay'][data-fullscreen-overlay-offset='native-controls'].nox-button {
|
|
38
|
-
inset-block-end: calc(
|
|
39
|
-
var(--nox-fullscreen-native-controls-block-size, 2.5rem) +
|
|
40
|
-
var(--nox-space-3)
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
18
|
[data-fullscreen-region]:fullscreen {
|
|
45
19
|
inline-size: 100vw;
|
|
46
20
|
block-size: 100vh;
|
|
@@ -4,7 +4,7 @@ import type { Tone } from '../../theme/contracts';
|
|
|
4
4
|
import ModalClose from './ModalClose.astro';
|
|
5
5
|
|
|
6
6
|
type ModalCloseControl = 'default' | 'delegated';
|
|
7
|
-
type ModalSizing = 'content' | 'viewport';
|
|
7
|
+
type ModalSizing = 'adaptive' | 'confirmation' | 'content' | 'viewport';
|
|
8
8
|
|
|
9
9
|
type Props = HTMLAttributes<'dialog'> & {
|
|
10
10
|
id: string;
|
|
@@ -48,6 +48,33 @@ html:has(.modal[open]) [data-site-viewport] {
|
|
|
48
48
|
overflow: auto;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
.modal[data-sizing='confirmation'] .modal__content {
|
|
52
|
+
inline-size: min(42rem, 100%);
|
|
53
|
+
block-size: auto;
|
|
54
|
+
max-block-size: 100%;
|
|
55
|
+
align-self: center;
|
|
56
|
+
overflow: auto;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.modal[data-sizing='adaptive'] .modal__content {
|
|
60
|
+
display: flex;
|
|
61
|
+
block-size: 100%;
|
|
62
|
+
min-block-size: 0;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.modal[data-sizing='adaptive']
|
|
69
|
+
.modal__content
|
|
70
|
+
> [data-composition='surface-group'] {
|
|
71
|
+
inline-size: 100%;
|
|
72
|
+
min-block-size: 0;
|
|
73
|
+
max-block-size: 100%;
|
|
74
|
+
flex: 0 1 auto;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
}
|
|
77
|
+
|
|
51
78
|
.modal__default-close {
|
|
52
79
|
position: absolute;
|
|
53
80
|
z-index: 1;
|
|
@@ -81,7 +108,17 @@ html:has(.modal[open]) [data-site-viewport] {
|
|
|
81
108
|
padding-inline: 0;
|
|
82
109
|
}
|
|
83
110
|
|
|
84
|
-
.
|
|
111
|
+
.modal:not(
|
|
112
|
+
[data-sizing='adaptive'],
|
|
113
|
+
[data-sizing='confirmation']
|
|
114
|
+
)
|
|
115
|
+
.modal__content
|
|
116
|
+
[data-surface] {
|
|
85
117
|
border-radius: 0;
|
|
86
118
|
}
|
|
119
|
+
|
|
120
|
+
.modal:is([data-sizing='adaptive'], [data-sizing='confirmation'])
|
|
121
|
+
.modal__content {
|
|
122
|
+
padding: var(--nox-space-2);
|
|
123
|
+
}
|
|
87
124
|
}
|
|
@@ -3,14 +3,17 @@ import type { HTMLAttributes } from 'astro/types';
|
|
|
3
3
|
import type {
|
|
4
4
|
TableColumn,
|
|
5
5
|
TablePaginationModel,
|
|
6
|
+
TableProgressiveModel,
|
|
6
7
|
TableRow,
|
|
7
8
|
} from '../../display/contracts';
|
|
9
|
+
import { tableProgressiveBatchSize } from '../../display/tableProgressive';
|
|
8
10
|
import type { Tone } from '../../theme/contracts';
|
|
9
11
|
import SurfaceGroup from '../../composition/SurfaceGroup/SurfaceGroup.astro';
|
|
10
12
|
import TableBody from './TableBody.astro';
|
|
11
13
|
import TableFooter from './TableFooter.astro';
|
|
12
14
|
import TableHeader from './TableHeader.astro';
|
|
13
15
|
import TablePagination from './TablePagination.astro';
|
|
16
|
+
import TableSkeleton from './TableSkeleton.astro';
|
|
14
17
|
import {
|
|
15
18
|
getTableColumnDensity,
|
|
16
19
|
getTableMinimumInlineSize,
|
|
@@ -33,6 +36,7 @@ type TablePaginationMessages = {
|
|
|
33
36
|
|
|
34
37
|
type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
|
|
35
38
|
caption: string;
|
|
39
|
+
clientProgressive?: boolean;
|
|
36
40
|
columns: readonly TableColumn[];
|
|
37
41
|
density?: 'regular' | 'compact';
|
|
38
42
|
emptyMessage: string;
|
|
@@ -44,6 +48,8 @@ type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
|
|
|
44
48
|
pagination?: TablePaginationModel;
|
|
45
49
|
paginationKey?: string;
|
|
46
50
|
paginationMessages?: TablePaginationMessages;
|
|
51
|
+
preparation?: 'automatic' | 'controlled';
|
|
52
|
+
progressive?: false | TableProgressiveModel;
|
|
47
53
|
rowSizing?: 'content' | 'fill';
|
|
48
54
|
rows: readonly TableRow[];
|
|
49
55
|
skeletonRows?: number;
|
|
@@ -54,6 +60,7 @@ type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
|
|
|
54
60
|
|
|
55
61
|
const {
|
|
56
62
|
caption,
|
|
63
|
+
clientProgressive = false,
|
|
57
64
|
class: className,
|
|
58
65
|
columns,
|
|
59
66
|
'data-tone': dataTone,
|
|
@@ -67,6 +74,8 @@ const {
|
|
|
67
74
|
pagination,
|
|
68
75
|
paginationKey,
|
|
69
76
|
paginationMessages,
|
|
77
|
+
preparation = 'automatic',
|
|
78
|
+
progressive,
|
|
70
79
|
rowSizing = 'content',
|
|
71
80
|
rows,
|
|
72
81
|
skeletonRows,
|
|
@@ -95,6 +104,62 @@ const columnDensity = getTableColumnDensity(columns);
|
|
|
95
104
|
const resolvedTone = tone ?? dataTone;
|
|
96
105
|
const hasSortableColumns = columns.some((column) => column.sort);
|
|
97
106
|
const hasFooter = Astro.slots.has('footer');
|
|
107
|
+
const configuredProgressive =
|
|
108
|
+
progressive && typeof progressive === 'object' ? progressive : undefined;
|
|
109
|
+
const batchSize = Math.max(
|
|
110
|
+
1,
|
|
111
|
+
Math.floor(configuredProgressive?.batchSize ?? tableProgressiveBatchSize),
|
|
112
|
+
);
|
|
113
|
+
const resolvedRows = rows.map((row, index): TableRow => ({
|
|
114
|
+
...row,
|
|
115
|
+
key: row.key ?? `${paginationKey ?? 'table'}-${index + 1}`,
|
|
116
|
+
}));
|
|
117
|
+
const inferredProgressive =
|
|
118
|
+
progressive === undefined && resolvedRows.length > batchSize
|
|
119
|
+
? {
|
|
120
|
+
batchSize,
|
|
121
|
+
entries: resolvedRows.map((row) => ({
|
|
122
|
+
key: row.key!,
|
|
123
|
+
filters: row.filters,
|
|
124
|
+
sortValues: row.sortValues,
|
|
125
|
+
})),
|
|
126
|
+
}
|
|
127
|
+
: undefined;
|
|
128
|
+
const progressiveModel =
|
|
129
|
+
progressive === false
|
|
130
|
+
? undefined
|
|
131
|
+
: configuredProgressive ?? inferredProgressive;
|
|
132
|
+
const hasProgressiveRows = Boolean(
|
|
133
|
+
progressiveModel && progressiveModel.entries.length > batchSize,
|
|
134
|
+
);
|
|
135
|
+
const hasProgressivePagination = hasProgressiveRows || clientProgressive;
|
|
136
|
+
const initialSkeletonRows = Math.max(
|
|
137
|
+
1,
|
|
138
|
+
Math.floor(
|
|
139
|
+
skeletonRows ??
|
|
140
|
+
Math.min(
|
|
141
|
+
batchSize,
|
|
142
|
+
(progressiveModel?.entries.length ?? resolvedRows.length) || 1,
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
const preparesRows =
|
|
147
|
+
(resolvedRows.length > 0 || clientProgressive) && !errorMessage;
|
|
148
|
+
const initialLoadingState = errorMessage
|
|
149
|
+
? 'error'
|
|
150
|
+
: resolvedRows.length === 0 && !clientProgressive
|
|
151
|
+
? 'empty'
|
|
152
|
+
: 'pending';
|
|
153
|
+
const progressiveManifest = hasProgressiveRows
|
|
154
|
+
? JSON.stringify({
|
|
155
|
+
...progressiveModel,
|
|
156
|
+
batchSize,
|
|
157
|
+
}).replaceAll('<', '\\u003c')
|
|
158
|
+
: undefined;
|
|
159
|
+
const rowKeys = new Set(resolvedRows.map((row) => row.key));
|
|
160
|
+
const progressiveKeys = new Set(
|
|
161
|
+
progressiveModel?.entries.map((entry) => entry.key) ?? [],
|
|
162
|
+
);
|
|
98
163
|
|
|
99
164
|
if (header === 'visible' && hasSortableColumns && !sortMessages) {
|
|
100
165
|
throw new Error('Sortable table columns require localized sort messages');
|
|
@@ -102,6 +167,21 @@ if (header === 'visible' && hasSortableColumns && !sortMessages) {
|
|
|
102
167
|
if (pagination?.nextHref && !paginationMessages) {
|
|
103
168
|
throw new Error('Paginated tables require localized pagination messages');
|
|
104
169
|
}
|
|
170
|
+
if (clientProgressive && !paginationMessages) {
|
|
171
|
+
throw new Error('Client-progressive tables require localized messages');
|
|
172
|
+
}
|
|
173
|
+
if (progressiveModel?.sources && !paginationKey) {
|
|
174
|
+
throw new Error('External progressive tables require a stable pagination key');
|
|
175
|
+
}
|
|
176
|
+
if (rowKeys.size !== resolvedRows.length) {
|
|
177
|
+
throw new Error('Table rows require unique keys');
|
|
178
|
+
}
|
|
179
|
+
if (
|
|
180
|
+
progressiveModel &&
|
|
181
|
+
progressiveKeys.size !== progressiveModel.entries.length
|
|
182
|
+
) {
|
|
183
|
+
throw new Error('Progressive table entries require unique keys');
|
|
184
|
+
}
|
|
105
185
|
---
|
|
106
186
|
|
|
107
187
|
<div
|
|
@@ -110,18 +190,32 @@ if (pagination?.nextHref && !paginationMessages) {
|
|
|
110
190
|
data-widget="table"
|
|
111
191
|
data-composition="table"
|
|
112
192
|
data-table-column-density={columnDensity}
|
|
193
|
+
data-table-client-progressive={clientProgressive ? true : undefined}
|
|
113
194
|
data-table-density={density}
|
|
114
195
|
data-table-inline-overflow={inlineOverflow}
|
|
115
196
|
data-table-index-minimum-rem={
|
|
116
197
|
minimumInlineSize ? Number.parseFloat(minimumInlineSize) : undefined
|
|
117
198
|
}
|
|
118
199
|
data-table-layout={layout}
|
|
200
|
+
data-table-loading-state={initialLoadingState}
|
|
201
|
+
data-table-preparation={preparation}
|
|
119
202
|
data-table-key={paginationKey}
|
|
120
203
|
data-table-scroll-root
|
|
121
204
|
data-table-sticky-header={stickyHeader ? true : undefined}
|
|
122
205
|
data-tone={resolvedTone}
|
|
123
206
|
style={tableStyle}
|
|
124
207
|
>
|
|
208
|
+
{
|
|
209
|
+
preparesRows ? (
|
|
210
|
+
<script is:inline>
|
|
211
|
+
document.documentElement.setAttribute('data-nox-table-enhanced', '');
|
|
212
|
+
document.currentScript?.parentElement?.setAttribute(
|
|
213
|
+
'aria-busy',
|
|
214
|
+
'true',
|
|
215
|
+
);
|
|
216
|
+
</script>
|
|
217
|
+
) : null
|
|
218
|
+
}
|
|
125
219
|
<SurfaceGroup>
|
|
126
220
|
<slot name="heading" />
|
|
127
221
|
{
|
|
@@ -137,17 +231,17 @@ if (pagination?.nextHref && !paginationMessages) {
|
|
|
137
231
|
filterEmptyMessage={filterEmptyMessage}
|
|
138
232
|
layout={layout}
|
|
139
233
|
rowSizing={rowSizing}
|
|
140
|
-
rows={
|
|
234
|
+
rows={resolvedRows}
|
|
235
|
+
skeletonRows={preparesRows ? initialSkeletonRows : 0}
|
|
141
236
|
/>
|
|
142
237
|
{
|
|
143
|
-
pagination && paginationMessages ? (
|
|
238
|
+
hasProgressivePagination || (pagination && paginationMessages) ? (
|
|
144
239
|
<TablePagination
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
nextLabel={paginationMessages.nextPage}
|
|
240
|
+
errorLabel={paginationMessages?.error ?? ''}
|
|
241
|
+
loadingLabel={paginationMessages?.loading ?? ''}
|
|
242
|
+
nextLabel={paginationMessages?.nextPage ?? ''}
|
|
149
243
|
pagination={pagination}
|
|
150
|
-
|
|
244
|
+
progressive={hasProgressivePagination}
|
|
151
245
|
/>
|
|
152
246
|
) : null
|
|
153
247
|
}
|
|
@@ -159,6 +253,27 @@ if (pagination?.nextHref && !paginationMessages) {
|
|
|
159
253
|
) : null
|
|
160
254
|
}
|
|
161
255
|
</SurfaceGroup>
|
|
256
|
+
{
|
|
257
|
+
progressiveManifest ? (
|
|
258
|
+
<script
|
|
259
|
+
is:inline
|
|
260
|
+
type="application/json"
|
|
261
|
+
data-table-progressive-manifest
|
|
262
|
+
set:html={progressiveManifest}
|
|
263
|
+
/>
|
|
264
|
+
) : null
|
|
265
|
+
}
|
|
266
|
+
{
|
|
267
|
+
preparesRows ? (
|
|
268
|
+
<template data-table-skeleton-template>
|
|
269
|
+
<table>
|
|
270
|
+
<tbody>
|
|
271
|
+
<TableSkeleton columns={columns} rows={initialSkeletonRows} />
|
|
272
|
+
</tbody>
|
|
273
|
+
</table>
|
|
274
|
+
</template>
|
|
275
|
+
) : null
|
|
276
|
+
}
|
|
162
277
|
</div>
|
|
163
278
|
|
|
164
279
|
<script>
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
min-inline-size: 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
.table[data-table-column-density='compact']
|
|
20
|
+
.table[data-table-column-density='compact'],
|
|
21
|
+
.table[data-table-layout='index'] {
|
|
21
22
|
--table-cell-padding-inline: var(--nox-space-1);
|
|
22
23
|
--table-edge-padding-inline: var(--nox-space-3);
|
|
23
24
|
--table-header-letter-spacing: 0;
|
|
@@ -5,6 +5,7 @@ import type { Tone } from '../../theme/contracts';
|
|
|
5
5
|
import TableSortIcon from '../../components/icons/TableSortIcon.astro';
|
|
6
6
|
import TextBlock from '../../primitives/text/TextBlock/TextBlock.astro';
|
|
7
7
|
import TableCellContent from './TableCell.astro';
|
|
8
|
+
import TableSkeleton from './TableSkeleton.astro';
|
|
8
9
|
import {
|
|
9
10
|
getTableColumnAlignment,
|
|
10
11
|
type TableLayout,
|
|
@@ -20,6 +21,7 @@ type Props = HTMLAttributes<'div'> & {
|
|
|
20
21
|
layout?: TableLayout;
|
|
21
22
|
rowSizing?: 'content' | 'fill';
|
|
22
23
|
rows: readonly TableRow[];
|
|
24
|
+
skeletonRows?: number;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
const {
|
|
@@ -33,6 +35,7 @@ const {
|
|
|
33
35
|
layout = 'default',
|
|
34
36
|
rowSizing = 'content',
|
|
35
37
|
rows,
|
|
38
|
+
skeletonRows = 0,
|
|
36
39
|
...attrs
|
|
37
40
|
}: Props = Astro.props;
|
|
38
41
|
|
|
@@ -86,6 +89,11 @@ const stateMessage =
|
|
|
86
89
|
))
|
|
87
90
|
}
|
|
88
91
|
</tr>
|
|
92
|
+
{
|
|
93
|
+
skeletonRows > 0 ? (
|
|
94
|
+
<TableSkeleton columns={columns} rows={skeletonRows} />
|
|
95
|
+
) : null
|
|
96
|
+
}
|
|
89
97
|
{
|
|
90
98
|
state && stateMessage ? (
|
|
91
99
|
<tr class="table-body__row" data-table-state={state}>
|
|
@@ -102,12 +110,14 @@ const stateMessage =
|
|
|
102
110
|
</tr>
|
|
103
111
|
) : (
|
|
104
112
|
rows.map((row) => {
|
|
105
|
-
const rowHref = row.href;
|
|
113
|
+
const rowHref = row.disabled ? undefined : row.href;
|
|
106
114
|
|
|
107
115
|
return (
|
|
108
116
|
<tr
|
|
109
117
|
class="table-body__row"
|
|
110
118
|
hidden={row.hidden}
|
|
119
|
+
aria-disabled={row.disabled ? 'true' : undefined}
|
|
120
|
+
data-disabled={row.disabled ? 'true' : undefined}
|
|
111
121
|
data-table-row
|
|
112
122
|
data-table-row-key={row.key}
|
|
113
123
|
data-table-row-href={rowHref}
|
|
@@ -166,6 +166,12 @@
|
|
|
166
166
|
cursor: pointer;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
.table-body__row[data-disabled='true'] {
|
|
170
|
+
opacity: 0.4;
|
|
171
|
+
filter: grayscale(0.65);
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
}
|
|
174
|
+
|
|
169
175
|
.table-body__row[data-table-row-href]:focus-within {
|
|
170
176
|
--table-primary-link-color: var(--nox-link-hover);
|
|
171
177
|
}
|
|
@@ -41,6 +41,12 @@
|
|
|
41
41
|
min-inline-size: max-content;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
.table-cell[data-table-cell-kind='actions'] {
|
|
45
|
+
inline-size: max-content;
|
|
46
|
+
min-inline-size: max-content;
|
|
47
|
+
overflow: visible;
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
.table-cell[data-table-cell-kind='tags']:not([data-wrap])
|
|
45
51
|
> [data-composition='inline']
|
|
46
52
|
> [data-widget='tag'] {
|
|
@@ -6,6 +6,7 @@ import TextPair from '../../components/data-display/TextPair/TextPair.astro';
|
|
|
6
6
|
import Media from '../../primitives/media/Media/Media.astro';
|
|
7
7
|
import Inline from '../../composition/Inline/Inline.astro';
|
|
8
8
|
import Tag from '../../components/data-display/Tag/Tag.astro';
|
|
9
|
+
import Button from '../../components/actions/Button/Button.astro';
|
|
9
10
|
|
|
10
11
|
type Props = {
|
|
11
12
|
cell: TableCell;
|
|
@@ -84,3 +85,33 @@ const formatValue = (value: DisplayTextValue): string => String(value);
|
|
|
84
85
|
</Inline>
|
|
85
86
|
) : null
|
|
86
87
|
}
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
cell.kind === 'actions' ? (
|
|
91
|
+
<Inline align="center" gap="xs" justify="end" wrap={false}>
|
|
92
|
+
{cell.actions.map((action) => {
|
|
93
|
+
const Icon = action.icon;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Button
|
|
97
|
+
aria-controls={action.modalTargetId}
|
|
98
|
+
aria-haspopup={action.modalTargetId ? 'dialog' : undefined}
|
|
99
|
+
aria-label={action.label}
|
|
100
|
+
borderless={action.borderless !== false}
|
|
101
|
+
href={action.href}
|
|
102
|
+
hoverTone={action.hoverTone}
|
|
103
|
+
iconOnly
|
|
104
|
+
size="sm"
|
|
105
|
+
title={action.label}
|
|
106
|
+
tone={action.tone}
|
|
107
|
+
data-modal-open={action.modalTargetId}
|
|
108
|
+
>
|
|
109
|
+
<span data-tone={action.iconTone}>
|
|
110
|
+
<Icon />
|
|
111
|
+
</span>
|
|
112
|
+
</Button>
|
|
113
|
+
);
|
|
114
|
+
})}
|
|
115
|
+
</Inline>
|
|
116
|
+
) : null
|
|
117
|
+
}
|
|
@@ -6,6 +6,7 @@ type TableFooterPadding = 'none' | 'sm' | 'md' | 'lg';
|
|
|
6
6
|
|
|
7
7
|
type Props = HTMLAttributes<'div'> & {
|
|
8
8
|
padding?: TableFooterPadding;
|
|
9
|
+
surface?: boolean;
|
|
9
10
|
tone?: Tone;
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@ const {
|
|
|
13
14
|
class: className,
|
|
14
15
|
'data-tone': dataTone,
|
|
15
16
|
padding = 'md',
|
|
17
|
+
surface = true,
|
|
16
18
|
tone,
|
|
17
19
|
...attrs
|
|
18
20
|
}: Props = Astro.props;
|
|
@@ -26,7 +28,7 @@ const resolvedTone = tone ?? dataTone;
|
|
|
26
28
|
data-padding={padding}
|
|
27
29
|
data-table-footer
|
|
28
30
|
data-tone={resolvedTone}
|
|
29
|
-
data-surface
|
|
31
|
+
data-surface={surface ? true : undefined}
|
|
30
32
|
>
|
|
31
33
|
<slot />
|
|
32
34
|
</div>
|
|
@@ -53,6 +53,7 @@ const hasSortableColumns = columns.some((column) => column.sort);
|
|
|
53
53
|
type="button"
|
|
54
54
|
data-table-sort-column={column.id}
|
|
55
55
|
data-table-sort-direction="none"
|
|
56
|
+
data-table-sort-first-direction={column.sort.firstDirection}
|
|
56
57
|
data-table-sort-label={column.label}
|
|
57
58
|
data-table-sort-type={column.sort.type}
|
|
58
59
|
data-table-sort-tie-breaker={column.sort.tieBreaker}
|