@dxos/react-ui-form 0.11.1 → 1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d

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 (45) hide show
  1. package/dist/lib/index.mjs +87 -19
  2. package/dist/lib/index.mjs.map +1 -1
  3. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +1 -1
  4. package/dist/types/src/components/Form/Card.stories.d.ts +1 -1
  5. package/dist/types/src/components/Form/Form.stories.d.ts +1 -1
  6. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts +1 -1
  7. package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.d.ts +1 -1
  8. package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts +1 -1
  9. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts +1 -1
  10. package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.d.ts +1 -1
  11. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts +1 -1
  12. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts +1 -1
  13. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts +1 -1
  15. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts +1 -1
  16. package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.stories.d.ts +1 -1
  17. package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts +1 -1
  18. package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts +1 -1
  19. package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts +1 -1
  20. package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts +1 -1
  21. package/dist/types/src/components/Form/meta-tags.d.ts +34 -0
  22. package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -1
  23. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
  24. package/dist/types/src/components/ObjectForm/ObjectForm.stories.d.ts +1 -1
  25. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +1 -1
  26. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
  27. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +1 -1
  28. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +1 -1
  29. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +1 -1
  30. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  31. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +1 -1
  32. package/dist/types/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +34 -38
  34. package/src/components/FieldEditor/FieldEditor.test.tsx +1 -1
  35. package/src/components/Form/Form.theme.ts +1 -1
  36. package/src/components/Form/FormField/fields/RefField/RefField.tsx +12 -3
  37. package/src/components/Form/FormLayout/FormLayout.stories.test.tsx +13 -7
  38. package/src/components/Form/meta-tags.test.ts +71 -1
  39. package/src/components/Form/meta-tags.ts +69 -1
  40. package/src/components/ObjectForm/ObjectForm.tsx +15 -8
  41. package/src/components/ObjectProperties/ObjectProperties.test.tsx +1 -1
  42. package/src/components/ObjectProperties/ObjectProperties.tsx +15 -8
  43. package/src/components/ViewEditor/ViewEditor.test.tsx +1 -1
  44. package/src/components/ViewEditor/ViewEditor.tsx +4 -2
  45. package/src/vitest-setup.ts +21 -0
@@ -12,6 +12,7 @@ import * as Schema from "effect/Schema";
12
12
  import * as SchemaAST from "effect/SchemaAST";
13
13
  import { SchemaEx } from "@dxos/effect";
14
14
  import { DEFAULT_LAYOUT_NAME, FormLayoutAnnotation, ReferenceAnnotationId as ReferenceAnnotationId$1 } from "@dxos/echo/Annotation";
15
+ import { DXN as DXN$1, EID as EID$1, PublicKey, URI } from "@dxos/keys";
15
16
  import { MarkdownView } from "@dxos/react-ui-markdown";
16
17
  import { getHashHue, getStyles, mx, osTranslations, tv } from "@dxos/ui-theme";
17
18
  import * as Cause from "effect/Cause";
@@ -22,7 +23,6 @@ import * as Equal from "effect/Equal";
22
23
  import * as Utils from "effect/Utils";
23
24
  import { addEventListener } from "@dxos/async";
24
25
  import { useObject, useQuery, useType } from "@dxos/echo-react";
25
- import { DXN as DXN$1, EID as EID$1, PublicKey, URI } from "@dxos/keys";
26
26
  import { Combobox, OrderedList } from "@dxos/react-ui-list";
27
27
  import { arrayMove, safeParseFloat } from "@dxos/util";
28
28
  import * as Match from "effect/Match";
@@ -266,6 +266,62 @@ var META_TAGS_KEY = "_tags";
266
266
  * `tags` field the schema declares itself.
267
267
  */
268
268
  var withMetaTags = (schema) => omitId(Schema.Struct({ [META_TAGS_KEY]: Schema.Array(Ref.Ref(Tag.Tag)).pipe(Schema.annotations({ title: "Tags" }), Schema.optional) }).pipe(Schema.extend(schema)));
269
+ var isTag = Obj.instanceOf(Tag.Tag);
270
+ /**
271
+ * Narrows a tag picker's candidates to the tags a user may apply by hand: those with no origin.
272
+ *
273
+ * A tag that carries an origin is owned by whoever put it there — a Gmail label or a JMAP folder is
274
+ * sync's to apply, and a canonical DXOS tag (`starred`, `sent`, `draft`) is applied by a purpose-built
275
+ * affordance such as the star button or the draft lifecycle. Offering either in a generic picker
276
+ * invites an attribution that nothing maintains: on a synced object the next delta silently strips it,
277
+ * and on an unsynced one it never gets corrected at all. See `Tag.md` §"Tag origin".
278
+ *
279
+ * Non-tag candidates pass through untouched, so this is safe to apply to any ref field's results —
280
+ * hence generic in the element type rather than narrowed to `Entity.Any`, which concrete instances are
281
+ * deliberately not assignable to.
282
+ */
283
+ var filterTagCandidates = (results) => results.filter((result) => !isTag(result) || Tag.isUserTag(result));
284
+ /**
285
+ * A ref's target entity id, ignoring whether the uri is local (`echo:/<id>`) or qualified
286
+ * (`echo://<space>/<id>`) — `Ref.make` produces the former while `Obj.getURI` produces the latter, so
287
+ * the two forms must compare equal. Same reason as `findRefOption`.
288
+ */
289
+ var entityIdOf = (uri) => {
290
+ const parsed = EID$1.tryParse(uri);
291
+ return parsed ? EID$1.getEntityId(parsed) : void 0;
292
+ };
293
+ /**
294
+ * Splits an object's meta tags into the ones a form may edit and the ones a provider owns.
295
+ *
296
+ * Provider-owned tags are held back rather than shown read-only: the tags field is a generic array of
297
+ * refs whose delete affordance knows nothing about tags, so the only way to make one non-removable
298
+ * would be to thread tag-specific policy through a type-agnostic renderer. Holding them out of the
299
+ * form value instead keeps them off the picker *and* out of reach of the delete button, and matches
300
+ * where they are already surfaced — as chips on the object's own views.
301
+ *
302
+ * The caller must write `preserved` back alongside whatever the form returns: `meta.tags` is replaced
303
+ * wholesale on save, so omitting them would delete the object's provider tags on the first edit.
304
+ *
305
+ * `candidates` is the space's tag set (each tag's origin lives on the tag, not on the ref); non-tag
306
+ * entries are ignored, so a caller may pass a broader query result.
307
+ */
308
+ var partitionMetaTags = (refs, candidates) => {
309
+ const providerIds = /* @__PURE__ */ new Set();
310
+ for (const candidate of candidates) if (isTag(candidate) && Tag.isProviderTag(candidate)) {
311
+ const id = entityIdOf(Obj.getURI(candidate).toString());
312
+ if (id !== void 0) providerIds.add(id);
313
+ }
314
+ const editable = [];
315
+ const preserved = [];
316
+ for (const ref of refs) {
317
+ const id = entityIdOf(ref.uri);
318
+ (id !== void 0 && providerIds.has(id) ? preserved : editable).push(ref);
319
+ }
320
+ return {
321
+ editable,
322
+ preserved
323
+ };
324
+ };
269
325
  //#endregion
270
326
  //#region src/hooks/useAsyncFieldEffect.ts
271
327
  /** Picks the declared dependency fields from the form values — the subset a dynamic field subscribes to. */
@@ -666,7 +722,7 @@ var formTheme = {
666
722
  sectionDescription: "text-description",
667
723
  fieldSet: "",
668
724
  field: "",
669
- fieldLabel: "min-h-(--dx-control) grid grid-cols-[1fr_auto_auto_auto] items-center select-none",
725
+ fieldLabel: "min-h-(--dx-control) grid grid-cols-[1fr_auto_auto_auto] items-center",
670
726
  fieldLabelText: "",
671
727
  fieldDescription: "text-description",
672
728
  fieldControl: "",
@@ -2016,7 +2072,11 @@ var defaultGetOptions = (results, { parentLabel, getTypePlaceholder } = {}) => r
2016
2072
  label: Entity.getLabel(labelEntity) ?? (typename ? getTypePlaceholder?.(typename) : void 0) ?? id
2017
2073
  };
2018
2074
  });
2019
- var defaultUseResults = (db, typename) => useQuery(db, !typename ? Query.select(Filter.nothing()) : typename === ANY_OBJECT_TYPENAME ? Query.select(Filter.everything()) : Query.select(Filter.type(DXN$1.make(typename))).from(Scope.space(), Scope.registry()));
2075
+ var defaultUseResults = (db, typename) => {
2076
+ const results = useQuery(db, !typename ? Query.select(Filter.nothing()) : typename === ANY_OBJECT_TYPENAME ? Query.select(Filter.everything()) : Query.select(Filter.type(DXN$1.make(typename))).from(Scope.space(), Scope.registry()));
2077
+ const isTagField = typename === Type.getTypename(Tag.Tag);
2078
+ return useMemo(() => isTagField ? filterTagCandidates(results) : results, [isTagField, results]);
2079
+ };
2020
2080
  var RefField = (props) => {
2021
2081
  const { type, readonly, label, jsonPath, placeholder, presentation, required, getStatus, getValue, createOptionLabel, createOptionIcon, createInitialValuePath, createFieldMap, db, useType: useType$3 = useType, useResults = defaultUseResults, getOptions = defaultGetOptions, onCreate, resolveCreateEntry, onValueChange } = props;
2022
2082
  const { t } = useTranslation(translationKey);
@@ -3159,7 +3219,9 @@ var __dxlog_file$2 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/Ob
3159
3219
  var ObjectForm = ({ object, type, schema, showTags = true }) => {
3160
3220
  const db = Obj.getDatabase(object);
3161
3221
  const [snapshot] = useObject(object);
3162
- const tags = [...Obj.getMeta(object).tags];
3222
+ const meta = Obj.getMeta(object);
3223
+ const spaceTags = useQuery(db, Filter.type(Tag.Tag));
3224
+ const { editable: tags, preserved: preservedTags } = useMemo(() => partitionMetaTags([...meta.tags], spaceTags), [meta.tags, spaceTags]);
3163
3225
  const values = useMemo(() => ({
3164
3226
  [META_TAGS_KEY]: tags,
3165
3227
  ...snapshot
@@ -3176,14 +3238,14 @@ var ObjectForm = ({ object, type, schema, showTags = true }) => {
3176
3238
  invariant(db, void 0, {
3177
3239
  "~LogMeta": "~LogMeta",
3178
3240
  F: __dxlog_file$2,
3179
- L: 49,
3241
+ L: 54,
3180
3242
  S: void 0,
3181
3243
  A: ["db", ""]
3182
3244
  });
3183
3245
  invariant(Type.isObject(type), void 0, {
3184
3246
  "~LogMeta": "~LogMeta",
3185
3247
  F: __dxlog_file$2,
3186
- L: 50,
3248
+ L: 55,
3187
3249
  S: void 0,
3188
3250
  A: ["Type.isObject(type)", ""]
3189
3251
  });
@@ -3196,7 +3258,8 @@ var ObjectForm = ({ object, type, schema, showTags = true }) => {
3196
3258
  if (!isValid) return;
3197
3259
  const changedPaths = Object.keys(changed).filter((path) => changed[path]);
3198
3260
  if (changedPaths.some((path) => SchemaEx.splitJsonPath(path)[0] === "_tags")) Obj.update(object, (object) => {
3199
- Obj.getMeta(object).tags = Array.isArray(metaTags) ? [...metaTags] : [];
3261
+ const edited = Array.isArray(metaTags) ? metaTags : [];
3262
+ Obj.getMeta(object).tags = [...preservedTags, ...edited];
3200
3263
  });
3201
3264
  const nonTagPaths = changedPaths.filter((path) => SchemaEx.splitJsonPath(path)[0] !== META_TAGS_KEY);
3202
3265
  if (nonTagPaths.length > 0) Obj.update(object, () => {
@@ -3206,7 +3269,7 @@ var ObjectForm = ({ object, type, schema, showTags = true }) => {
3206
3269
  Obj.setValue(object, parts, value);
3207
3270
  }
3208
3271
  });
3209
- }, [object]);
3272
+ }, [object, preservedTags]);
3210
3273
  return /* @__PURE__ */ jsx(Form.Root, {
3211
3274
  schema: formSchema,
3212
3275
  values,
@@ -3235,7 +3298,9 @@ var createFieldMap$1 = { hue: ({ type, label, presentation, getValue, onValueCha
3235
3298
  var __dxlog_file$1 = "/__w/dxos/dxos/packages/ui/react-ui-form/src/components/ObjectProperties/ObjectProperties.tsx";
3236
3299
  var ObjectProperties = composable(({ children, object, getCreateDefaults, resolveCreateEntry, ...props }, forwardedRef) => {
3237
3300
  const db = Obj.getDatabase(object);
3238
- const tags = [...Obj.getMeta(object).tags];
3301
+ const meta = Obj.getMeta(object);
3302
+ const spaceTags = useQuery(db, Filter.type(Tag.Tag));
3303
+ const { editable: tags, preserved: preservedTags } = useMemo(() => partitionMetaTags([...meta.tags], spaceTags), [meta.tags, spaceTags]);
3239
3304
  const values = useMemo(() => ({
3240
3305
  [META_TAGS_KEY]: tags,
3241
3306
  ...object
@@ -3249,14 +3314,14 @@ var ObjectProperties = composable(({ children, object, getCreateDefaults, resolv
3249
3314
  invariant(db, void 0, {
3250
3315
  "~LogMeta": "~LogMeta",
3251
3316
  F: __dxlog_file$1,
3252
- L: 69,
3317
+ L: 74,
3253
3318
  S: void 0,
3254
3319
  A: ["db", ""]
3255
3320
  });
3256
3321
  invariant(Type.isObject(type), void 0, {
3257
3322
  "~LogMeta": "~LogMeta",
3258
3323
  F: __dxlog_file$1,
3259
- L: 70,
3324
+ L: 75,
3260
3325
  S: void 0,
3261
3326
  A: ["Type.isObject(type)", ""]
3262
3327
  });
@@ -3268,7 +3333,8 @@ var ObjectProperties = composable(({ children, object, getCreateDefaults, resolv
3268
3333
  if (!isValid) return;
3269
3334
  const changedPaths = Object.keys(changed).filter((path) => changed[path]);
3270
3335
  if (changedPaths.some((path) => SchemaEx.splitJsonPath(path)[0] === "_tags")) Obj.update(object, (object) => {
3271
- Obj.getMeta(object).tags = Array.isArray(metaTags) ? [...metaTags] : [];
3336
+ const edited = Array.isArray(metaTags) ? metaTags : [];
3337
+ Obj.getMeta(object).tags = [...preservedTags, ...edited];
3272
3338
  });
3273
3339
  const nonTagPaths = changedPaths.filter((path) => SchemaEx.splitJsonPath(path)[0] !== META_TAGS_KEY);
3274
3340
  if (nonTagPaths.length > 0) Obj.update(object, () => {
@@ -3278,7 +3344,7 @@ var ObjectProperties = composable(({ children, object, getCreateDefaults, resolv
3278
3344
  Obj.setValue(object, parts, value);
3279
3345
  }
3280
3346
  });
3281
- }, [object]);
3347
+ }, [object, preservedTags]);
3282
3348
  if (!formSchema) return null;
3283
3349
  return /* @__PURE__ */ jsx(Form.Root, {
3284
3350
  db,
@@ -3897,8 +3963,10 @@ var ViewEditor = forwardRef(({ classNames, type, view, mode = "schema", registry
3897
3963
  children: /* @__PURE__ */ jsx(Form.Viewport, { children: /* @__PURE__ */ jsxs(Form.Content, { children: [
3898
3964
  schemaReadonly && !readonly && /* @__PURE__ */ jsx(Message.Root, {
3899
3965
  valence: "info",
3900
- classNames: "my-form-padding",
3901
- children: /* @__PURE__ */ jsx(Message.Title, { children: t("system-schema.description") })
3966
+ children: /* @__PURE__ */ jsx(Message.Content, {
3967
+ classNames: "my-form-padding",
3968
+ children: /* @__PURE__ */ jsx(Message.Title, { children: t("system-schema.description") })
3969
+ })
3902
3970
  }),
3903
3971
  /* @__PURE__ */ jsx(Form.FieldSet, {}),
3904
3972
  type && /* @__PURE__ */ jsx(FieldList, {
@@ -3949,7 +4017,7 @@ var FieldList = ({ type, view, registry, readonly, showHeading = false, onDelete
3949
4017
  invariant(!readonly, void 0, {
3950
4018
  "~LogMeta": "~LogMeta",
3951
4019
  F: __dxlog_file,
3952
- L: 290,
4020
+ L: 292,
3953
4021
  S: void 0,
3954
4022
  A: ["!readonly", ""]
3955
4023
  });
@@ -3960,7 +4028,7 @@ var FieldList = ({ type, view, registry, readonly, showHeading = false, onDelete
3960
4028
  invariant(!readonly, void 0, {
3961
4029
  "~LogMeta": "~LogMeta",
3962
4030
  F: __dxlog_file,
3963
- L: 297,
4031
+ L: 299,
3964
4032
  S: void 0,
3965
4033
  A: ["!readonly", ""]
3966
4034
  });
@@ -3975,7 +4043,7 @@ var FieldList = ({ type, view, registry, readonly, showHeading = false, onDelete
3975
4043
  invariant(!readonly, void 0, {
3976
4044
  "~LogMeta": "~LogMeta",
3977
4045
  F: __dxlog_file,
3978
- L: 309,
4046
+ L: 311,
3979
4047
  S: void 0,
3980
4048
  A: ["!readonly", ""]
3981
4049
  });
@@ -4059,6 +4127,6 @@ var FieldList = ({ type, view, registry, readonly, showHeading = false, onDelete
4059
4127
  });
4060
4128
  };
4061
4129
  //#endregion
4062
- export { ArrayField, AsyncSelectField, AutofillAnnotation, AutofillAnnotationId, AutofillField, BooleanField, ComboboxField, CompactIconButton, DateField, EMAIL_REGEX, FieldEditor, Form, FormContextProvider, FormField, FormFieldErrorBoundary, FormFieldHeader, FormFieldLabel, FormFieldSet, FormFieldSetContainer, FormRow, GeoPointField, InlineRefField, META_TAGS_KEY, MarkdownField, NAME_ADDR_REGEX, NumberField, ObjectForm, ObjectPicker, ObjectProperties, ObjectTree, OptionsLookupAnnotation, OptionsLookupAnnotationId, PasswordField, REF_REGEX, RefEditor, RefField, SelectField, SelectOptionField, TextAreaField, TextField, TupleField, ViewEditor, autofill, cellClassesForFieldType, createSelectField, findRefOption, formatMailbox, getDefaultValue, getFormProperties, getNumericConstraints, getRefProps, getRootFormProperties, omitHiddenFormFields, omitId, optionsLookup, parseValue, pathNotUniqueError, pickValues, presentationFor, refEditor, refEditorRedecorate, useAsyncFieldEffect, useFormContext, useFormFieldState, useFormHandler, useFormValues, useKeyHandler, withMetaTags };
4130
+ export { ArrayField, AsyncSelectField, AutofillAnnotation, AutofillAnnotationId, AutofillField, BooleanField, ComboboxField, CompactIconButton, DateField, EMAIL_REGEX, FieldEditor, Form, FormContextProvider, FormField, FormFieldErrorBoundary, FormFieldHeader, FormFieldLabel, FormFieldSet, FormFieldSetContainer, FormRow, GeoPointField, InlineRefField, META_TAGS_KEY, MarkdownField, NAME_ADDR_REGEX, NumberField, ObjectForm, ObjectPicker, ObjectProperties, ObjectTree, OptionsLookupAnnotation, OptionsLookupAnnotationId, PasswordField, REF_REGEX, RefEditor, RefField, SelectField, SelectOptionField, TextAreaField, TextField, TupleField, ViewEditor, autofill, cellClassesForFieldType, createSelectField, filterTagCandidates, findRefOption, formatMailbox, getDefaultValue, getFormProperties, getNumericConstraints, getRefProps, getRootFormProperties, omitHiddenFormFields, omitId, optionsLookup, parseValue, partitionMetaTags, pathNotUniqueError, pickValues, presentationFor, refEditor, refEditorRedecorate, useAsyncFieldEffect, useFormContext, useFormFieldState, useFormHandler, useFormValues, useKeyHandler, withMetaTags };
4063
4131
 
4064
4132
  //# sourceMappingURL=index.mjs.map