@proteos/ui 0.55.0 → 0.56.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.
package/dist/index.d.cts CHANGED
@@ -2356,7 +2356,10 @@ type FilterField = {
2356
2356
  /** Parent label for breadcrumb in chips, e.g. `'Owner'`. Omitted for top-level. */
2357
2357
  parentLabel?: string;
2358
2358
  type: FilterFieldType;
2359
- /** Required when `type === 'enum'`; ignored otherwise. */
2359
+ /** Required when `type === 'enum'`. For `type === 'array'` these are the
2360
+ * ITEM enum's values (an array of enum items), so the membership editors
2361
+ * and chips pick from the same list the record editor does. Ignored
2362
+ * otherwise. */
2360
2363
  options?: ReadonlyArray<FilterFieldOption>;
2361
2364
  /** Hint for `<Input type=…>`. Defaults derived from `type`. */
2362
2365
  inputType?: 'text' | 'number' | 'date' | 'datetime' | 'email';
@@ -2407,7 +2410,7 @@ type FilterFieldRelation = {
2407
2410
  /** Dot-path of the field on the related entity, e.g. `'name'` or `'address.city'`. */
2408
2411
  relatedFieldPath: string;
2409
2412
  };
2410
- type ComparisonOperator = 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | 'ne' | 'in' | 'not_in' | 'contains' | 'starts_with' | 'ends_with' | 'empty' | 'not_empty';
2413
+ type ComparisonOperator = 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | 'ne' | 'in' | 'not_in' | 'contains' | 'not_contains' | 'contains_all' | 'starts_with' | 'ends_with' | 'empty' | 'not_empty';
2411
2414
  type LogicalOperator = 'and' | 'or';
2412
2415
  type FilterValue = string | string[] | boolean | null;
2413
2416
  type FilterElement = {
package/dist/index.d.ts CHANGED
@@ -2356,7 +2356,10 @@ type FilterField = {
2356
2356
  /** Parent label for breadcrumb in chips, e.g. `'Owner'`. Omitted for top-level. */
2357
2357
  parentLabel?: string;
2358
2358
  type: FilterFieldType;
2359
- /** Required when `type === 'enum'`; ignored otherwise. */
2359
+ /** Required when `type === 'enum'`. For `type === 'array'` these are the
2360
+ * ITEM enum's values (an array of enum items), so the membership editors
2361
+ * and chips pick from the same list the record editor does. Ignored
2362
+ * otherwise. */
2360
2363
  options?: ReadonlyArray<FilterFieldOption>;
2361
2364
  /** Hint for `<Input type=…>`. Defaults derived from `type`. */
2362
2365
  inputType?: 'text' | 'number' | 'date' | 'datetime' | 'email';
@@ -2407,7 +2410,7 @@ type FilterFieldRelation = {
2407
2410
  /** Dot-path of the field on the related entity, e.g. `'name'` or `'address.city'`. */
2408
2411
  relatedFieldPath: string;
2409
2412
  };
2410
- type ComparisonOperator = 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | 'ne' | 'in' | 'not_in' | 'contains' | 'starts_with' | 'ends_with' | 'empty' | 'not_empty';
2413
+ type ComparisonOperator = 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | 'ne' | 'in' | 'not_in' | 'contains' | 'not_contains' | 'contains_all' | 'starts_with' | 'ends_with' | 'empty' | 'not_empty';
2411
2414
  type LogicalOperator = 'and' | 'or';
2412
2415
  type FilterValue = string | string[] | boolean | null;
2413
2416
  type FilterElement = {
package/dist/index.js CHANGED
@@ -6217,7 +6217,8 @@ var OPERATORS = [
6217
6217
  "relation",
6218
6218
  "user",
6219
6219
  "principal",
6220
- "contact-address"
6220
+ "contact-address",
6221
+ "array"
6221
6222
  ]
6222
6223
  },
6223
6224
  {
@@ -6233,10 +6234,19 @@ var OPERATORS = [
6233
6234
  "relation",
6234
6235
  "user",
6235
6236
  "principal",
6236
- "contact-address"
6237
+ "contact-address",
6238
+ "array"
6237
6239
  ]
6238
6240
  },
6239
6241
  { id: "contains", label: "contains", arity: 1, types: ["string", "array", "contact-address"] },
6242
+ {
6243
+ id: "not_contains",
6244
+ label: "does not contain",
6245
+ arity: 1,
6246
+ types: ["string", "array", "contact-address"]
6247
+ },
6248
+ // Array-only: "has every one of these". On a scalar the backend rejects it.
6249
+ { id: "contains_all", label: "has all of", arity: "n", types: ["array"] },
6240
6250
  { id: "starts_with", label: "starts with", arity: 1, types: ["string", "contact-address"] },
6241
6251
  { id: "ends_with", label: "ends with", arity: 1, types: ["string", "contact-address"] },
6242
6252
  {
@@ -6299,14 +6309,33 @@ var OP_LABEL_OVERRIDES = {
6299
6309
  integer: { gt: "is greater than", lt: "is less than", eq: "equals", ne: "does not equal" },
6300
6310
  currency: { gt: "is greater than", lt: "is less than", eq: "equals", ne: "does not equal" },
6301
6311
  datetime: { eq: "is on", ne: "is not on", gt: "is after", lt: "is before" },
6302
- array: { contains: "contains", empty: "is empty", not_empty: "is not empty" },
6312
+ // Arrays filter by set membership, in the words every CRM uses for it.
6313
+ array: {
6314
+ contains: "has",
6315
+ not_contains: "does not have",
6316
+ in: "has any of",
6317
+ not_in: "has none of",
6318
+ contains_all: "has all of",
6319
+ empty: "is empty",
6320
+ not_empty: "is not empty"
6321
+ },
6303
6322
  boolean: { eq: "is", ne: "is not" }
6304
6323
  };
6324
+ var OP_ORDER_OVERRIDES = {
6325
+ array: ["contains", "not_contains", "in", "contains_all", "not_in", "empty", "not_empty"]
6326
+ };
6305
6327
  function operatorsForType(type) {
6306
- return OPERATORS.filter((op) => op.types.includes(type)).map((op) => {
6328
+ const ops = OPERATORS.filter((op) => op.types.includes(type)).map((op) => {
6307
6329
  const override = OP_LABEL_OVERRIDES[type]?.[op.id];
6308
6330
  return override ? { ...op, label: override } : op;
6309
6331
  });
6332
+ const order = OP_ORDER_OVERRIDES[type];
6333
+ if (!order) return ops;
6334
+ return [...ops].sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
6335
+ }
6336
+ function isEnumLike(field) {
6337
+ if (field.type === "enum") return true;
6338
+ return field.type === "array" && (field.options?.length ?? 0) > 0;
6310
6339
  }
6311
6340
  function operatorById(id) {
6312
6341
  return OPERATORS.find((o) => o.id === id);
@@ -6378,7 +6407,7 @@ function ChipItem({
6378
6407
  const custom = effectiveRenderer({ field, operator, value });
6379
6408
  if (custom !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: custom });
6380
6409
  }
6381
- if (field.type === "enum") {
6410
+ if (isEnumLike(field)) {
6382
6411
  const option = findEnumOption(field, value);
6383
6412
  return /* @__PURE__ */ jsx(
6384
6413
  Badge,
@@ -6406,7 +6435,7 @@ function formatValue(field, operator, value) {
6406
6435
  if (arity === "n") {
6407
6436
  const arr = Array.isArray(value) ? value : typeof value === "string" && value.length > 0 ? value.split(",") : [];
6408
6437
  if (arr.length === 0) return "\u2014";
6409
- if (field.type === "enum") {
6438
+ if (isEnumLike(field)) {
6410
6439
  const labels = arr.map((v) => findEnumOption(field, v)?.label ?? v);
6411
6440
  return labels.length <= 2 ? labels.join(", ") : `${labels[0]}, ${labels[1]} +${labels.length - 2}`;
6412
6441
  }
@@ -6414,7 +6443,7 @@ function formatValue(field, operator, value) {
6414
6443
  }
6415
6444
  if (field.type === "boolean") return value === true || value === "true" ? "true" : "false";
6416
6445
  if (value === "" || value == null) return "\u2014";
6417
- if (field.type === "enum") {
6446
+ if (isEnumLike(field)) {
6418
6447
  return findEnumOption(field, String(value))?.label ?? String(value);
6419
6448
  }
6420
6449
  if (field.type === "number" || field.type === "integer") {
@@ -6662,7 +6691,7 @@ function FilterValueInput({
6662
6691
  }
6663
6692
  const leaf = (() => {
6664
6693
  if (arity === "n") {
6665
- if (field.type === "enum") {
6694
+ if (isEnumLike(field)) {
6666
6695
  return /* @__PURE__ */ jsx(
6667
6696
  EnumMultiValue,
6668
6697
  {
@@ -6689,7 +6718,7 @@ function FilterValueInput({
6689
6718
  }
6690
6719
  );
6691
6720
  }
6692
- if (field.type === "enum") {
6721
+ if (isEnumLike(field)) {
6693
6722
  return /* @__PURE__ */ jsx(
6694
6723
  EnumSingleValue,
6695
6724
  {
@@ -6850,7 +6879,9 @@ function placeholderFor(field, operator) {
6850
6879
  if (field.type === "number" || field.type === "integer" || field.type === "currency") return "0";
6851
6880
  if (operator === "starts_with") return "starts with\u2026";
6852
6881
  if (operator === "ends_with") return "ends with\u2026";
6853
- if (operator === "contains") return "contains\u2026";
6882
+ if (operator === "contains") return field.type === "array" ? "has\u2026" : "contains\u2026";
6883
+ if (operator === "not_contains")
6884
+ return field.type === "array" ? "does not have\u2026" : "does not contain\u2026";
6854
6885
  return "value";
6855
6886
  }
6856
6887
  function OperatorPicker({