@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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
min-width: 0;
|
|
9
9
|
align-items: var(--grid-align-items);
|
|
10
10
|
gap: var(--grid-gap);
|
|
11
|
+
grid-auto-flow: var(--grid-flow);
|
|
11
12
|
justify-items: var(--grid-justify-items);
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -300,7 +301,58 @@
|
|
|
300
301
|
|
|
301
302
|
@container (width < 24rem) {
|
|
302
303
|
.grid[data-compact-columns] {
|
|
304
|
+
grid-auto-flow: var(--grid-compact-flow);
|
|
303
305
|
grid-template-columns: repeat(var(--grid-compact-columns), minmax(0, 1fr));
|
|
306
|
+
grid-template-rows: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.grid[data-compact-columns]
|
|
310
|
+
> [data-composition='grid-item'][data-compact-span='1'] {
|
|
311
|
+
grid-column: span 1;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.grid[data-compact-columns]
|
|
315
|
+
> [data-composition='grid-item'][data-compact-span='2'] {
|
|
316
|
+
grid-column: span 2;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.grid[data-compact-columns]
|
|
320
|
+
> [data-composition='grid-item'][data-compact-span='3'] {
|
|
321
|
+
grid-column: span 3;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.grid[data-compact-columns]
|
|
325
|
+
> [data-composition='grid-item'][data-compact-span='4'] {
|
|
326
|
+
grid-column: span 4;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.grid[data-compact-columns]
|
|
330
|
+
> [data-composition='grid-item'][data-compact-row-span='1'] {
|
|
331
|
+
grid-row: span 1;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.grid[data-compact-columns]
|
|
335
|
+
> [data-composition='grid-item'][data-compact-row-span='2'] {
|
|
336
|
+
grid-row: span 2;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.grid[data-compact-columns]
|
|
340
|
+
> [data-composition='grid-item'][data-compact-row-span='3'] {
|
|
341
|
+
grid-row: span 3;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.grid[data-compact-columns]
|
|
345
|
+
> [data-composition='grid-item'][data-compact-row-span='4'] {
|
|
346
|
+
grid-row: span 4;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.grid[data-compact-columns]
|
|
350
|
+
> [data-composition='grid-item'][data-sizing='fill-track'] {
|
|
351
|
+
--grid-item-fill-track-contain: none;
|
|
352
|
+
--grid-item-fill-track-content-block-size: auto;
|
|
353
|
+
--grid-item-fill-track-content-inset: auto;
|
|
354
|
+
--grid-item-fill-track-content-position: static;
|
|
355
|
+
--grid-item-fill-track-overflow: visible;
|
|
304
356
|
}
|
|
305
357
|
|
|
306
358
|
.grid[data-last-row='fill'][data-compact-columns] > * {
|
|
@@ -14,8 +14,11 @@ type Props = Omit<GridItemAttributes, 'class' | 'style'> & {
|
|
|
14
14
|
alignSelf?: GridItemAlignment;
|
|
15
15
|
class?: string;
|
|
16
16
|
collapseOrder?: GridItemCollapseOrder;
|
|
17
|
+
compactRowSpan?: GridItemSpan;
|
|
18
|
+
compactSpan?: GridItemSpan;
|
|
17
19
|
justifySelf?: GridItemAlignment;
|
|
18
20
|
position?: GridItemPosition;
|
|
21
|
+
rowSpan?: GridItemSpan;
|
|
19
22
|
sizing?: GridItemSizing;
|
|
20
23
|
span?: GridItemSpan;
|
|
21
24
|
};
|
|
@@ -25,8 +28,11 @@ const {
|
|
|
25
28
|
alignSelf = 'auto',
|
|
26
29
|
class: className,
|
|
27
30
|
collapseOrder = 'preserve',
|
|
31
|
+
compactRowSpan,
|
|
32
|
+
compactSpan,
|
|
28
33
|
justifySelf = 'auto',
|
|
29
34
|
position = 'static',
|
|
35
|
+
rowSpan = 1,
|
|
30
36
|
sizing = 'auto',
|
|
31
37
|
span = 1,
|
|
32
38
|
...attrs
|
|
@@ -39,8 +45,11 @@ const {
|
|
|
39
45
|
data-composition="grid-item"
|
|
40
46
|
data-align-self={alignSelf}
|
|
41
47
|
data-collapse-order={collapseOrder}
|
|
48
|
+
data-compact-row-span={compactRowSpan}
|
|
49
|
+
data-compact-span={compactSpan}
|
|
42
50
|
data-justify-self={justifySelf}
|
|
43
51
|
data-position={position}
|
|
52
|
+
data-row-span={rowSpan}
|
|
44
53
|
data-sizing={sizing}
|
|
45
54
|
data-span={span}
|
|
46
55
|
>
|
|
@@ -69,6 +69,18 @@
|
|
|
69
69
|
grid-column: span 4;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
.grid-item[data-row-span='2'] {
|
|
73
|
+
grid-row: span 2;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.grid-item[data-row-span='3'] {
|
|
77
|
+
grid-row: span 3;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.grid-item[data-row-span='4'] {
|
|
81
|
+
grid-row: span 4;
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
.grid-item[data-sizing='fill-track'] {
|
|
73
85
|
--grid-item-fill-track-contain: size layout;
|
|
74
86
|
--grid-item-fill-track-content-block-size: 100%;
|
|
@@ -34,34 +34,48 @@
|
|
|
34
34
|
content: '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
.surface-group:has(> [data-sticky-surface]):not(
|
|
38
|
+
[data-sticky-measured]
|
|
39
|
+
)::before,
|
|
37
40
|
.surface-group[data-sticky-active]::before {
|
|
38
41
|
visibility: visible;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
.surface-group > [data-surface] {
|
|
44
|
+
.surface-group > [data-surface]:not([hidden]) {
|
|
42
45
|
flex: 0 0 auto;
|
|
43
46
|
min-width: 0;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
.surface-group
|
|
47
|
-
> [data-surface]:not(
|
|
48
|
-
:has(~ [data-surface]),
|
|
50
|
+
> [data-surface]:not([hidden]):not(
|
|
51
|
+
:has(~ [data-surface]:not([hidden])),
|
|
49
52
|
[data-surface-sizing='content']
|
|
50
53
|
),
|
|
51
54
|
.surface-group
|
|
52
|
-
> [data-surface]:
|
|
53
|
-
|
|
55
|
+
> [data-surface]:not([hidden]):not(
|
|
56
|
+
[data-surface-sizing='content']
|
|
57
|
+
):has(
|
|
58
|
+
~ [data-surface]:not([hidden])[data-surface-sizing='content']
|
|
59
|
+
):not(
|
|
60
|
+
:has(
|
|
61
|
+
~ [data-surface]:not([hidden]):not(
|
|
62
|
+
[data-surface-sizing='content']
|
|
63
|
+
)
|
|
64
|
+
)
|
|
54
65
|
) {
|
|
55
66
|
flex: 1 1 auto;
|
|
56
67
|
min-height: 0;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
|
-
.surface-group
|
|
70
|
+
.surface-group
|
|
71
|
+
> [data-surface]:not([hidden]):has(~ [data-surface]:not([hidden])) {
|
|
60
72
|
border-end-start-radius: 0;
|
|
61
73
|
border-end-end-radius: 0;
|
|
62
74
|
}
|
|
63
75
|
|
|
64
|
-
.surface-group
|
|
76
|
+
.surface-group
|
|
77
|
+
> [data-surface]:not([hidden])
|
|
78
|
+
~ [data-surface]:not([hidden]) {
|
|
65
79
|
border-block-start-width: 0;
|
|
66
80
|
border-start-start-radius: 0;
|
|
67
81
|
border-start-end-radius: 0;
|
|
@@ -42,6 +42,7 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
42
42
|
const observe = () => {
|
|
43
43
|
resizeFrame = 0;
|
|
44
44
|
observer?.disconnect();
|
|
45
|
+
root.removeAttribute('data-sticky-measured');
|
|
45
46
|
|
|
46
47
|
const stickyOffset =
|
|
47
48
|
Number.parseFloat(window.getComputedStyle(stickySurface).top) || 0;
|
|
@@ -54,6 +55,7 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
54
55
|
'data-sticky-active',
|
|
55
56
|
entry.boundingClientRect.top <= activationBoundary + 0.5,
|
|
56
57
|
);
|
|
58
|
+
root.setAttribute('data-sticky-measured', 'true');
|
|
57
59
|
},
|
|
58
60
|
{
|
|
59
61
|
root: scrollParent instanceof Window ? null : scrollParent,
|
|
@@ -85,5 +87,6 @@ export const initStickySurfaceGroup = (root: HTMLElement): (() => void) => {
|
|
|
85
87
|
|
|
86
88
|
root.removeAttribute('data-sticky-active');
|
|
87
89
|
root.removeAttribute('data-sticky-initialized');
|
|
90
|
+
root.removeAttribute('data-sticky-measured');
|
|
88
91
|
};
|
|
89
92
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Firestore } from 'firebase/firestore';
|
|
2
|
+
|
|
3
|
+
export type ClientFirestorePage<TItem> = {
|
|
4
|
+
items: readonly TItem[];
|
|
5
|
+
nextCursor?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ClientFirestoreOrder = {
|
|
9
|
+
direction?: 'asc' | 'desc';
|
|
10
|
+
field: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ClientFirestoreCursor<TDocument> = {
|
|
14
|
+
serialize(input: { document: TDocument; id: string }): string;
|
|
15
|
+
toQueryValues(cursor: string): readonly unknown[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ClientFirestoreDocumentMapper<
|
|
19
|
+
TItem,
|
|
20
|
+
TCreateInput,
|
|
21
|
+
TUpdateInput,
|
|
22
|
+
> = {
|
|
23
|
+
fromDocument(id: string, value: unknown): TItem;
|
|
24
|
+
toCreate(input: TCreateInput): Record<string, unknown>;
|
|
25
|
+
toUpdate(input: TUpdateInput): Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ClientFirestoreSchema<TDocument> = {
|
|
29
|
+
parse(input: unknown): TDocument;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ClientFirestoreRepository<TItem, TCreateInput, TUpdateInput> = {
|
|
33
|
+
getIndex(input?: {
|
|
34
|
+
cursor?: string;
|
|
35
|
+
limit?: number;
|
|
36
|
+
}): Promise<ClientFirestorePage<TItem>>;
|
|
37
|
+
getDetail(input: { id: string }): Promise<TItem | null>;
|
|
38
|
+
create(input: TCreateInput): Promise<TItem>;
|
|
39
|
+
update(input: { id: string; value: TUpdateInput }): Promise<TItem>;
|
|
40
|
+
delete(input: { id: string }): Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ClientFirestoreRepositoryConfig<
|
|
44
|
+
TDocument,
|
|
45
|
+
TItem,
|
|
46
|
+
TCreateInput,
|
|
47
|
+
TUpdateInput,
|
|
48
|
+
> = {
|
|
49
|
+
collection: string;
|
|
50
|
+
cursor?: ClientFirestoreCursor<TDocument>;
|
|
51
|
+
database: Firestore;
|
|
52
|
+
mapper: ClientFirestoreDocumentMapper<TItem, TCreateInput, TUpdateInput>;
|
|
53
|
+
orderBy?: readonly ClientFirestoreOrder[];
|
|
54
|
+
schema: ClientFirestoreSchema<TDocument>;
|
|
55
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getApps, initializeApp } from 'firebase/app';
|
|
2
|
+
import { getFirestore } from 'firebase/firestore';
|
|
3
|
+
|
|
4
|
+
import type { FirebaseClientConfig } from '../../../auth/firebase/contracts';
|
|
5
|
+
|
|
6
|
+
export function createFirebaseClientFirestore(config: FirebaseClientConfig) {
|
|
7
|
+
const app =
|
|
8
|
+
getApps().find((candidate) => candidate.name === config.projectId) ??
|
|
9
|
+
initializeApp(config, config.projectId);
|
|
10
|
+
|
|
11
|
+
return getFirestore(app);
|
|
12
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addDoc,
|
|
3
|
+
collection,
|
|
4
|
+
deleteDoc,
|
|
5
|
+
doc,
|
|
6
|
+
documentId,
|
|
7
|
+
getDoc,
|
|
8
|
+
getDocs,
|
|
9
|
+
limit as limitQuery,
|
|
10
|
+
orderBy as orderByQuery,
|
|
11
|
+
query,
|
|
12
|
+
startAfter,
|
|
13
|
+
updateDoc,
|
|
14
|
+
type QueryConstraint,
|
|
15
|
+
} from 'firebase/firestore';
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
ClientFirestoreRepository,
|
|
19
|
+
ClientFirestoreRepositoryConfig,
|
|
20
|
+
} from './contracts';
|
|
21
|
+
|
|
22
|
+
const defaultPageSize = 25;
|
|
23
|
+
const maximumPageSize = 100;
|
|
24
|
+
|
|
25
|
+
export function createClientFirestoreRepository<
|
|
26
|
+
TDocument,
|
|
27
|
+
TItem,
|
|
28
|
+
TCreateInput,
|
|
29
|
+
TUpdateInput = TCreateInput,
|
|
30
|
+
>({
|
|
31
|
+
collection: collectionName,
|
|
32
|
+
cursor: cursorConfig,
|
|
33
|
+
database,
|
|
34
|
+
mapper,
|
|
35
|
+
orderBy = [],
|
|
36
|
+
schema,
|
|
37
|
+
}: ClientFirestoreRepositoryConfig<
|
|
38
|
+
TDocument,
|
|
39
|
+
TItem,
|
|
40
|
+
TCreateInput,
|
|
41
|
+
TUpdateInput
|
|
42
|
+
>): ClientFirestoreRepository<TItem, TCreateInput, TUpdateInput> {
|
|
43
|
+
const documents = collection(database, collectionName);
|
|
44
|
+
const read = (id: string, value: unknown): TItem =>
|
|
45
|
+
mapper.fromDocument(id, schema.parse(value));
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
async getIndex({ cursor, limit = defaultPageSize } = {}) {
|
|
49
|
+
const pageSize = Math.min(
|
|
50
|
+
maximumPageSize,
|
|
51
|
+
Math.max(1, Math.floor(limit)),
|
|
52
|
+
);
|
|
53
|
+
const constraints: QueryConstraint[] = orderBy.map((order) =>
|
|
54
|
+
orderByQuery(order.field, order.direction ?? 'asc'),
|
|
55
|
+
);
|
|
56
|
+
constraints.push(orderByQuery(documentId(), 'asc'));
|
|
57
|
+
|
|
58
|
+
if (cursor && cursorConfig) {
|
|
59
|
+
constraints.push(startAfter(...cursorConfig.toQueryValues(cursor)));
|
|
60
|
+
} else if (cursor) {
|
|
61
|
+
const cursorDocument = await getDoc(doc(documents, cursor));
|
|
62
|
+
if (cursorDocument.exists()) {
|
|
63
|
+
constraints.push(startAfter(cursorDocument));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
constraints.push(limitQuery(pageSize));
|
|
67
|
+
|
|
68
|
+
const snapshot = await getDocs(query(documents, ...constraints));
|
|
69
|
+
const results = snapshot.docs.map((document) => ({
|
|
70
|
+
document: schema.parse(document.data()),
|
|
71
|
+
id: document.id,
|
|
72
|
+
}));
|
|
73
|
+
const items = results.map(({ document, id }) =>
|
|
74
|
+
mapper.fromDocument(id, document),
|
|
75
|
+
);
|
|
76
|
+
const lastResult = results.at(-1);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
items,
|
|
80
|
+
nextCursor:
|
|
81
|
+
snapshot.size === pageSize && lastResult
|
|
82
|
+
? cursorConfig
|
|
83
|
+
? cursorConfig.serialize(lastResult)
|
|
84
|
+
: lastResult.id
|
|
85
|
+
: undefined,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async getDetail({ id }) {
|
|
90
|
+
const snapshot = await getDoc(doc(documents, id));
|
|
91
|
+
return snapshot.exists() ? read(snapshot.id, snapshot.data()) : null;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async create(input) {
|
|
95
|
+
const reference = await addDoc(documents, mapper.toCreate(input));
|
|
96
|
+
const snapshot = await getDoc(reference);
|
|
97
|
+
return read(snapshot.id, snapshot.data());
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async update({ id, value }) {
|
|
101
|
+
const reference = doc(documents, id);
|
|
102
|
+
await updateDoc(reference, mapper.toUpdate(value));
|
|
103
|
+
const snapshot = await getDoc(reference);
|
|
104
|
+
return read(snapshot.id, snapshot.data());
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async delete({ id }) {
|
|
108
|
+
await deleteDoc(doc(documents, id));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
package/src/display/contracts.ts
CHANGED
|
@@ -190,11 +190,33 @@ export type TableColumn = {
|
|
|
190
190
|
sizing?: 'content' | 'fill';
|
|
191
191
|
wrap?: boolean;
|
|
192
192
|
sort?: {
|
|
193
|
+
firstDirection?: 'ascending' | 'descending';
|
|
193
194
|
type: 'number' | 'text';
|
|
194
195
|
tieBreaker?: string;
|
|
195
196
|
};
|
|
196
197
|
};
|
|
197
198
|
|
|
199
|
+
type TableActionBase = {
|
|
200
|
+
borderless?: boolean;
|
|
201
|
+
hoverTone?: Tone;
|
|
202
|
+
icon: AstroComponent;
|
|
203
|
+
iconTone?: Tone;
|
|
204
|
+
label: string;
|
|
205
|
+
tone?: Tone;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export type TableAction = TableActionBase &
|
|
209
|
+
(
|
|
210
|
+
| {
|
|
211
|
+
href: string;
|
|
212
|
+
modalTargetId?: never;
|
|
213
|
+
}
|
|
214
|
+
| {
|
|
215
|
+
href?: never;
|
|
216
|
+
modalTargetId: string;
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
198
220
|
export type TableCell = {
|
|
199
221
|
tooltip?: string;
|
|
200
222
|
} & (
|
|
@@ -238,10 +260,15 @@ export type TableCell = {
|
|
|
238
260
|
kind: 'tags';
|
|
239
261
|
tags: readonly DisplayTag[];
|
|
240
262
|
}
|
|
263
|
+
| {
|
|
264
|
+
kind: 'actions';
|
|
265
|
+
actions: readonly TableAction[];
|
|
266
|
+
}
|
|
241
267
|
);
|
|
242
268
|
|
|
243
269
|
export type TableRow = {
|
|
244
270
|
key?: string;
|
|
271
|
+
disabled?: boolean;
|
|
245
272
|
href?: string;
|
|
246
273
|
hidden?: boolean;
|
|
247
274
|
filters?: Readonly<Record<string, readonly string[]>>;
|
|
@@ -255,6 +282,20 @@ export type TablePaginationModel = {
|
|
|
255
282
|
nextHref?: string;
|
|
256
283
|
};
|
|
257
284
|
|
|
285
|
+
export type TableProgressiveEntry = {
|
|
286
|
+
key: string;
|
|
287
|
+
source?: string;
|
|
288
|
+
filters?: TableRow['filters'];
|
|
289
|
+
sortValues?: TableRow['sortValues'];
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export type TableProgressiveModel = {
|
|
293
|
+
entries: readonly TableProgressiveEntry[];
|
|
294
|
+
sources?: Readonly<Record<string, string>>;
|
|
295
|
+
batchSize?: number;
|
|
296
|
+
initialOffset?: number;
|
|
297
|
+
};
|
|
298
|
+
|
|
258
299
|
type NavigationItemBase = {
|
|
259
300
|
active?: boolean;
|
|
260
301
|
id?: string;
|
package/src/display/index.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TablePaginationModel,
|
|
3
|
+
TableProgressiveModel,
|
|
4
|
+
TableRow,
|
|
5
|
+
} from './contracts';
|
|
6
|
+
|
|
7
|
+
export const tableProgressiveBatchSize = 25;
|
|
8
|
+
|
|
9
|
+
export type PagedTablePresentation = {
|
|
10
|
+
pagination: TablePaginationModel;
|
|
11
|
+
progressive: TableProgressiveModel;
|
|
12
|
+
rows: readonly TableRow[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function createPagedTablePresentation(
|
|
16
|
+
allRows: readonly TableRow[],
|
|
17
|
+
page: number,
|
|
18
|
+
getPageHref: (page: number) => string,
|
|
19
|
+
batchSize: number = tableProgressiveBatchSize,
|
|
20
|
+
): PagedTablePresentation {
|
|
21
|
+
const resolvedBatchSize = Math.max(1, Math.floor(batchSize));
|
|
22
|
+
const totalPages = Math.max(1, Math.ceil(allRows.length / resolvedBatchSize));
|
|
23
|
+
if (!Number.isInteger(page) || page < 1 || page > totalPages) {
|
|
24
|
+
throw new RangeError(`Invalid table page: ${page}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const normalizedRows = allRows.map((row, index): TableRow => ({
|
|
28
|
+
...row,
|
|
29
|
+
key: row.key ?? `table-row-${index + 1}`,
|
|
30
|
+
}));
|
|
31
|
+
const keys = new Set(normalizedRows.map((row) => row.key));
|
|
32
|
+
if (keys.size !== normalizedRows.length) {
|
|
33
|
+
throw new Error('Progressive table rows require unique keys');
|
|
34
|
+
}
|
|
35
|
+
const sources = Object.fromEntries(
|
|
36
|
+
Array.from({ length: totalPages }, (_, index) => {
|
|
37
|
+
const sourcePage = index + 1;
|
|
38
|
+
return [String(sourcePage), getPageHref(sourcePage)];
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
const start = (page - 1) * resolvedBatchSize;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
pagination: {
|
|
45
|
+
page,
|
|
46
|
+
totalPages,
|
|
47
|
+
nextHref: page < totalPages ? getPageHref(page + 1) : undefined,
|
|
48
|
+
},
|
|
49
|
+
progressive: {
|
|
50
|
+
batchSize: resolvedBatchSize,
|
|
51
|
+
entries: normalizedRows.map((row, index) => ({
|
|
52
|
+
key: row.key!,
|
|
53
|
+
source: String(Math.floor(index / resolvedBatchSize) + 1),
|
|
54
|
+
filters: row.filters,
|
|
55
|
+
sortValues: row.sortValues,
|
|
56
|
+
})),
|
|
57
|
+
initialOffset: start,
|
|
58
|
+
sources,
|
|
59
|
+
},
|
|
60
|
+
rows: normalizedRows.slice(start, start + resolvedBatchSize),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import type {
|
|
4
|
+
PreferenceBinding,
|
|
5
|
+
PreferenceNumberBounds,
|
|
6
|
+
} from '../../client/preferences';
|
|
3
7
|
import type { Tone } from '../../theme/contracts';
|
|
4
8
|
import type {
|
|
5
9
|
ComboboxOption,
|
|
@@ -15,6 +19,7 @@ type SharedProps = Omit<HTMLAttributes<'div'>, 'id'> & {
|
|
|
15
19
|
id: string;
|
|
16
20
|
label: string;
|
|
17
21
|
disabled?: boolean;
|
|
22
|
+
preference?: PreferenceBinding;
|
|
18
23
|
size?: ComboboxSize;
|
|
19
24
|
tone?: Tone;
|
|
20
25
|
width?: 'auto' | 'fill';
|
|
@@ -41,6 +46,7 @@ type NumberProps = SharedProps & {
|
|
|
41
46
|
value: number;
|
|
42
47
|
decrementLabel: string;
|
|
43
48
|
incrementLabel: string;
|
|
49
|
+
preferenceBounds?: PreferenceNumberBounds;
|
|
44
50
|
};
|
|
45
51
|
|
|
46
52
|
type Props = OptionsProps | NumberProps;
|
|
@@ -130,6 +136,8 @@ if (mode === 'number') {
|
|
|
130
136
|
'max',
|
|
131
137
|
'min',
|
|
132
138
|
'mode',
|
|
139
|
+
'preference',
|
|
140
|
+
'preferenceBounds',
|
|
133
141
|
'size',
|
|
134
142
|
'step',
|
|
135
143
|
'tone',
|
|
@@ -185,6 +193,7 @@ if (mode === 'number') {
|
|
|
185
193
|
'mode',
|
|
186
194
|
'options',
|
|
187
195
|
'placeholder',
|
|
196
|
+
'preference',
|
|
188
197
|
'removeLabel',
|
|
189
198
|
'searchPlaceholder',
|
|
190
199
|
'selectionMode',
|
|
@@ -233,6 +242,20 @@ const searchId = `${props.id}-search`;
|
|
|
233
242
|
data-combobox-number-step={step}
|
|
234
243
|
data-disabled={disabled ? true : undefined}
|
|
235
244
|
data-tone={resolvedTone}
|
|
245
|
+
data-preference-id={props.preference?.id}
|
|
246
|
+
data-preference-path={props.preference?.path}
|
|
247
|
+
data-preference-control={
|
|
248
|
+
props.preference
|
|
249
|
+
? mode === 'number'
|
|
250
|
+
? 'combobox-number'
|
|
251
|
+
: 'combobox-options'
|
|
252
|
+
: undefined
|
|
253
|
+
}
|
|
254
|
+
data-preference-number-bounds={
|
|
255
|
+
mode === 'number' && (props as NumberProps).preferenceBounds
|
|
256
|
+
? JSON.stringify((props as NumberProps).preferenceBounds)
|
|
257
|
+
: undefined
|
|
258
|
+
}
|
|
236
259
|
>
|
|
237
260
|
<span class="combobox__sizer" aria-hidden="true">
|
|
238
261
|
{
|