@nixxie-cms/core 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CHANGES-1.1.md +134 -0
  3. package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
  4. package/context/dist/nixxie-cms-core-context.esm.js +3 -2
  5. package/dist/declarations/src/access.d.ts +2 -2
  6. package/dist/declarations/src/access.d.ts.map +1 -1
  7. package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
  8. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  9. package/dist/declarations/src/admin-ui/context.d.ts +6 -6
  10. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  11. package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
  12. package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
  13. package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
  14. package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
  15. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
  16. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
  18. package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
  19. package/dist/declarations/src/context.d.ts +1 -1
  20. package/dist/declarations/src/context.d.ts.map +1 -1
  21. package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
  22. package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
  23. package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
  24. package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
  25. package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
  26. package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
  27. package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
  28. package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
  29. package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
  30. package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
  31. package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
  32. package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
  33. package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
  34. package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
  35. package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
  36. package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
  37. package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
  38. package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
  39. package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
  40. package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
  41. package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
  42. package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
  43. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  44. package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
  45. package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
  46. package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
  47. package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
  48. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
  49. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
  50. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
  51. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
  52. package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
  53. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  54. package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
  55. package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
  56. package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
  57. package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
  58. package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
  59. package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
  60. package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
  61. package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
  62. package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
  63. package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
  64. package/dist/declarations/src/helpers.d.ts +249 -13
  65. package/dist/declarations/src/helpers.d.ts.map +1 -1
  66. package/dist/declarations/src/index.d.ts +9 -4
  67. package/dist/declarations/src/index.d.ts.map +1 -1
  68. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
  69. package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
  70. package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
  71. package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
  72. package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
  73. package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
  74. package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
  75. package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
  76. package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
  77. package/dist/declarations/src/lib/env.d.ts +9 -0
  78. package/dist/declarations/src/lib/env.d.ts.map +1 -0
  79. package/dist/declarations/src/lib/system.d.ts +1 -1
  80. package/dist/declarations/src/lib/system.d.ts.map +1 -1
  81. package/dist/declarations/src/list-features.d.ts +162 -0
  82. package/dist/declarations/src/list-features.d.ts.map +1 -0
  83. package/dist/declarations/src/schema.d.ts +24 -23
  84. package/dist/declarations/src/schema.d.ts.map +1 -1
  85. package/dist/declarations/src/session.d.ts +75 -0
  86. package/dist/declarations/src/session.d.ts.map +1 -1
  87. package/dist/declarations/src/types/admin-meta.d.ts +11 -11
  88. package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
  89. package/dist/declarations/src/types/config/access-control.d.ts +42 -42
  90. package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
  91. package/dist/declarations/src/types/config/fields.d.ts +19 -19
  92. package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
  93. package/dist/declarations/src/types/config/hooks.d.ts +131 -131
  94. package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
  95. package/dist/declarations/src/types/config/index.d.ts +190 -8
  96. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  97. package/dist/declarations/src/types/config/lists.d.ts +146 -108
  98. package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
  99. package/dist/declarations/src/types/context.d.ts +507 -47
  100. package/dist/declarations/src/types/context.d.ts.map +1 -1
  101. package/dist/declarations/src/types/next-fields.d.ts +28 -28
  102. package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
  103. package/dist/declarations/src/types/type-info.d.ts +3 -3
  104. package/dist/declarations/src/types/type-info.d.ts.map +1 -1
  105. package/dist/{express-455ae20c.cjs.js → express-84d534c2.cjs.js} +6 -6
  106. package/dist/{express-7559ca2d.esm.js → express-d0a4ce99.esm.js} +6 -6
  107. package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
  108. package/dist/index-6055753b.cjs.js +393 -0
  109. package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
  110. package/dist/index-f1703b7b.esm.js +386 -0
  111. package/dist/nixxie-cms-core.cjs.js +1388 -30
  112. package/dist/nixxie-cms-core.esm.js +1362 -24
  113. package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
  114. package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
  115. package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
  116. package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
  117. package/dist/{system-a321642d.cjs.js → system-6b37a5f8.cjs.js} +33 -7
  118. package/dist/{system-03e49e4f.esm.js → system-e591d821.esm.js} +33 -7
  119. package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
  120. package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
  121. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
  122. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
  123. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
  124. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
  125. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
  126. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
  127. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
  128. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
  129. package/package.json +4 -4
  130. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
  131. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
  132. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
  133. package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
  134. package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
  135. package/session/dist/nixxie-cms-core-session.esm.js +279 -1
  136. package/src/access.ts +25 -25
  137. package/src/admin-ui/admin-meta-graphql.ts +5 -5
  138. package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
  139. package/src/admin-ui/components/Navigation.tsx +3 -3
  140. package/src/admin-ui/context.tsx +6 -6
  141. package/src/admin-ui/utils/Fields.tsx +241 -241
  142. package/src/admin-ui/utils/actionData.ts +36 -36
  143. package/src/admin-ui/utils/filters.ts +148 -148
  144. package/src/admin-ui/utils/useCreateItem.ts +171 -171
  145. package/src/admin-ui/utils/utils.tsx +127 -127
  146. package/src/context.ts +1 -1
  147. package/src/fields/non-null-graphql.ts +115 -115
  148. package/src/fields/types/bigInt/index.ts +6 -6
  149. package/src/fields/types/bytes/index.ts +6 -6
  150. package/src/fields/types/calendarDay/index.ts +18 -19
  151. package/src/fields/types/checkbox/index.ts +6 -6
  152. package/src/fields/types/decimal/index.ts +6 -6
  153. package/src/fields/types/file/index.ts +8 -8
  154. package/src/fields/types/float/index.ts +6 -6
  155. package/src/fields/types/image/index.ts +8 -8
  156. package/src/fields/types/integer/index.ts +6 -6
  157. package/src/fields/types/json/index.ts +5 -5
  158. package/src/fields/types/multiselect/index.ts +7 -7
  159. package/src/fields/types/multiselect/views/index.tsx +149 -151
  160. package/src/fields/types/password/index.ts +6 -6
  161. package/src/fields/types/relationship/index.ts +13 -13
  162. package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
  163. package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
  164. package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
  165. package/src/fields/types/relationship/views/index.tsx +492 -492
  166. package/src/fields/types/relationship/views/types.ts +46 -46
  167. package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
  168. package/src/fields/types/relationship/views/useFilter.tsx +109 -109
  169. package/src/fields/types/select/index.ts +6 -6
  170. package/src/fields/types/text/index.ts +6 -6
  171. package/src/fields/types/timestamp/index.ts +23 -21
  172. package/src/fields/types/virtual/index.ts +11 -11
  173. package/src/helpers.ts +773 -42
  174. package/src/index.ts +66 -24
  175. package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
  176. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
  177. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
  178. package/src/lib/admin-meta.ts +369 -369
  179. package/src/lib/context/createContext.ts +6 -0
  180. package/src/lib/core/access-control.ts +434 -434
  181. package/src/lib/core/cascade.ts +236 -0
  182. package/src/lib/core/initialise-lists.ts +49 -33
  183. package/src/lib/core/mutations/index.ts +7 -0
  184. package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
  185. package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
  186. package/src/lib/core/queries/output-field.ts +178 -178
  187. package/src/lib/env.ts +50 -0
  188. package/src/lib/id-field.ts +2 -2
  189. package/src/lib/system.ts +221 -207
  190. package/src/lib/typescript-schema-printer.ts +227 -227
  191. package/src/list-features.ts +476 -0
  192. package/src/schema.ts +92 -22
  193. package/src/session.ts +225 -0
  194. package/src/types/admin-meta.ts +218 -218
  195. package/src/types/config/access-control.ts +186 -186
  196. package/src/types/config/fields.ts +96 -96
  197. package/src/types/config/hooks.ts +529 -529
  198. package/src/types/config/index.ts +206 -7
  199. package/src/types/config/lists.ts +606 -565
  200. package/src/types/context.ts +592 -55
  201. package/src/types/next-fields.ts +31 -31
  202. package/src/types/type-info.ts +38 -38
  203. package/src/types/type-tests.ts +21 -21
@@ -1,241 +1,241 @@
1
- import { useSlotId } from '@react-aria/utils'
2
- import { type ReactNode, useId } from 'react'
3
-
4
- import { FieldButton } from '@keystar/ui/button'
5
- import { Icon } from '@keystar/ui/icon'
6
- import { chevronRightIcon } from '@keystar/ui/icon/icons/chevronRightIcon'
7
- import { HStack, VStack } from '@keystar/ui/layout'
8
- import { css, tokenSchema } from '@keystar/ui/style'
9
- import { Text } from '@keystar/ui/typography'
10
-
11
- import { textCursorInputIcon } from '@keystar/ui/icon/icons/textCursorInputIcon'
12
- import type {
13
- BaseListTypeInfo,
14
- ConditionalFilter,
15
- ConditionalFilterCase,
16
- FieldGroupMeta,
17
- FieldMeta,
18
- } from '../../types'
19
- import { EmptyState } from '../components/EmptyState'
20
- import { resolveFieldMode, serializeItemForConditionalFilters, testFilter } from './filters'
21
-
22
- export function Fields({
23
- view,
24
- position,
25
- fields,
26
- groups = [],
27
- forceValidation,
28
- invalidFields,
29
- onChange,
30
- value: itemValue,
31
- fieldModes = {},
32
- fieldPositions = {},
33
- isRequireds,
34
- }: {
35
- view: 'createView' | 'itemView'
36
- position: 'form' | 'sidebar'
37
- fields: Record<string, FieldMeta>
38
- forceValidation: boolean
39
- groups?: FieldGroupMeta[]
40
- invalidFields: ReadonlySet<string>
41
- onChange(value: Record<string, unknown>): void
42
- value: Record<string, unknown>
43
- fieldModes?: Record<
44
- string,
45
- ConditionalFilter<'read' | 'edit' | 'hidden', 'read' | 'hidden', BaseListTypeInfo>
46
- >
47
- fieldPositions?: Record<string, 'form' | 'sidebar'>
48
- isRequireds: Record<string, ConditionalFilterCase<BaseListTypeInfo>>
49
- }) {
50
- const fieldDomByKey: Record<string, ReactNode> = {}
51
- let focused = false
52
-
53
- const serialized = serializeItemForConditionalFilters(fields, itemValue)
54
- for (const fieldKey in fields) {
55
- const field = fields[fieldKey]
56
- const fieldPosition = fieldPositions[fieldKey] ?? field.itemView.fieldPosition
57
- if (view === 'itemView' && fieldPosition !== position) continue
58
-
59
- const fieldModeFilter = fieldModes[fieldKey] ?? field[view].fieldMode
60
- const fieldMode = resolveFieldMode(fieldModeFilter, serialized, view)
61
- if (fieldMode === 'hidden') continue
62
-
63
- const fieldValue = itemValue[fieldKey]
64
- const autoFocus = focused === false // not great, but focuses the first field
65
- focused = true
66
-
67
- fieldDomByKey[fieldKey] = (
68
- <field.views.Field
69
- key={fieldKey}
70
- autoFocus={autoFocus}
71
- forceValidation={forceValidation && invalidFields.has(fieldKey)}
72
- field={field.controller}
73
- isRequired={testFilter(isRequireds[fieldKey] ?? false, serialized)}
74
- onChange={
75
- fieldMode === 'read' || onChange === undefined
76
- ? undefined
77
- : newFieldValue => {
78
- onChange({
79
- ...itemValue,
80
- [field.controller.fieldKey]: newFieldValue,
81
- })
82
- }
83
- }
84
- value={fieldValue}
85
- itemValue={itemValue}
86
- />
87
- )
88
- }
89
-
90
- const groupByFieldKey: Record<string, FieldGroupMeta> = {}
91
- for (const group of groups) {
92
- for (const { key: fieldKey } of group.fields) {
93
- groupByFieldKey[fieldKey] = group
94
- }
95
- }
96
-
97
- // WARNING: for no fields and position: sidebar, we show an empty state, that is OK
98
- if (Object.keys(fieldDomByKey).length === 0 && position === 'form') {
99
- return (
100
- <EmptyState
101
- icon={textCursorInputIcon}
102
- title="No fields"
103
- message="There are no fields to be shown."
104
- />
105
- )
106
- }
107
-
108
- return (
109
- // the "inline" container allows fields to react to the width of their column
110
- <VStack gap="xlarge" UNSAFE_style={{ containerType: 'inline-size' }}>
111
- {[
112
- ...(function* () {
113
- const rendered: Record<string, true> = {}
114
- for (const fieldKey in fields) {
115
- if (fieldKey in rendered) continue
116
-
117
- const group = groupByFieldKey[fieldKey]
118
- if (group) {
119
- const fields = [
120
- ...(function* () {
121
- for (const { key: fieldKey } of group.fields) {
122
- if (fieldKey in rendered) continue
123
- if (fieldDomByKey[fieldKey]) {
124
- yield fieldDomByKey[fieldKey]
125
- }
126
- rendered[fieldKey] = true
127
- }
128
- })(),
129
- ]
130
- if (fields.length === 0) continue
131
- yield (
132
- <FieldGroup key={group.label} label={group.label} description={group.description}>
133
- {fields}
134
- </FieldGroup>
135
- )
136
- continue
137
- }
138
-
139
- yield fieldDomByKey[fieldKey] ?? null
140
- rendered[fieldKey] = true
141
- }
142
- })(),
143
- ]}
144
- </VStack>
145
- )
146
- }
147
-
148
- function FieldGroup({
149
- description,
150
- label,
151
- children,
152
- }: {
153
- label: string
154
- description: string | null
155
- children: ReactNode
156
- }) {
157
- const labelId = useId()
158
- const descriptionId = useSlotId([Boolean(description)])
159
-
160
- return (
161
- <details aria-labelledby={labelId} aria-describedby={descriptionId} open>
162
- <HStack
163
- gap="medium"
164
- alignItems="center"
165
- elementType="summary"
166
- UNSAFE_className={css({
167
- listStyle: 'none',
168
- outline: 0,
169
- '::-webkit-details-marker': { display: 'none' },
170
- })}
171
- >
172
- <FieldButton
173
- // @ts-expect-error — this works, it's just not exposed in the public types
174
- elementType="div"
175
- // the <summary/> (above) is the focusable element
176
- excludeFromTabOrder
177
- prominence="low"
178
- height="element.small"
179
- width="element.small"
180
- minWidth="auto"
181
- UNSAFE_className={css({
182
- 'details[open] &': {
183
- transform: 'rotate(90deg)',
184
- },
185
- })}
186
- >
187
- <Icon src={chevronRightIcon} size="medium" />
188
- </FieldButton>
189
- <VStack
190
- gap="regular"
191
- UNSAFE_className={css({
192
- cursor: 'default',
193
- userSelect: 'none',
194
- })}
195
- >
196
- <Text
197
- color="neutralEmphasis"
198
- size="large"
199
- weight="medium"
200
- id={labelId}
201
- position="relative"
202
- >
203
- {label}
204
- </Text>
205
- {!!description && (
206
- <Text id={descriptionId} size="regular" color="neutralSecondary">
207
- {description}
208
- </Text>
209
- )}
210
- </VStack>
211
- </HStack>
212
-
213
- {/*
214
- Padding is applied here because `<details>` doesn't accept flex/grid
215
- layout, prefer "gap" in most cases.
216
- */}
217
- <HStack gap="medium" paddingTop="medium">
218
- <GroupIndicatorLine />
219
- <VStack gap="xlarge" flex>
220
- {children}
221
- </VStack>
222
- </HStack>
223
- </details>
224
- )
225
- }
226
-
227
- export function GroupIndicatorLine() {
228
- return (
229
- <HStack justifyContent="center" width="element.small">
230
- <div
231
- className={css({
232
- height: '100%',
233
- width: tokenSchema.size.alias.focusRing,
234
- borderRadius: tokenSchema.size.alias.focusRing,
235
- backgroundColor: tokenSchema.color.border.neutral,
236
- flexShrink: 0,
237
- })}
238
- />
239
- </HStack>
240
- )
241
- }
1
+ import { useSlotId } from '@react-aria/utils'
2
+ import { type ReactNode, useId } from 'react'
3
+
4
+ import { FieldButton } from '@keystar/ui/button'
5
+ import { Icon } from '@keystar/ui/icon'
6
+ import { chevronRightIcon } from '@keystar/ui/icon/icons/chevronRightIcon'
7
+ import { HStack, VStack } from '@keystar/ui/layout'
8
+ import { css, tokenSchema } from '@keystar/ui/style'
9
+ import { Text } from '@keystar/ui/typography'
10
+
11
+ import { textCursorInputIcon } from '@keystar/ui/icon/icons/textCursorInputIcon'
12
+ import type {
13
+ BaseCollectionTypeInfo,
14
+ ConditionalFilter,
15
+ ConditionalFilterCase,
16
+ FieldGroupMeta,
17
+ FieldMeta,
18
+ } from '../../types'
19
+ import { EmptyState } from '../components/EmptyState'
20
+ import { resolveFieldMode, serializeItemForConditionalFilters, testFilter } from './filters'
21
+
22
+ export function Fields({
23
+ view,
24
+ position,
25
+ fields,
26
+ groups = [],
27
+ forceValidation,
28
+ invalidFields,
29
+ onChange,
30
+ value: itemValue,
31
+ fieldModes = {},
32
+ fieldPositions = {},
33
+ isRequireds,
34
+ }: {
35
+ view: 'createView' | 'itemView'
36
+ position: 'form' | 'sidebar'
37
+ fields: Record<string, FieldMeta>
38
+ forceValidation: boolean
39
+ groups?: FieldGroupMeta[]
40
+ invalidFields: ReadonlySet<string>
41
+ onChange(value: Record<string, unknown>): void
42
+ value: Record<string, unknown>
43
+ fieldModes?: Record<
44
+ string,
45
+ ConditionalFilter<'read' | 'edit' | 'hidden', 'read' | 'hidden', BaseCollectionTypeInfo>
46
+ >
47
+ fieldPositions?: Record<string, 'form' | 'sidebar'>
48
+ isRequireds: Record<string, ConditionalFilterCase<BaseCollectionTypeInfo>>
49
+ }) {
50
+ const fieldDomByKey: Record<string, ReactNode> = {}
51
+ let focused = false
52
+
53
+ const serialized = serializeItemForConditionalFilters(fields, itemValue)
54
+ for (const fieldKey in fields) {
55
+ const field = fields[fieldKey]
56
+ const fieldPosition = fieldPositions[fieldKey] ?? field.itemView.fieldPosition
57
+ if (view === 'itemView' && fieldPosition !== position) continue
58
+
59
+ const fieldModeFilter = fieldModes[fieldKey] ?? field[view].fieldMode
60
+ const fieldMode = resolveFieldMode(fieldModeFilter, serialized, view)
61
+ if (fieldMode === 'hidden') continue
62
+
63
+ const fieldValue = itemValue[fieldKey]
64
+ const autoFocus = focused === false // not great, but focuses the first field
65
+ focused = true
66
+
67
+ fieldDomByKey[fieldKey] = (
68
+ <field.views.Field
69
+ key={fieldKey}
70
+ autoFocus={autoFocus}
71
+ forceValidation={forceValidation && invalidFields.has(fieldKey)}
72
+ field={field.controller}
73
+ isRequired={testFilter(isRequireds[fieldKey] ?? false, serialized)}
74
+ onChange={
75
+ fieldMode === 'read' || onChange === undefined
76
+ ? undefined
77
+ : newFieldValue => {
78
+ onChange({
79
+ ...itemValue,
80
+ [field.controller.fieldKey]: newFieldValue,
81
+ })
82
+ }
83
+ }
84
+ value={fieldValue}
85
+ itemValue={itemValue}
86
+ />
87
+ )
88
+ }
89
+
90
+ const groupByFieldKey: Record<string, FieldGroupMeta> = {}
91
+ for (const group of groups) {
92
+ for (const { key: fieldKey } of group.fields) {
93
+ groupByFieldKey[fieldKey] = group
94
+ }
95
+ }
96
+
97
+ // WARNING: for no fields and position: sidebar, we show an empty state, that is OK
98
+ if (Object.keys(fieldDomByKey).length === 0 && position === 'form') {
99
+ return (
100
+ <EmptyState
101
+ icon={textCursorInputIcon}
102
+ title="No fields"
103
+ message="There are no fields to be shown."
104
+ />
105
+ )
106
+ }
107
+
108
+ return (
109
+ // the "inline" container allows fields to react to the width of their column
110
+ <VStack gap="xlarge" UNSAFE_style={{ containerType: 'inline-size' }}>
111
+ {[
112
+ ...(function* () {
113
+ const rendered: Record<string, true> = {}
114
+ for (const fieldKey in fields) {
115
+ if (fieldKey in rendered) continue
116
+
117
+ const group = groupByFieldKey[fieldKey]
118
+ if (group) {
119
+ const fields = [
120
+ ...(function* () {
121
+ for (const { key: fieldKey } of group.fields) {
122
+ if (fieldKey in rendered) continue
123
+ if (fieldDomByKey[fieldKey]) {
124
+ yield fieldDomByKey[fieldKey]
125
+ }
126
+ rendered[fieldKey] = true
127
+ }
128
+ })(),
129
+ ]
130
+ if (fields.length === 0) continue
131
+ yield (
132
+ <FieldGroup key={group.label} label={group.label} description={group.description}>
133
+ {fields}
134
+ </FieldGroup>
135
+ )
136
+ continue
137
+ }
138
+
139
+ yield fieldDomByKey[fieldKey] ?? null
140
+ rendered[fieldKey] = true
141
+ }
142
+ })(),
143
+ ]}
144
+ </VStack>
145
+ )
146
+ }
147
+
148
+ function FieldGroup({
149
+ description,
150
+ label,
151
+ children,
152
+ }: {
153
+ label: string
154
+ description: string | null
155
+ children: ReactNode
156
+ }) {
157
+ const labelId = useId()
158
+ const descriptionId = useSlotId([Boolean(description)])
159
+
160
+ return (
161
+ <details aria-labelledby={labelId} aria-describedby={descriptionId} open>
162
+ <HStack
163
+ gap="medium"
164
+ alignItems="center"
165
+ elementType="summary"
166
+ UNSAFE_className={css({
167
+ listStyle: 'none',
168
+ outline: 0,
169
+ '::-webkit-details-marker': { display: 'none' },
170
+ })}
171
+ >
172
+ <FieldButton
173
+ // @ts-expect-error — this works, it's just not exposed in the public types
174
+ elementType="div"
175
+ // the <summary/> (above) is the focusable element
176
+ excludeFromTabOrder
177
+ prominence="low"
178
+ height="element.small"
179
+ width="element.small"
180
+ minWidth="auto"
181
+ UNSAFE_className={css({
182
+ 'details[open] &': {
183
+ transform: 'rotate(90deg)',
184
+ },
185
+ })}
186
+ >
187
+ <Icon src={chevronRightIcon} size="medium" />
188
+ </FieldButton>
189
+ <VStack
190
+ gap="regular"
191
+ UNSAFE_className={css({
192
+ cursor: 'default',
193
+ userSelect: 'none',
194
+ })}
195
+ >
196
+ <Text
197
+ color="neutralEmphasis"
198
+ size="large"
199
+ weight="medium"
200
+ id={labelId}
201
+ position="relative"
202
+ >
203
+ {label}
204
+ </Text>
205
+ {!!description && (
206
+ <Text id={descriptionId} size="regular" color="neutralSecondary">
207
+ {description}
208
+ </Text>
209
+ )}
210
+ </VStack>
211
+ </HStack>
212
+
213
+ {/*
214
+ Padding is applied here because `<details>` doesn't accept flex/grid
215
+ layout, prefer "gap" in most cases.
216
+ */}
217
+ <HStack gap="medium" paddingTop="medium">
218
+ <GroupIndicatorLine />
219
+ <VStack gap="xlarge" flex>
220
+ {children}
221
+ </VStack>
222
+ </HStack>
223
+ </details>
224
+ )
225
+ }
226
+
227
+ export function GroupIndicatorLine() {
228
+ return (
229
+ <HStack justifyContent="center" width="element.small">
230
+ <div
231
+ className={css({
232
+ height: '100%',
233
+ width: tokenSchema.size.alias.focusRing,
234
+ borderRadius: tokenSchema.size.alias.focusRing,
235
+ backgroundColor: tokenSchema.color.border.neutral,
236
+ flexShrink: 0,
237
+ })}
238
+ />
239
+ </HStack>
240
+ )
241
+ }
@@ -1,36 +1,36 @@
1
- import type { ActionMeta, ListMeta } from '../../types'
2
- import { serializeValueToOperationItem } from './utils'
3
-
4
- export function getActionArguments(
5
- list: ListMeta,
6
- action: ActionMeta,
7
- value: Record<string, unknown>
8
- ): Record<string, unknown> {
9
- const args: Record<string, unknown> = {}
10
- for (const arg of action.graphql.arguments) {
11
- const { source } = arg
12
- if (!source) continue
13
-
14
- const field = list.fields[source.itemField]
15
- if (!field) continue
16
-
17
- const serialized = serializeValueToOperationItem(
18
- 'update',
19
- { [source.itemField]: field },
20
- value,
21
- {}
22
- )
23
- if (Object.prototype.hasOwnProperty.call(serialized, source.itemField)) {
24
- args[arg.name] = serialized[source.itemField]
25
- }
26
- }
27
- return args
28
- }
29
-
30
- export function getActionGraphQLArgs(action: ActionMeta): string {
31
- return action.graphql.arguments.map(arg => `${arg.name}: $${arg.name}`).join(', ')
32
- }
33
-
34
- export function getActionGraphQLVariables(action: ActionMeta): string {
35
- return action.graphql.arguments.map(arg => `$${arg.name}: ${arg.type}`).join(', ')
36
- }
1
+ import type { ActionMeta, CollectionMeta } from '../../types'
2
+ import { serializeValueToOperationItem } from './utils'
3
+
4
+ export function getActionArguments(
5
+ list: CollectionMeta,
6
+ action: ActionMeta,
7
+ value: Record<string, unknown>
8
+ ): Record<string, unknown> {
9
+ const args: Record<string, unknown> = {}
10
+ for (const arg of action.graphql.arguments) {
11
+ const { source } = arg
12
+ if (!source) continue
13
+
14
+ const field = list.fields[source.itemField]
15
+ if (!field) continue
16
+
17
+ const serialized = serializeValueToOperationItem(
18
+ 'update',
19
+ { [source.itemField]: field },
20
+ value,
21
+ {}
22
+ )
23
+ if (Object.prototype.hasOwnProperty.call(serialized, source.itemField)) {
24
+ args[arg.name] = serialized[source.itemField]
25
+ }
26
+ }
27
+ return args
28
+ }
29
+
30
+ export function getActionGraphQLArgs(action: ActionMeta): string {
31
+ return action.graphql.arguments.map(arg => `${arg.name}: $${arg.name}`).join(', ')
32
+ }
33
+
34
+ export function getActionGraphQLVariables(action: ActionMeta): string {
35
+ return action.graphql.arguments.map(arg => `$${arg.name}: ${arg.type}`).join(', ')
36
+ }