@nixxie-cms/core 1.0.0 → 1.0.1

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.
Files changed (186) hide show
  1. package/README.md +2 -2
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +4 -4
  3. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +4 -4
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  5. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  6. package/context/dist/nixxie-cms-core-context.cjs.js +2 -2
  7. package/context/dist/nixxie-cms-core-context.esm.js +2 -2
  8. package/dist/{CreateItemDialog-33335548.esm.js → CreateItemDialog-7008b050.esm.js} +1 -1
  9. package/dist/{CreateItemDialog-56cf59b7.cjs.js → CreateItemDialog-a0cab315.cjs.js} +1 -1
  10. package/dist/{PageContainer-7db73317.esm.js → PageContainer-5ae731cc.esm.js} +25 -18
  11. package/dist/{PageContainer-27c27f10.cjs.js → PageContainer-abd7159f.cjs.js} +25 -18
  12. package/dist/{admin-meta-graphql-6f7f5331.esm.js → admin-meta-graphql-0e6e606e.esm.js} +1 -1
  13. package/dist/{admin-meta-graphql-c8f926e9.cjs.js → admin-meta-graphql-306c224a.cjs.js} +1 -1
  14. package/dist/{context-3132c3ed.esm.js → context-af9957ed.esm.js} +2 -2
  15. package/dist/{context-e7a45152.cjs.js → context-b5204629.cjs.js} +2 -2
  16. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  18. package/dist/declarations/src/helpers.d.ts.map +1 -1
  19. package/dist/declarations/src/index.d.ts +1 -0
  20. package/dist/declarations/src/index.d.ts.map +1 -1
  21. package/dist/declarations/src/internal-unstable/admin-ui/id-field-view.d.ts.map +1 -0
  22. package/dist/declarations/src/internal-unstable/admin-ui/pages/App/index.d.ts.map +1 -0
  23. package/dist/declarations/src/internal-unstable/admin-ui/pages/CreateItemPage/index.d.ts.map +1 -0
  24. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -0
  25. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -0
  26. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -0
  27. package/dist/declarations/src/internal-unstable/admin-ui/pages/NoAccessPage/index.d.ts.map +1 -0
  28. package/dist/declarations/src/internal-unstable/artifacts.d.ts.map +1 -0
  29. package/dist/declarations/src/lib/core/initialise-lists.d.ts +1 -1
  30. package/dist/declarations/src/schema.d.ts.map +1 -1
  31. package/dist/declarations/src/types/config/index.d.ts +60 -1
  32. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  33. package/dist/declarations/src/types/config/lists.d.ts +4 -4
  34. package/dist/declarations/src/types/context.d.ts +150 -0
  35. package/dist/declarations/src/types/context.d.ts.map +1 -1
  36. package/dist/declarations/src/types/next-fields.d.ts +1 -1
  37. package/dist/{express-e9ed9a7d.cjs.js → express-455ae20c.cjs.js} +1 -1
  38. package/dist/{express-6743b918.esm.js → express-7559ca2d.esm.js} +1 -1
  39. package/dist/{index-ac01583b.cjs.js → index-89635494.cjs.js} +4 -4
  40. package/dist/{index-24b78415.esm.js → index-baa799e0.esm.js} +4 -4
  41. package/dist/nixxie-cms-core.cjs.js +104 -77
  42. package/dist/nixxie-cms-core.esm.js +104 -77
  43. package/dist/{non-null-graphql-5315718c.esm.js → non-null-graphql-a84ed64d.esm.js} +1 -1
  44. package/dist/{non-null-graphql-17b83ddc.cjs.js → non-null-graphql-add6bb3d.cjs.js} +1 -1
  45. package/dist/{resolve-hooks-66fe8a8e.cjs.js → resolve-hooks-165a9ce2.cjs.js} +1 -1
  46. package/dist/{resolve-hooks-17aafd37.esm.js → resolve-hooks-6813a045.esm.js} +2 -2
  47. package/dist/{system-dfec2f0a.esm.js → system-03e49e4f.esm.js} +8 -4
  48. package/dist/{system-48c5f6df.cjs.js → system-a321642d.cjs.js} +8 -4
  49. package/dist/{useFilter-0b5a1ee6.esm.js → useFilter-9b6db1f9.esm.js} +1 -1
  50. package/dist/{useFilter-1a4e6900.cjs.js → useFilter-acc9d413.cjs.js} +1 -1
  51. package/fields/dist/nixxie-cms-core-fields.cjs.js +16 -16
  52. package/fields/dist/nixxie-cms-core-fields.esm.js +17 -17
  53. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +3 -3
  54. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +3 -3
  55. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +1 -1
  56. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +1 -1
  57. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +3 -3
  58. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +3 -3
  59. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +4 -4
  60. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +4 -4
  61. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +1 -1
  62. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +1 -1
  63. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +1 -1
  64. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +1 -1
  65. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.d.ts +2 -0
  66. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.js +244 -0
  67. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.esm.js +235 -0
  68. package/internal-unstable/admin-ui/id-field-view/package.json +4 -0
  69. package/internal-unstable/admin-ui/next-config/package.json +4 -0
  70. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.d.ts +2 -0
  71. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +59 -0
  72. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +55 -0
  73. package/internal-unstable/admin-ui/pages/App/package.json +4 -0
  74. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.d.ts +2 -0
  75. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +116 -0
  76. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +112 -0
  77. package/internal-unstable/admin-ui/pages/CreateItemPage/package.json +4 -0
  78. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.d.ts +2 -0
  79. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +336 -0
  80. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +332 -0
  81. package/internal-unstable/admin-ui/pages/HomePage/package.json +4 -0
  82. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.d.ts +2 -0
  83. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +463 -0
  84. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +455 -0
  85. package/internal-unstable/admin-ui/pages/ItemPage/package.json +4 -0
  86. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.d.ts +2 -0
  87. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +1195 -0
  88. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +1187 -0
  89. package/internal-unstable/admin-ui/pages/ListPage/package.json +4 -0
  90. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.d.ts +2 -0
  91. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.js +40 -0
  92. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.esm.js +35 -0
  93. package/internal-unstable/admin-ui/pages/NoAccessPage/package.json +4 -0
  94. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.d.ts +2 -0
  95. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +51 -0
  96. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +38 -0
  97. package/internal-unstable/artifacts/package.json +4 -0
  98. package/package.json +44 -44
  99. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +15 -15
  100. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +15 -15
  101. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +3 -3
  102. package/scripts/dist/nixxie-cms-core-scripts.esm.js +3 -3
  103. package/src/admin-ui/admin-meta-graphql.ts +168 -168
  104. package/src/admin-ui/components/CommandPalette.tsx +433 -431
  105. package/src/admin-ui/components/Navigation.tsx +389 -385
  106. package/src/admin-ui/components/PageContainer.tsx +311 -310
  107. package/src/admin-ui/components/WelcomeDialog.tsx +1 -1
  108. package/src/admin-ui/context.tsx +338 -338
  109. package/src/admin-ui/templates/app.ts +60 -60
  110. package/src/admin-ui/templates/create-item.ts +5 -5
  111. package/src/admin-ui/templates/home.ts +2 -2
  112. package/src/admin-ui/templates/item.tsx +5 -5
  113. package/src/admin-ui/templates/list.tsx +5 -5
  114. package/src/admin-ui/templates/no-access.ts +7 -7
  115. package/src/fields/types/bigInt/index.ts +181 -181
  116. package/src/fields/types/bytes/index.ts +275 -275
  117. package/src/fields/types/calendarDay/index.ts +194 -194
  118. package/src/fields/types/checkbox/index.ts +76 -76
  119. package/src/fields/types/decimal/index.ts +182 -182
  120. package/src/fields/types/file/index.ts +168 -168
  121. package/src/fields/types/float/index.ts +133 -133
  122. package/src/fields/types/image/index.ts +244 -244
  123. package/src/fields/types/integer/index.ts +156 -156
  124. package/src/fields/types/json/index.ts +77 -77
  125. package/src/fields/types/multiselect/index.ts +212 -212
  126. package/src/fields/types/password/index.ts +241 -241
  127. package/src/fields/types/relationship/index.ts +381 -381
  128. package/src/fields/types/relationship/views/RelationshipTable.tsx +190 -190
  129. package/src/fields/types/select/index.ts +226 -226
  130. package/src/fields/types/text/index.ts +207 -207
  131. package/src/fields/types/timestamp/index.ts +116 -116
  132. package/src/fields/types/virtual/index.ts +108 -108
  133. package/src/helpers.ts +342 -316
  134. package/src/index.ts +4 -0
  135. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.tsx +167 -167
  136. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.tsx +22 -22
  137. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.tsx +71 -71
  138. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.tsx +333 -333
  139. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/common.tsx +358 -358
  140. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.tsx +483 -483
  141. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/FilterAdd.tsx +221 -221
  142. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/PaginationControls.tsx +170 -170
  143. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/Tag.tsx +72 -72
  144. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.tsx +1006 -1006
  145. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.tsx +24 -24
  146. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.ts +5 -5
  147. package/src/lib/context/createContext.ts +165 -161
  148. package/src/lib/core/initialise-lists.ts +1097 -1097
  149. package/src/lib/id-field.ts +214 -214
  150. package/src/lib/telemetry.ts +342 -342
  151. package/src/schema.ts +237 -233
  152. package/src/scripts/telemetry.ts +1 -1
  153. package/src/types/config/index.ts +400 -333
  154. package/src/types/config/lists.ts +4 -4
  155. package/src/types/context.ts +700 -530
  156. package/src/types/next-fields.ts +499 -499
  157. package/src/types/telemetry.ts +51 -51
  158. package/tests/telemetry.test.ts +361 -361
  159. package/CHANGELOG.md +0 -3158
  160. package/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view/package.json +0 -4
  161. package/___internal-do-not-use-will-break-in-patch/admin-ui/next-config/package.json +0 -4
  162. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/package.json +0 -4
  163. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/package.json +0 -4
  164. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/package.json +0 -4
  165. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/package.json +0 -4
  166. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/package.json +0 -4
  167. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/package.json +0 -4
  168. package/___internal-do-not-use-will-break-in-patch/artifacts/package.json +0 -4
  169. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.d.ts.map +0 -1
  170. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/index.d.ts.map +0 -1
  171. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/index.d.ts.map +0 -1
  172. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/index.d.ts.map +0 -1
  173. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.d.ts.map +0 -1
  174. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.d.ts.map +0 -1
  175. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/index.d.ts.map +0 -1
  176. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/artifacts.d.ts.map +0 -1
  177. /package/dist/{common-1a350e11.cjs.js → common-5933f758.cjs.js} +0 -0
  178. /package/dist/{common-29fc82e6.esm.js → common-ea5c441a.esm.js} +0 -0
  179. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.d.ts +0 -0
  180. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.d.ts +0 -0
  181. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.d.ts +0 -0
  182. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.d.ts +0 -0
  183. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.d.ts +0 -0
  184. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.d.ts +0 -0
  185. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.d.ts +0 -0
  186. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.d.ts +0 -0
@@ -0,0 +1,1187 @@
1
+ import isDeepEqual from 'fast-deep-equal';
2
+ import { useRouter } from 'next/router';
3
+ import { useRef, useState, useId, Fragment, useMemo, useEffect } from 'react';
4
+ import { Item as Item$1, ActionBarContainer, ActionBar } from '@keystar/ui/action-bar';
5
+ import { ActionButton, ButtonGroup, Button } from '@keystar/ui/button';
6
+ import { DialogTrigger, Dialog, DialogContainer, AlertDialog } from '@keystar/ui/dialog';
7
+ import { Icon } from '@keystar/ui/icon';
8
+ import { allIcons } from '@keystar/ui/icon/all';
9
+ import { chevronDownIcon } from '@keystar/ui/icon/icons/chevronDownIcon';
10
+ import { searchXIcon } from '@keystar/ui/icon/icons/searchXIcon';
11
+ import { textSelectIcon } from '@keystar/ui/icon/icons/textSelectIcon';
12
+ import { undo2Icon } from '@keystar/ui/icon/icons/undo2Icon';
13
+ import { Grid, VStack, HStack, Flex, Box } from '@keystar/ui/layout';
14
+ import { Item, MenuTrigger, Menu } from '@keystar/ui/menu';
15
+ import { ProgressCircle } from '@keystar/ui/progress';
16
+ import { SearchField } from '@keystar/ui/search-field';
17
+ import { Content, SlotProvider } from '@keystar/ui/slots';
18
+ import { css, tokenSchema } from '@keystar/ui/style';
19
+ import { TableView, TableHeader, Column, TableBody, Row, Cell } from '@keystar/ui/table';
20
+ import { toastQueue } from '@keystar/ui/toast';
21
+ import { TooltipTrigger, Tooltip } from '@keystar/ui/tooltip';
22
+ import { Text, Heading } from '@keystar/ui/typography';
23
+ import { TextLink } from '@keystar/ui/link';
24
+ import { Notice } from '@keystar/ui/notice';
25
+ import { gql, CombinedGraphQLErrors } from '@apollo/client';
26
+ import { useQuery, useMutation } from '@apollo/client/react';
27
+ import { p as pick, C as CreateButtonLink } from '../../../../../dist/pick-b7ef3115.esm.js';
28
+ import { d as deserializeItemToValue, E as EmptyState } from '../../../../../dist/utils-5e1d4d28.esm.js';
29
+ import { G as GraphQLErrorNotice } from '../../../../../dist/GraphQLErrorNotice-d9f0931b.esm.js';
30
+ import { P as PageContainer } from '../../../../../dist/PageContainer-5ae731cc.esm.js';
31
+ import { a as useList, P as PaginationControls, s as snapValueToClosest } from '../../../../../dist/context-af9957ed.esm.js';
32
+ import '@react-aria/utils';
33
+ import '@keystar/ui/icon/icons/chevronRightIcon';
34
+ import '@keystar/ui/icon/icons/textCursorInputIcon';
35
+ import { i as isActionAvailable, g as getConditionalFilterFieldKeys, s as serializeItemForConditionalFilters, r as resolveActionMode } from '../../../../../dist/filters-8c8616f9.esm.js';
36
+ import { jsxs, jsx } from 'react/jsx-runtime';
37
+ import { g as getActionArguments } from '../../../../../dist/actionData-64d4c37a.esm.js';
38
+ import { u as useSearchFilter } from '../../../../../dist/useFilter-9b6db1f9.esm.js';
39
+ import { Picker } from '@keystar/ui/picker';
40
+ import { xIcon } from '@keystar/ui/icon/icons/xIcon';
41
+ import { composeId } from '@keystar/ui/utils';
42
+ import 'graphql';
43
+ import 'next/head';
44
+ import 'next/link';
45
+ import 'apollo-upload-client/UploadHttpLink.mjs';
46
+ import '@keystar/ui/core';
47
+ import '@nixxie-cms/core/admin-ui/router';
48
+ import '@keystar/ui/icon/icons/chevronLeftIcon';
49
+ import '@keystar/ui/tag';
50
+ import '../../../../../dist/admin-meta-graphql-0e6e606e.esm.js';
51
+
52
+ function FilterAdd({
53
+ listKey,
54
+ onAdd,
55
+ isDisabled
56
+ }) {
57
+ const triggerRef = useRef(null);
58
+ const [state, setState] = useState({
59
+ kind: 'selecting-field'
60
+ });
61
+ const [forceValidation, setForceValidation] = useState(false);
62
+ const formId = useId();
63
+ const {
64
+ fieldsWithFilters,
65
+ filtersByFieldThenType,
66
+ list
67
+ } = useFilterFields(listKey);
68
+ const resetState = () => {
69
+ setState({
70
+ kind: 'selecting-field'
71
+ });
72
+ setForceValidation(false);
73
+ // This is a bit of a hack to ensure the trigger button is focused after the
74
+ // dialog closes, since we're forking the render
75
+ setTimeout(() => {
76
+ var _triggerRef$current;
77
+ triggerRef === null || triggerRef === void 0 || (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 || _triggerRef$current.focus();
78
+ }, 200);
79
+ };
80
+ const onSubmit = event => {
81
+ if (event.target !== event.currentTarget) return;
82
+ event.preventDefault();
83
+ setForceValidation(true);
84
+ if (state.kind !== 'filter-value') return;
85
+
86
+ // TODO: Special "empty" types need to be documented somewhere. Filters that
87
+ // have no editable value, basically `null` or `!null`. Which offers:
88
+ // * better DX — we can avoid weird nullable types and UIs that don't make sense
89
+ // * better UX — users don't have to jump through mental hoops, like "is not exactly" + submit empty field
90
+ if (state.filterType !== 'empty' && state.filterType !== 'not_empty' && state.filterValue == null) {
91
+ return;
92
+ }
93
+ onAdd({
94
+ field: state.fieldPath,
95
+ type: state.filterType,
96
+ value: state.filterValue
97
+ });
98
+ resetState();
99
+ };
100
+ if (state.kind === 'filter-value') {
101
+ const {
102
+ Filter
103
+ } = fieldsWithFilters[state.fieldPath].controller.filter;
104
+ const fieldLabel = list.fields[state.fieldPath].label;
105
+ const filterTypes = filtersByFieldThenType[state.fieldPath];
106
+ const typeLabel = filterTypes[state.filterType];
107
+ return /*#__PURE__*/jsxs(DialogTrigger, {
108
+ type: "popover",
109
+ mobileType: "tray",
110
+ defaultOpen: true,
111
+ onOpenChange: isOpen => !isOpen && resetState(),
112
+ children: [/*#__PURE__*/jsxs(ActionButton, {
113
+ children: [/*#__PURE__*/jsx(Text, {
114
+ children: "Filter"
115
+ }), /*#__PURE__*/jsx(Icon, {
116
+ src: chevronDownIcon
117
+ })]
118
+ }), /*#__PURE__*/jsxs(Dialog, {
119
+ children: [/*#__PURE__*/jsxs(Heading, {
120
+ children: ["Filter by ", fieldLabel.toLocaleLowerCase()]
121
+ }), /*#__PURE__*/jsx(Content, {
122
+ children: /*#__PURE__*/jsxs("form", {
123
+ onSubmit: onSubmit,
124
+ id: formId,
125
+ children: [/*#__PURE__*/jsx("button", {
126
+ type: "submit",
127
+ form: formId,
128
+ style: {
129
+ display: 'none'
130
+ }
131
+ }), /*#__PURE__*/jsxs(Grid, {
132
+ gap: "large",
133
+ rows: "auto minmax(0, 1fr)",
134
+ height: "100%",
135
+ children: [/*#__PURE__*/jsx(Picker, {
136
+ width: "100%",
137
+ "aria-label": "filter type",
138
+ isRequired: true,
139
+ items: Object.keys(filterTypes).map(filterType => ({
140
+ label: filterTypes[filterType],
141
+ value: filterType
142
+ })),
143
+ selectedKey: state.filterType,
144
+ onSelectionChange: key => {
145
+ if (key) {
146
+ setState({
147
+ kind: 'filter-value',
148
+ fieldPath: state.fieldPath,
149
+ filterValue: fieldsWithFilters[state.fieldPath].controller.filter.types[key].initialValue,
150
+ filterType: key
151
+ });
152
+ }
153
+ },
154
+ children: item => /*#__PURE__*/jsx(Item, {
155
+ children: item.label
156
+ }, item.value)
157
+ }), /*#__PURE__*/jsx(Filter, {
158
+ autoFocus: true,
159
+ context: "add",
160
+ forceValidation: forceValidation,
161
+ typeLabel: typeLabel,
162
+ type: state.filterType,
163
+ value: state.filterValue,
164
+ onChange: value => {
165
+ setState(state => ({
166
+ ...state,
167
+ filterValue: value
168
+ }));
169
+ }
170
+ })]
171
+ })]
172
+ })
173
+ }), /*#__PURE__*/jsxs(ButtonGroup, {
174
+ children: [/*#__PURE__*/jsx(Button, {
175
+ onPress: resetState,
176
+ children: "Cancel"
177
+ }), /*#__PURE__*/jsx(Button, {
178
+ prominence: "high",
179
+ type: "submit",
180
+ form: formId,
181
+ children: "Add"
182
+ })]
183
+ })]
184
+ })]
185
+ });
186
+ }
187
+ return /*#__PURE__*/jsx(Fragment, {
188
+ children: /*#__PURE__*/jsxs(MenuTrigger, {
189
+ children: [/*#__PURE__*/jsxs(ActionButton, {
190
+ ref: triggerRef,
191
+ isDisabled: isDisabled,
192
+ children: [/*#__PURE__*/jsx(Text, {
193
+ children: "Filter"
194
+ }), /*#__PURE__*/jsx(Icon, {
195
+ src: chevronDownIcon
196
+ })]
197
+ }), /*#__PURE__*/jsx(Menu, {
198
+ items: Object.keys(filtersByFieldThenType).map(fieldPath => ({
199
+ label: fieldsWithFilters[fieldPath].label,
200
+ value: fieldPath
201
+ })),
202
+ onAction: fieldPath => {
203
+ const filterType = Object.keys(filtersByFieldThenType[fieldPath])[0];
204
+ setState({
205
+ kind: 'filter-value',
206
+ fieldPath: fieldPath,
207
+ filterType,
208
+ filterValue: fieldsWithFilters[fieldPath].controller.filter.types[filterType].initialValue
209
+ });
210
+ },
211
+ children: item => /*#__PURE__*/jsx(Item, {
212
+ children: item.label
213
+ }, item.value)
214
+ })]
215
+ })
216
+ });
217
+ }
218
+
219
+ // TODO: broken if user uses the same filter twice
220
+ function useFilterFields(listKey) {
221
+ const list = useList(listKey);
222
+ const fieldsWithFilters = useMemo(() => {
223
+ const fieldsWithFilters = {};
224
+ for (const fieldPath in list.fields) {
225
+ const field = list.fields[fieldPath];
226
+ if (field.isFilterable && field.controller.filter) {
227
+ fieldsWithFilters[fieldPath] = field;
228
+ }
229
+ }
230
+ return fieldsWithFilters;
231
+ }, [list.fields]);
232
+ const filtersByFieldThenType = useMemo(() => {
233
+ const filtersByFieldThenType = {};
234
+ for (const fieldPath in fieldsWithFilters) {
235
+ const field = fieldsWithFilters[fieldPath];
236
+ const filters = {};
237
+ for (const filterType in field.controller.filter.types) {
238
+ filters[filterType] = field.controller.filter.types[filterType].label;
239
+ }
240
+ filtersByFieldThenType[fieldPath] = filters;
241
+ }
242
+ return filtersByFieldThenType;
243
+ }, [fieldsWithFilters]);
244
+ return {
245
+ fieldsWithFilters,
246
+ filtersByFieldThenType,
247
+ list
248
+ };
249
+ }
250
+
251
+ // TODO: move to @keystar/ui and implement properly
252
+ function Tag(props) {
253
+ const {
254
+ children,
255
+ onRemove
256
+ } = props;
257
+ const rootId = useId();
258
+ const textId = composeId(rootId, 'label');
259
+ const removeId = composeId(rootId, 'remove');
260
+ return /*#__PURE__*/jsxs(ActionButton, {
261
+ "aria-labelledby": [textId, removeId].join(' '),
262
+ onKeyDown: e => {
263
+ if (!onRemove) {
264
+ return;
265
+ }
266
+ if (e.key === 'Backspace' || e.key === 'Delete') {
267
+ onRemove();
268
+ }
269
+ },
270
+ UNSAFE_className: css({
271
+ borderRadius: tokenSchema.size.radius.full,
272
+ height: tokenSchema.size.element.small,
273
+ paddingInlineStart: tokenSchema.size.space.small,
274
+ paddingInlineEnd: 0
275
+ }),
276
+ children: [/*#__PURE__*/jsx(SlotProvider, {
277
+ slots: {
278
+ text: {
279
+ id: textId
280
+ }
281
+ },
282
+ children: children
283
+ }), onRemove && /*#__PURE__*/jsx("span", {
284
+ role: "button",
285
+ className: css({
286
+ alignItems: 'center',
287
+ borderRadius: '50%',
288
+ display: 'flex',
289
+ justifyContent: 'center',
290
+ height: tokenSchema.size.element.small,
291
+ width: tokenSchema.size.element.small,
292
+ ':hover': {
293
+ backgroundColor: tokenSchema.color.alias.backgroundHovered
294
+ },
295
+ ':active': {
296
+ backgroundColor: tokenSchema.color.alias.backgroundPressed
297
+ }
298
+ }),
299
+ onClick: e => {
300
+ e.preventDefault();
301
+ e.stopPropagation();
302
+ onRemove();
303
+ },
304
+ children: /*#__PURE__*/jsx(Icon, {
305
+ id: removeId,
306
+ src: xIcon,
307
+ "aria-label": "backspace to remove"
308
+ })
309
+ })]
310
+ });
311
+ }
312
+
313
+ function FilterTag({
314
+ filter,
315
+ field,
316
+ onChange,
317
+ onRemove
318
+ }) {
319
+ const Label = field.controller.filter.Label;
320
+ const tagElement = /*#__PURE__*/jsx(Tag, {
321
+ onRemove: onRemove,
322
+ children: /*#__PURE__*/jsxs(Text, {
323
+ children: [/*#__PURE__*/jsxs("span", {
324
+ children: [field.label, " "]
325
+ }), /*#__PURE__*/jsx(Label, {
326
+ label: field.controller.filter.types[filter.type].label,
327
+ type: filter.type,
328
+ value: filter.value
329
+ })]
330
+ })
331
+ });
332
+
333
+ // TODO: Special "empty" types need to be documented somewhere. Filters that
334
+ // have no editable value, basically `null` or `!null`. Which offers:
335
+ // * better DX — we can avoid weird nullable types and UIs that don't make sense
336
+ // * better UX — users don't have to jump through mental hoops, like "is not exactly" + submit empty field
337
+ if (filter.type === 'empty' || filter.type === 'not_empty') return tagElement;
338
+ return /*#__PURE__*/jsxs(DialogTrigger, {
339
+ type: "popover",
340
+ mobileType: "tray",
341
+ children: [tagElement, onDismiss => /*#__PURE__*/jsx(FilterEdit, {
342
+ onChange: onChange,
343
+ onDismiss: onDismiss,
344
+ field: field,
345
+ filter: filter
346
+ })]
347
+ });
348
+ }
349
+ function FilterEdit({
350
+ filter,
351
+ field,
352
+ onChange: onAdd,
353
+ onDismiss
354
+ }) {
355
+ var _field$controller$fil;
356
+ const formId = useId();
357
+ const [value, setValue] = useState(filter.value);
358
+ const onSubmit = event => {
359
+ if (event.target !== event.currentTarget) return;
360
+ event.preventDefault();
361
+ onAdd({
362
+ ...filter,
363
+ value
364
+ });
365
+ onDismiss();
366
+ };
367
+ const Filter = field.controller.filter.Filter;
368
+ const filterTypeLabel = (_field$controller$fil = field.controller.filter) === null || _field$controller$fil === void 0 ? void 0 : _field$controller$fil.types[filter.type].label;
369
+ return /*#__PURE__*/jsxs(Dialog, {
370
+ children: [/*#__PURE__*/jsx(Heading, {
371
+ children: field.label
372
+ }), /*#__PURE__*/jsx(Content, {
373
+ children: /*#__PURE__*/jsx("form", {
374
+ onSubmit: onSubmit,
375
+ id: formId,
376
+ children: /*#__PURE__*/jsx(Filter, {
377
+ autoFocus: true,
378
+ context: "edit",
379
+ typeLabel: filterTypeLabel,
380
+ onChange: setValue,
381
+ type: filter.type,
382
+ value: value
383
+ })
384
+ })
385
+ }), /*#__PURE__*/jsxs(ButtonGroup, {
386
+ children: [/*#__PURE__*/jsx(Button, {
387
+ onPress: onDismiss,
388
+ children: "Cancel"
389
+ }), /*#__PURE__*/jsx(Button, {
390
+ type: "submit",
391
+ prominence: "high",
392
+ form: formId,
393
+ children: "Save"
394
+ })]
395
+ })]
396
+ });
397
+ }
398
+ function getFilters(list, query) {
399
+ const param_ = query.filter;
400
+ const params = Array.isArray(param_) ? param_ : typeof param_ === 'string' ? [param_] : [];
401
+ if (!params.length) {
402
+ if (!list.initialFilter) return [];
403
+ const filters = [];
404
+ for (const [fieldKey, filter] of Object.entries(list.initialFilter)) {
405
+ const {
406
+ controller
407
+ } = list.fields[fieldKey];
408
+ for (const f of (_controller$filter$pa = (_controller$filter = controller.filter) === null || _controller$filter === void 0 ? void 0 : _controller$filter.parseGraphQL(filter)) !== null && _controller$filter$pa !== void 0 ? _controller$filter$pa : []) {
409
+ var _controller$filter$pa, _controller$filter;
410
+ filters.push({
411
+ field: fieldKey,
412
+ ...f
413
+ });
414
+ }
415
+ }
416
+ return filters;
417
+ }
418
+ const filters = [];
419
+ for (const [fieldPath, field] of Object.entries(list.fields)) {
420
+ if (!field.isFilterable) continue;
421
+ if (!field.controller.filter) continue;
422
+ for (const filterType in field.controller.filter.types) {
423
+ const prefix = `${fieldPath}_${filterType}`;
424
+ for (const queryFilter of params) {
425
+ if (!queryFilter.startsWith(prefix)) continue;
426
+ if (queryFilter === prefix) {
427
+ filters.push({
428
+ type: filterType,
429
+ field: fieldPath,
430
+ value: null
431
+ });
432
+ continue;
433
+ }
434
+ const queryValue = queryFilter.slice(prefix.length + 1);
435
+ try {
436
+ const value = JSON.parse(queryValue);
437
+ filters.push({
438
+ type: filterType,
439
+ field: fieldPath,
440
+ value
441
+ });
442
+ } catch (e) {
443
+ console.error('Error parsing filter', queryFilter);
444
+ }
445
+ }
446
+ }
447
+ }
448
+ return filters;
449
+ }
450
+ function getSort(list, query) {
451
+ const param = typeof query.sortBy === 'string' ? query.sortBy : null;
452
+ if (param === '') return null;
453
+ if (!param) {
454
+ if (!list.initialSort) return null;
455
+ return {
456
+ column: list.initialSort.field,
457
+ direction: list.initialSort.direction === 'ASC' ? 'ascending' : 'descending'
458
+ };
459
+ }
460
+ const fieldKey = param.startsWith('-') ? param.slice(1) : param;
461
+ const direction = param.startsWith('-') ? 'descending' : 'ascending';
462
+ const field = list.fields[fieldKey];
463
+ if (!field) return null;
464
+ if (!field.isOrderable) return null;
465
+ return {
466
+ column: fieldKey,
467
+ direction
468
+ };
469
+ }
470
+ function getCurrentPage(_, query) {
471
+ const currentPage = Number(query.page);
472
+ if (Number.isNaN(currentPage) || currentPage < 1) return 1;
473
+ return currentPage;
474
+ }
475
+ function getPageSize(list, query) {
476
+ const pageSize = Number(query.pageSize);
477
+ if (Number.isNaN(pageSize) || pageSize < 1) return list.pageSize;
478
+ return snapValueToClosest(pageSize);
479
+ }
480
+ function getColumns(list, query) {
481
+ const param_ = query.column;
482
+ const params = Array.isArray(param_) ? param_ : typeof param_ === 'string' ? [param_] : [];
483
+ if (!params.length) return list.initialColumns;
484
+ return params;
485
+ }
486
+ const getListPage = props => () => /*#__PURE__*/jsx(ListPage, {
487
+ ...props
488
+ });
489
+ function ListPage({
490
+ listKey
491
+ }) {
492
+ var _list$hideCreate, _data$items$map, _data$items, _data$items4;
493
+ const localStorageListKey = `nixxie.list.${listKey}.list.page.info`;
494
+ const list = useList(listKey);
495
+ const {
496
+ query,
497
+ replace: routerReplace,
498
+ isReady
499
+ } = useRouter();
500
+ const [loaded, setLoaded] = useState(false);
501
+ const [sort, setSort] = useState(() => getSort(list, {}));
502
+ const [columns, setColumns] = useState(list.initialColumns);
503
+ const [filters, setFilters] = useState(() => getFilters(list, {}));
504
+ const [currentPage, setCurrentPage] = useState(1);
505
+ const [pageSize, setPageSize] = useState(list.pageSize);
506
+ const [searchString, setSearchString] = useState('');
507
+ const [selectedItems, setSelectedItems] = useState(() => new Set([]));
508
+ const [activeAction, setActiveAction] = useState(null);
509
+ const [actionResult, setActionResult] = useState(null);
510
+ const dirty = useMemo(() => {
511
+ const defaultFilters = getFilters(list, {});
512
+ const defaultSort = getSort(list, {});
513
+ return !!searchString || !isDeepEqual(filters, defaultFilters) || !isDeepEqual(sort, defaultSort) || !isDeepEqual(columns, list.initialColumns);
514
+ }, [searchString, filters, sort, columns, list.initialColumns]);
515
+ useEffect(() => {
516
+ if (!isReady) return;
517
+ if (loaded) return;
518
+ let localStorageQuery;
519
+ try {
520
+ var _localStorage$getItem;
521
+ localStorageQuery = JSON.parse((_localStorage$getItem = localStorage.getItem(localStorageListKey)) !== null && _localStorage$getItem !== void 0 ? _localStorage$getItem : '{}');
522
+ } catch {}
523
+ setSort(getSort(list, {
524
+ ...localStorageQuery,
525
+ ...query
526
+ }));
527
+ setColumns(getColumns(list, {
528
+ ...localStorageQuery,
529
+ ...query
530
+ }));
531
+ setFilters(getFilters(list, {
532
+ ...localStorageQuery,
533
+ ...query
534
+ }));
535
+ setCurrentPage(getCurrentPage(list, {
536
+ ...localStorageQuery,
537
+ ...query
538
+ }));
539
+ setPageSize(getPageSize(list, {
540
+ ...localStorageQuery,
541
+ ...query
542
+ }));
543
+ setSearchString(typeof query.search === 'string' ? query.search : '');
544
+ setLoaded(true);
545
+ }, [list, isReady]);
546
+ useEffect(() => {
547
+ if (!isReady) return;
548
+ if (!loaded) return; // TODO: stop this race condition properly
549
+ const updatedQuery = {
550
+ ...(columns.length ? {
551
+ column: columns
552
+ } : {}),
553
+ ...(sort ? {
554
+ sortBy: sort.direction === 'ascending' ? sort.column : `-${sort.column}`
555
+ } : {}),
556
+ ...(filters.length ? {
557
+ filter: function () {
558
+ const result = [];
559
+ for (const filter of filters) {
560
+ if (filter.type === 'not_empty' || filter.type === 'empty') {
561
+ result.push(`${filter.field}_${filter.type}`);
562
+ continue;
563
+ }
564
+ result.push(`${filter.field}_${filter.type}_${JSON.stringify(filter.value)}`);
565
+ }
566
+ return result;
567
+ }()
568
+ } : {}),
569
+ ...(currentPage > 1 ? {
570
+ page: currentPage
571
+ } : {}),
572
+ ...(pageSize !== list.pageSize ? {
573
+ pageSize
574
+ } : {}),
575
+ ...(searchString ? {
576
+ search: searchString
577
+ } : {})
578
+ };
579
+ localStorage.setItem(localStorageListKey, JSON.stringify(updatedQuery));
580
+ routerReplace({
581
+ query: updatedQuery
582
+ });
583
+ }, [columns, sort, filters, currentPage, pageSize, searchString, list, loaded]);
584
+ const allowCreate = !((_list$hideCreate = list.hideCreate) !== null && _list$hideCreate !== void 0 ? _list$hideCreate : true);
585
+ const isConstrained = Boolean(filters.length || query.search);
586
+ const readableFields = Object.values(list.fields).map(f => ({
587
+ id: f.key,
588
+ value: f.key,
589
+ label: f.label,
590
+ isDisabled: f.listView.fieldMode === 'read'
591
+ }));
592
+ const where = useMemo(() => filters.map(filter => {
593
+ return list.fields[filter.field].controller.filter.graphql({
594
+ type: filter.type,
595
+ value: filter.value
596
+ });
597
+ }), [list, filters]);
598
+ const actionsAvailable = useMemo(() => list.actions.filter(x => isActionAvailable(x, x.listView)), [list.actions]);
599
+ const actionConditionalFilterFieldKeys = useMemo(() => {
600
+ const fieldKeys = new Set();
601
+ for (const action of actionsAvailable) {
602
+ for (const fieldKey of getConditionalFilterFieldKeys(action.listView.actionMode)) {
603
+ fieldKeys.add(fieldKey);
604
+ }
605
+ }
606
+ return fieldKeys;
607
+ }, [actionsAvailable]);
608
+ const search = useSearchFilter(searchString, list, list.initialSearchFields);
609
+ const {
610
+ data,
611
+ error,
612
+ refetch,
613
+ loading
614
+ } = useQuery(useMemo(() => {
615
+ const fieldKeys = new Set(columns); // only the shown columns
616
+
617
+ // and any fields needed by the action filters
618
+ for (const fieldKey of actionConditionalFilterFieldKeys) {
619
+ fieldKeys.add(fieldKey);
620
+ }
621
+ for (const action of actionsAvailable) {
622
+ for (const arg of action.graphql.arguments) {
623
+ if (!arg.source) continue;
624
+ fieldKeys.add(arg.source.itemField);
625
+ }
626
+ }
627
+ const fieldsToQuery = [...fieldKeys].filter(fieldKey => fieldKey !== 'id') // id is always included
628
+ .map(fieldKey => {
629
+ var _list$fields$fieldKey;
630
+ return (_list$fields$fieldKey = list.fields[fieldKey]) === null || _list$fields$fieldKey === void 0 ? void 0 : _list$fields$fieldKey.controller.graphqlSelection;
631
+ }).filter(Boolean).join('\n');
632
+
633
+ // TODO: less interpolation
634
+ return gql`
635
+ query (
636
+ $where: ${list.graphql.names.whereInputName},
637
+ $take: Int!,
638
+ $skip: Int!,
639
+ $orderBy: [${list.graphql.names.listOrderName}!]
640
+ ) {
641
+ items: ${list.graphql.names.listQueryName}(
642
+ where: $where,
643
+ take: $take,
644
+ skip: $skip,
645
+ orderBy: $orderBy
646
+ ) {
647
+ id
648
+ ${fieldsToQuery}
649
+ }
650
+ count: ${list.graphql.names.listQueryCountName}(where: $where)
651
+ }
652
+ `;
653
+ }, [list, list.fields, columns, actionsAvailable, actionConditionalFilterFieldKeys]), {
654
+ fetchPolicy: 'cache-and-network',
655
+ errorPolicy: 'all',
656
+ variables: {
657
+ where: {
658
+ ...(where.length ? {
659
+ AND: where
660
+ } : {}),
661
+ ...(search.length ? {
662
+ OR: search
663
+ } : {})
664
+ },
665
+ take: pageSize,
666
+ skip: (currentPage - 1) * pageSize,
667
+ orderBy: sort ? [{
668
+ [sort.column]: sort.direction === 'ascending' ? 'asc' : 'desc'
669
+ }] : undefined
670
+ }
671
+ });
672
+ useEffect(() => {
673
+ if (typeof (data === null || data === void 0 ? void 0 : data.count) !== 'number') return;
674
+ const lastPage = Math.max(Math.ceil(data.count / pageSize), 1);
675
+ if (currentPage > lastPage) {
676
+ setCurrentPage(lastPage);
677
+ }
678
+ }, [data]);
679
+ const selectedItemIds = (selectedItems === 'all' ? (_data$items$map = data === null || data === void 0 || (_data$items = data.items) === null || _data$items === void 0 ? void 0 : _data$items.map(item => item.id)) !== null && _data$items$map !== void 0 ? _data$items$map : [] : Array.from(selectedItems)).map(String);
680
+ const isEmpty = Boolean((data === null || data === void 0 ? void 0 : data.count) === 0 && !isConstrained);
681
+ const headers = columns.map(column => {
682
+ var _data$items2;
683
+ const field = list.fields[column];
684
+ if (!field) return;
685
+ return {
686
+ id: field.key,
687
+ label: field.label,
688
+ allowsSorting: !isConstrained && !(data !== null && data !== void 0 && (_data$items2 = data.items) !== null && _data$items2 !== void 0 && _data$items2.length) ? false : field.isOrderable
689
+ };
690
+ }).filter(x => Boolean(x));
691
+ function onAddFilter(newFilter) {
692
+ setFilters(prevFilters => [...prevFilters, newFilter]);
693
+ }
694
+ function resetToDefaults() {
695
+ const defaultFilters = getFilters(list, {});
696
+ const defaultSort = getSort(list, {});
697
+ setSearchString('');
698
+ setColumns(list.initialColumns);
699
+ setFilters(defaultFilters);
700
+ setSort(defaultSort);
701
+ }
702
+ const actionsList = [...actionsAvailable, ...(list.hideDelete ? [] : [{
703
+ key: 'delete',
704
+ label: 'Delete',
705
+ icon: 'trash2Icon',
706
+ graphql: {
707
+ arguments: [],
708
+ names: {
709
+ one: list.graphql.names.deleteMutationName,
710
+ many: list.graphql.names.deleteManyMutationName
711
+ }
712
+ },
713
+ messages: {
714
+ promptTitle: 'Delete {singular}?',
715
+ promptTitleMany: 'Delete {count} {singular|plural}?',
716
+ prompt: 'Are you sure you want to delete {singular}? This action cannot be undone.',
717
+ promptMany: 'Are you sure you want to delete {count} {singular|plural}? This action cannot be undone.',
718
+ promptConfirmLabel: 'Yes, delete',
719
+ promptConfirmLabelMany: 'Yes, delete',
720
+ success: 'Deleted {singular}.',
721
+ successMany: 'Deleted {countSuccess} {singular|plural}.',
722
+ fail: 'Unable to delete {singular}.',
723
+ failMany: 'Unable to delete {countFail} {singular|plural}.'
724
+ },
725
+ itemView: null,
726
+ // unusud
727
+ listView: {
728
+ actionMode: list.hideDelete ? 'hidden' : 'enabled'
729
+ }
730
+ }])];
731
+ const selectionMode = actionsList.length > 0 ? 'multiple' : 'none';
732
+ const selectedRows = useMemo(() => {
733
+ var _data$items$filter, _data$items3;
734
+ return (_data$items$filter = data === null || data === void 0 || (_data$items3 = data.items) === null || _data$items3 === void 0 ? void 0 : _data$items3.filter(item => selectedItemIds.includes(String(item.id)))) !== null && _data$items$filter !== void 0 ? _data$items$filter : [];
735
+ }, [data === null || data === void 0 ? void 0 : data.items, selectedItemIds]);
736
+ const actionConditionalFilterFields = useMemo(() => pick(list.fields, actionConditionalFilterFieldKeys), [actionConditionalFilterFieldKeys, list.fields]);
737
+ const serializedSelectedRows = useMemo(() => selectedRows.map(row => {
738
+ const value = deserializeItemToValue(actionConditionalFilterFields, row);
739
+ return serializeItemForConditionalFilters(actionConditionalFilterFields, value);
740
+ }), [actionConditionalFilterFields, selectedRows]);
741
+ const {
742
+ actions,
743
+ disabledKeys: disabledActionKeys
744
+ } = useMemo(() => {
745
+ const disabledKeys = [];
746
+ const actions = [];
747
+ for (const action of actionsList) {
748
+ let actionMode;
749
+ for (const serializedValue of serializedSelectedRows) {
750
+ const mode = resolveActionMode(action.listView.actionMode, serializedValue);
751
+ if (mode === 'hidden') {
752
+ actionMode = 'hidden';
753
+ break;
754
+ }
755
+ if (mode === 'disabled') actionMode = 'disabled';
756
+ }
757
+ if (actionMode === 'hidden') continue;
758
+ if (actionMode === 'disabled') disabledKeys.push(action.key);
759
+ actions.push(action);
760
+ }
761
+ return {
762
+ actions,
763
+ disabledKeys
764
+ };
765
+ }, [actionsList, serializedSelectedRows]);
766
+ return /*#__PURE__*/jsx(PageContainer, {
767
+ header: /*#__PURE__*/jsx(ListPageHeader, {
768
+ listKey: listKey,
769
+ showCreate: allowCreate
770
+ }),
771
+ title: list.label,
772
+ children: /*#__PURE__*/jsxs(VStack, {
773
+ flex: true,
774
+ gap: "large",
775
+ paddingX: "xlarge",
776
+ paddingY: "xlarge",
777
+ minHeight: 0,
778
+ minWidth: 0,
779
+ children: [/*#__PURE__*/jsxs(HStack, {
780
+ gap: "regular",
781
+ alignItems: "center",
782
+ children: [/*#__PURE__*/jsx(SearchField, {
783
+ "aria-label": "Search",
784
+ isDisabled: isEmpty,
785
+ onClear: () => setSearchString(''),
786
+ onChange: v => setSearchString(v),
787
+ placeholder: "Search\u2026",
788
+ value: searchString,
789
+ width: "alias.singleLineWidth",
790
+ flexGrow: {
791
+ mobile: 1,
792
+ tablet: 0
793
+ }
794
+ }), /*#__PURE__*/jsx(FilterAdd, {
795
+ listKey: listKey,
796
+ onAdd: onAddFilter,
797
+ isDisabled: isEmpty
798
+ }), /*#__PURE__*/jsxs(MenuTrigger, {
799
+ children: [/*#__PURE__*/jsxs(ActionButton, {
800
+ isDisabled: isEmpty,
801
+ children: [/*#__PURE__*/jsx(Text, {
802
+ children: "Columns"
803
+ }), /*#__PURE__*/jsx(Icon, {
804
+ src: chevronDownIcon
805
+ })]
806
+ }), /*#__PURE__*/jsx(Menu, {
807
+ items: readableFields,
808
+ disallowEmptySelection: true,
809
+ onSelectionChange: selection => {
810
+ if (selection === 'all') {
811
+ setColumns(readableFields.map(field => field.id));
812
+ } else {
813
+ setColumns(readableFields.filter(f => selection.has(f.id)).map(f => f.id));
814
+ }
815
+ },
816
+ selectionMode: "multiple",
817
+ selectedKeys: columns,
818
+ children: item => /*#__PURE__*/jsx(Item$1, {
819
+ children: item.label
820
+ }, item.value)
821
+ })]
822
+ }), dirty ? /*#__PURE__*/jsxs(TooltipTrigger, {
823
+ children: [/*#__PURE__*/jsx(ActionButton, {
824
+ "aria-label": "reset",
825
+ onPress: resetToDefaults,
826
+ prominence: "low",
827
+ children: /*#__PURE__*/jsx(Icon, {
828
+ src: undo2Icon
829
+ })
830
+ }), /*#__PURE__*/jsx(Tooltip, {
831
+ children: "Reset to defaults"
832
+ })]
833
+ }) : null, isReady && loading && /*#__PURE__*/jsx(ProgressCircle, {
834
+ "aria-label": "Loading\u2026",
835
+ size: "small",
836
+ isIndeterminate: true
837
+ })]
838
+ }), filters.length ? /*#__PURE__*/jsx(Flex, {
839
+ gap: "small",
840
+ wrap: true,
841
+ children: filters.map((filter, i) => {
842
+ const field = list.fields[filter.field];
843
+ function onRemove() {
844
+ setFilters(prevFilters => prevFilters.filter(f => f !== filter));
845
+ }
846
+ function onChange(updatedFilter) {
847
+ setFilters(prevFilters => [...prevFilters.filter(f => f !== filter), updatedFilter]);
848
+ }
849
+ return /*#__PURE__*/jsx(FilterTag, {
850
+ field: field,
851
+ filter: filter,
852
+ onChange: onChange,
853
+ onRemove: onRemove
854
+ }, i);
855
+ })
856
+ }) : null, /*#__PURE__*/jsx(GraphQLErrorNotice, {
857
+ errors: [error]
858
+ }), /*#__PURE__*/jsxs(ActionBarContainer, {
859
+ flex: true,
860
+ minHeight: "scale.3000",
861
+ children: [/*#__PURE__*/jsxs(TableView, {
862
+ "aria-labelledby": LIST_PAGE_TITLE_ID,
863
+ selectionMode: selectionMode,
864
+ onSortChange: setSort,
865
+ sortDescriptor: sort !== null && sort !== void 0 ? sort : undefined,
866
+ density: "spacious",
867
+ overflowMode: "truncate",
868
+ onSelectionChange: setSelectedItems,
869
+ selectedKeys: selectedItems,
870
+ renderEmptyState: () => loading ? /*#__PURE__*/jsx(ProgressCircle, {
871
+ "aria-label": "Preparing items",
872
+ isIndeterminate: true
873
+ }) : isConstrained ? /*#__PURE__*/jsx(EmptyState, {
874
+ icon: searchXIcon,
875
+ title: "No results",
876
+ message: "No items found. Try adjusting your search or filters."
877
+ }) : /*#__PURE__*/jsx(EmptyState, {
878
+ icon: textSelectIcon,
879
+ title: "Empty list",
880
+ message: "Add the first item to see it here."
881
+ }),
882
+ flex: true,
883
+ UNSAFE_style: {
884
+ opacity: loading && !!data ? 0.5 : undefined
885
+ },
886
+ children: [/*#__PURE__*/jsx(TableHeader, {
887
+ columns: headers,
888
+ children: ({
889
+ label,
890
+ id,
891
+ ...options
892
+ }) => /*#__PURE__*/jsx(Column, {
893
+ isRowHeader: true,
894
+ ...options,
895
+ children: label
896
+ }, id)
897
+ }), /*#__PURE__*/jsx(TableBody, {
898
+ items: (_data$items4 = data === null || data === void 0 ? void 0 : data.items) !== null && _data$items4 !== void 0 ? _data$items4 : [],
899
+ children: row => {
900
+ return /*#__PURE__*/jsx(Row, {
901
+ href: `/${list.path}/${row === null || row === void 0 ? void 0 : row.id}`,
902
+ children: key => {
903
+ const field = list.fields[key];
904
+ const value = row[key];
905
+ const CellContent = field.views.Cell;
906
+ return /*#__PURE__*/jsx(Cell, {
907
+ children: CellContent ? /*#__PURE__*/jsx(CellContent, {
908
+ value: value,
909
+ field: field.controller,
910
+ item: row
911
+ }) : /*#__PURE__*/jsx(Text, {
912
+ children: value === null || value === void 0 ? void 0 : value.toString()
913
+ })
914
+ });
915
+ }
916
+ });
917
+ }
918
+ })]
919
+ }), /*#__PURE__*/jsx(ActionBar, {
920
+ selectedItemCount: selectedItemIds.length,
921
+ onClearSelection: () => setSelectedItems(new Set()),
922
+ UNSAFE_className: css({
923
+ // TODO: update in @keystar/ui package
924
+ // make `tokenSchema.size.shadow.regular` token "0 1px 4px"
925
+ 'div:has([data-focus-scope-start])': {
926
+ backgroundColor: tokenSchema.color.background.canvas,
927
+ border: `${tokenSchema.size.border.regular} solid ${tokenSchema.color.border.emphasis}`,
928
+ borderRadius: tokenSchema.size.radius.regular,
929
+ boxShadow: `0 1px 4px ${tokenSchema.color.shadow.regular}`
930
+ }
931
+ }),
932
+ disabledKeys: disabledActionKeys,
933
+ onAction: setActiveAction,
934
+ children: [...function* () {
935
+ for (const action of actions) {
936
+ const iconComponent = action.icon ? allIcons[action.icon] : null;
937
+ yield /*#__PURE__*/jsxs(Item$1, {
938
+ textValue: action.label,
939
+ children: [iconComponent ? /*#__PURE__*/jsx(Icon, {
940
+ src: iconComponent
941
+ }) : null, /*#__PURE__*/jsx(Text, {
942
+ children: action.label
943
+ })]
944
+ }, action.key);
945
+ }
946
+ }()]
947
+ })]
948
+ }), !!(data !== null && data !== void 0 && data.count) && /*#__PURE__*/jsx(PaginationControls, {
949
+ singular: list.singular,
950
+ plural: list.plural,
951
+ currentPage: currentPage,
952
+ pageSize: pageSize,
953
+ total: data.count,
954
+ onChangePage: page => setCurrentPage(page),
955
+ onChangePageSize: pageSize => setPageSize(pageSize),
956
+ defaultPageSize: list.pageSize
957
+ }), /*#__PURE__*/jsx(DialogContainer, {
958
+ onDismiss: () => {
959
+ setActiveAction(null);
960
+ },
961
+ children: actions.filter(action => action.key === activeAction).map(action => {
962
+ var _data$items5;
963
+ return /*#__PURE__*/jsx(ActionItemsDialog, {
964
+ itemIds: selectedItemIds,
965
+ items: (_data$items5 = data === null || data === void 0 ? void 0 : data.items) !== null && _data$items5 !== void 0 ? _data$items5 : [],
966
+ action: action,
967
+ list: list,
968
+ onSuccess: remaining => {
969
+ refetch();
970
+ setSelectedItems(remaining);
971
+ },
972
+ onErrors: setActionResult
973
+ });
974
+ }).pop()
975
+ }), /*#__PURE__*/jsx(DialogContainer, {
976
+ onDismiss: () => setActionResult(null),
977
+ isDismissable: true,
978
+ children: actionResult ? /*#__PURE__*/jsxs(Dialog, {
979
+ children: [/*#__PURE__*/jsxs(Heading, {
980
+ children: ["Error details for ", actionResult.action.label, " action"]
981
+ }), /*#__PURE__*/jsx(Content, {
982
+ children: /*#__PURE__*/jsx(VStack, {
983
+ gap: "large",
984
+ children: [...function* () {
985
+ const {
986
+ action,
987
+ errors: actionErrors
988
+ } = actionResult;
989
+ for (const [itemId, itemActionErrors] of Object.entries(actionErrors)) {
990
+ var _data$items$find, _data$items6, _ref;
991
+ const item = (_data$items$find = data === null || data === void 0 || (_data$items6 = data.items) === null || _data$items6 === void 0 ? void 0 : _data$items6.find(i => i.id === itemId)) !== null && _data$items$find !== void 0 ? _data$items$find : null;
992
+ const itemLabel = (_ref = item === null || item === void 0 ? void 0 : item[list.labelField]) !== null && _ref !== void 0 ? _ref : itemId;
993
+ const href = `/${list.path}/${itemId}`;
994
+ for (const error of itemActionErrors) {
995
+ yield /*#__PURE__*/jsx(VStack, {
996
+ gap: "regular",
997
+ children: /*#__PURE__*/jsxs(Notice, {
998
+ tone: "critical",
999
+ children: [/*#__PURE__*/jsxs(Content, {
1000
+ children: [/*#__PURE__*/jsxs(Text, {
1001
+ children: ["You might try running the action again from", ' ', /*#__PURE__*/jsxs(TextLink, {
1002
+ href: href,
1003
+ children: ["the ", list.singular.toLowerCase(), "."]
1004
+ })]
1005
+ }), /*#__PURE__*/jsx(Box, {
1006
+ elementType: "pre",
1007
+ backgroundColor: "critical",
1008
+ borderRadius: "regular",
1009
+ maxHeight: "100%",
1010
+ overflow: "auto",
1011
+ children: /*#__PURE__*/jsx(Text, {
1012
+ color: "critical",
1013
+ UNSAFE_className: css({
1014
+ fontFamily: tokenSchema.typography.fontFamily.code
1015
+ }),
1016
+ children: error.message
1017
+ })
1018
+ })]
1019
+ }), /*#__PURE__*/jsx("div", {
1020
+ children: /*#__PURE__*/jsx(Heading, {
1021
+ children: replace(action.messages.fail, list, {
1022
+ ...action,
1023
+ itemLabel
1024
+ }, false)
1025
+ })
1026
+ })]
1027
+ })
1028
+ }, itemId);
1029
+ }
1030
+ }
1031
+ }()]
1032
+ })
1033
+ })]
1034
+ }) : null
1035
+ })]
1036
+ })
1037
+ });
1038
+ }
1039
+ const LIST_PAGE_TITLE_ID = 'nixxie-list-page-title';
1040
+ function ListPageHeader({
1041
+ listKey,
1042
+ showCreate
1043
+ }) {
1044
+ const list = useList(listKey);
1045
+ return /*#__PURE__*/jsxs(Fragment, {
1046
+ children: [/*#__PURE__*/jsx(Heading, {
1047
+ id: LIST_PAGE_TITLE_ID,
1048
+ elementType: "h1",
1049
+ size: "small",
1050
+ children: list.label
1051
+ }), showCreate && /*#__PURE__*/jsx(CreateButtonLink, {
1052
+ list: list,
1053
+ children: `New ${list.singular.toLocaleLowerCase()}`
1054
+ })]
1055
+ });
1056
+ }
1057
+ function replace(s, list, args, many) {
1058
+ var _args$itemLabel;
1059
+ s = s.replaceAll('{Label}', args.label);
1060
+ s = s.replaceAll('{label}', args.label.toLowerCase());
1061
+ if (s.includes('{singular|plural}')) s = s.replaceAll('{singular|plural}', many ? '{plural}' : '{singular}');
1062
+ if (s.includes('{Singular}')) s = s.replaceAll('{Singular}', list.singular);
1063
+ if (s.includes('{Plural}')) s = s.replaceAll('{Plural}', list.plural);
1064
+ if (s.includes('{singular}')) s = s.replaceAll('{singular}', list.singular.toLowerCase());
1065
+ if (s.includes('{plural}')) s = s.replaceAll('{plural}', list.plural.toLowerCase());
1066
+ if ('count' in args) s = s.replaceAll('{count}', String(args.count));
1067
+ if ('countFail' in args) s = s.replaceAll('{countFail}', String(args.countFail));
1068
+ if ('countSuccess' in args) s = s.replaceAll('{countSuccess}', String(args.countSuccess));
1069
+ if ('itemLabel' in args) s = s.replaceAll('{itemLabel}', (_args$itemLabel = args.itemLabel) !== null && _args$itemLabel !== void 0 ? _args$itemLabel : '');
1070
+ return s;
1071
+ }
1072
+ function ActionItemsDialog({
1073
+ list,
1074
+ itemIds,
1075
+ items,
1076
+ onSuccess,
1077
+ onErrors,
1078
+ action
1079
+ }) {
1080
+ const actionMutation = action.key === 'delete' ? gql`mutation($where: [${list.graphql.names.whereUniqueInputName}!]!) {
1081
+ results: ${action.graphql.names.many}(where: $where) {
1082
+ id
1083
+ }
1084
+ }` : gql`mutation($data: [${action.graphql.names.one[0].toUpperCase()}${action.graphql.names.one.slice(1)}Args!]!) {
1085
+ results: ${action.graphql.names.many}(data: $data) {
1086
+ id
1087
+ }
1088
+ }`;
1089
+ const [actionOnItems] = useMutation(actionMutation, {
1090
+ variables: action.key === 'delete' ? {
1091
+ where: itemIds.map(id => ({
1092
+ id
1093
+ }))
1094
+ } : {
1095
+ data: itemIds.flatMap(id => {
1096
+ const row = items.find(item => String(item.id) === id);
1097
+ if (!row) {
1098
+ return [];
1099
+ }
1100
+ const deserialized = deserializeItemToValue(list.fields, row);
1101
+ const args = getActionArguments(list, action, deserialized);
1102
+ return {
1103
+ where: {
1104
+ id
1105
+ },
1106
+ ...args
1107
+ };
1108
+ })
1109
+ },
1110
+ errorPolicy: 'all'
1111
+ });
1112
+ const {
1113
+ messages: m
1114
+ } = action;
1115
+ async function onTryAction() {
1116
+ try {
1117
+ const {
1118
+ error
1119
+ } = await actionOnItems();
1120
+ const failed = new Set();
1121
+ const actionErrors = {};
1122
+ let countFail = 0;
1123
+ if (CombinedGraphQLErrors.is(error)) {
1124
+ countFail = error.errors.length;
1125
+ for (const err of (_error$errors = error.errors) !== null && _error$errors !== void 0 ? _error$errors : []) {
1126
+ var _error$errors, _err$path, _actionErrors$itemId;
1127
+ const i = (_err$path = err.path) === null || _err$path === void 0 ? void 0 : _err$path[1];
1128
+ if (typeof i !== 'number') continue;
1129
+ const itemId = itemIds[i];
1130
+ failed.add(itemId);
1131
+ (_actionErrors$itemId = actionErrors[itemId]) !== null && _actionErrors$itemId !== void 0 ? _actionErrors$itemId : actionErrors[itemId] = [];
1132
+ actionErrors[itemId].push(err);
1133
+ }
1134
+ }
1135
+ const countSuccess = itemIds.length - countFail;
1136
+ if (countSuccess) {
1137
+ toastQueue.neutral(replace(m.successMany, list, {
1138
+ ...action,
1139
+ count: itemIds.length,
1140
+ countFail,
1141
+ countSuccess
1142
+ }, countSuccess > 1), {
1143
+ timeout: 5000
1144
+ });
1145
+ }
1146
+ if (countFail) {
1147
+ toastQueue.critical(replace(m.failMany, list, {
1148
+ ...action,
1149
+ count: itemIds.length,
1150
+ countFail,
1151
+ countSuccess
1152
+ }, countFail > 1), {
1153
+ actionLabel: 'Details',
1154
+ onAction: () => onErrors({
1155
+ action,
1156
+ errors: actionErrors
1157
+ }),
1158
+ shouldCloseOnAction: true
1159
+ });
1160
+ }
1161
+ return onSuccess(failed);
1162
+ } catch (error) {
1163
+ console.error(error);
1164
+ }
1165
+ }
1166
+ return /*#__PURE__*/jsx(AlertDialog, {
1167
+ tone: action.key === 'delete' ? 'critical' : 'neutral',
1168
+ title: replace(m.promptTitleMany, list, {
1169
+ ...action,
1170
+ count: itemIds.length
1171
+ }, itemIds.length > 1),
1172
+ cancelLabel: "Cancel",
1173
+ primaryActionLabel: replace(m.promptConfirmLabelMany, list, {
1174
+ ...action,
1175
+ count: itemIds.length
1176
+ }, itemIds.length > 1),
1177
+ onPrimaryAction: onTryAction,
1178
+ children: /*#__PURE__*/jsx(Text, {
1179
+ children: replace(m.promptMany, list, {
1180
+ ...action,
1181
+ count: itemIds.length
1182
+ }, itemIds.length > 1)
1183
+ })
1184
+ });
1185
+ }
1186
+
1187
+ export { getListPage };