@tensor_1/react 2.49.1 → 2.51.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.
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { TextFilterOperator, NumberFilterOperator, DateFilterOperator, FilterField, FilterValue } from '../filter-model.js';
2
+ import { TextFilterOperator, NumberFilterOperator, DateFilterOperator, EnumFilterOperator, FilterField, FilterValue } from '../filter-model.js';
3
3
 
4
4
  interface FilterEditorLabels {
5
5
  apply: string;
@@ -35,6 +35,8 @@ interface FilterEditorLabels {
35
35
  operators: Record<TextFilterOperator | NumberFilterOperator, string>;
36
36
  /** `date` conditions (HAR-1832). Optional, like `presets`. */
37
37
  dateOperators?: Partial<Record<DateFilterOperator, string>>;
38
+ /** `enum` conditions (HAR-2631), when the field offers both. Optional, like `presets`. */
39
+ enumOperators?: Partial<Record<EnumFilterOperator, string>>;
38
40
  }
39
41
  declare const DEFAULT_FILTER_EDITOR_LABELS: FilterEditorLabels;
40
42
  interface FilterEditorProps {
@@ -12,7 +12,7 @@ import { cx } from "../cx.js";
12
12
  import { useUixLabels } from "../labels-context.js";
13
13
  import { useDelayedPending } from "../hooks/useDelayedPending.js";
14
14
  import { foldForSearch } from "../search-suggest-model.js";
15
- import { DATE_FILTER_OPERATORS, NUMBER_FILTER_OPERATORS, TEXT_FILTER_OPERATORS, emptyFilterValue, isUnresolvedChoice, matchFilterPreset, orderFilterRange } from "../filter-model.js";
15
+ import { DATE_FILTER_OPERATORS, ENUM_FILTER_OPERATORS, NUMBER_FILTER_OPERATORS, TEXT_FILTER_OPERATORS, emptyFilterValue, isUnresolvedChoice, matchFilterPreset, orderFilterRange } from "../filter-model.js";
16
16
  const DEFAULT_FILTER_EDITOR_LABELS = {
17
17
  apply: "Apply",
18
18
  clear: "Clear",
@@ -38,6 +38,7 @@ const DEFAULT_FILTER_EDITOR_LABELS = {
38
38
  addTagHint: "Enter or a comma adds it.",
39
39
  date: "Date",
40
40
  dateOperators: { on: "on", before: "before", after: "after", between: "between" },
41
+ enumOperators: { "is-any-of": "is any of", "is-none-of": "is none of" },
41
42
  operators: {
42
43
  contains: "contains",
43
44
  "not-contains": "does not contain",
@@ -60,7 +61,8 @@ function FilterEditor({ field, value: valueProp, onValueChange, onApply, onClear
60
61
  ...provided,
61
62
  ...labelOverrides,
62
63
  operators: { ...DEFAULT_FILTER_EDITOR_LABELS.operators, ...provided?.operators, ...labelOverrides?.operators },
63
- dateOperators: { ...DEFAULT_FILTER_EDITOR_LABELS.dateOperators, ...provided?.dateOperators, ...labelOverrides?.dateOperators }
64
+ dateOperators: { ...DEFAULT_FILTER_EDITOR_LABELS.dateOperators, ...provided?.dateOperators, ...labelOverrides?.dateOperators },
65
+ enumOperators: { ...DEFAULT_FILTER_EDITOR_LABELS.enumOperators, ...provided?.enumOperators, ...labelOverrides?.enumOperators }
64
66
  };
65
67
  const id = useId();
66
68
  let value = valueProp?.kind === field.kind ? valueProp : emptyFilterValue(field.kind);
@@ -163,16 +165,30 @@ function EnumBody({ field, value, onChange, labels, threshold }) {
163
165
  return q ? options.filter((o) => foldForSearch(o.label).text.includes(q)) : options;
164
166
  }, [options, query]);
165
167
  const selected = new Set(value.values);
166
- const toggle = (v, on) => onChange({ kind: "enum", values: on ? [...value.values, v] : value.values.filter((x) => x !== v) });
168
+ const ops = (field.enumOperators ?? []).filter((op) => ENUM_FILTER_OPERATORS.includes(op));
169
+ const condition = value.operator ?? (ops.length === 1 ? ops[0] : void 0);
170
+ const withValues = (values) => condition && condition !== "is-any-of" ? { kind: "enum", values, operator: condition } : { kind: "enum", values };
171
+ const toggle = (v, on) => onChange(withValues(on ? [...value.values, v] : value.values.filter((x) => x !== v)));
167
172
  const presets = field.presets ?? [];
168
173
  const active = matchFilterPreset(field, value);
174
+ const words = labels.enumOperators ?? {};
169
175
  return /* @__PURE__ */ jsxs(Fragment, { children: [
170
- presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "uix-chip-group uix-filter-editor__presets", role: "group", "aria-label": labels.presets ?? DEFAULT_FILTER_EDITOR_LABELS.presets, children: presets.map((p) => /* @__PURE__ */ jsx(Chip, { pressed: p === active, onPressedChange: () => onChange({ kind: "enum", values: [...p.values] }), children: p.label }, p.id)) }),
176
+ ops.length > 1 && /* @__PURE__ */ jsx("div", { className: "uix-filter-editor__row", children: /* @__PURE__ */ jsx(
177
+ Select,
178
+ {
179
+ size: "sm",
180
+ "aria-label": labels.operator,
181
+ value: value.operator ?? "is-any-of",
182
+ options: ops.map((op) => ({ value: op, label: words[op] ?? DEFAULT_FILTER_EDITOR_LABELS.enumOperators[op] })),
183
+ onValueChange: (op) => onChange(op === "is-none-of" ? { kind: "enum", values: value.values, operator: "is-none-of" } : { kind: "enum", values: value.values })
184
+ }
185
+ ) }),
186
+ presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "uix-chip-group uix-filter-editor__presets", role: "group", "aria-label": labels.presets ?? DEFAULT_FILTER_EDITOR_LABELS.presets, children: presets.map((p) => /* @__PURE__ */ jsx(Chip, { pressed: p === active, onPressedChange: () => onChange(withValues([...p.values])), children: p.label }, p.id)) }),
171
187
  options.length > threshold && /* @__PURE__ */ jsx(Input, { type: "search", size: "sm", "aria-label": labels.search, placeholder: labels.search, value: query, onChange: (e) => setQuery(e.currentTarget.value), autoFocus: true }),
172
188
  /* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__bulk", children: [
173
189
  /* @__PURE__ */ jsx("span", { className: "uix-filter-editor__count", "aria-live": "polite", children: fillLabel(labels.selected, { count: value.values.length }) }),
174
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => onChange({ kind: "enum", values: [.../* @__PURE__ */ new Set([...value.values, ...shown.map((o) => o.value)])] }), children: labels.selectAll }),
175
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => onChange({ kind: "enum", values: [] }), children: labels.selectNone })
190
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => onChange(withValues([.../* @__PURE__ */ new Set([...value.values, ...shown.map((o) => o.value)])])), children: labels.selectAll }),
191
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => onChange(withValues([])), children: labels.selectNone })
176
192
  ] }),
177
193
  /* @__PURE__ */ jsx("div", { className: "uix-filter-editor__list", children: shown.length === 0 ? /* @__PURE__ */ jsx("p", { className: "uix-filter-editor__note", children: labels.noMatches }) : shown.map((o) => /* @__PURE__ */ jsx(Checkbox, { label: o.label, checked: selected.has(o.value), onChange: (e) => toggle(o.value, e.currentTarget.checked) }, o.value)) })
178
194
  ] });
@@ -46,6 +46,8 @@ interface SearchBarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSubmit'
46
46
  labels?: Partial<SearchBarLabels>;
47
47
  /** The field's `name`. */
48
48
  name?: string;
49
+ /** The longest text the field takes (HAR-2622). */
50
+ maxLength?: number;
49
51
  /** The field's `id`, e.g. for a "/" shortcut that focuses it. Default: generated. */
50
52
  inputId?: string;
51
53
  inputRef?: Ref<HTMLInputElement>;
@@ -63,6 +65,6 @@ interface SearchBarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSubmit'
63
65
  * takes Escape itself before the field (a ConfirmPopover, the table's view menu), Escape closes
64
66
  * the overlay rather than clearing the text.
65
67
  */
66
- declare function SearchBar({ label, value: valueProp, defaultValue, onValueChange, onSubmit, onClear, placeholder, size, submitButton, submitVariant, landmark, inputDescribedBy, labels: labelsProp, name, inputId, inputRef, autoFocus, disabled, className, ...props }: SearchBarProps): react.JSX.Element;
68
+ declare function SearchBar({ label, value: valueProp, defaultValue, onValueChange, onSubmit, onClear, placeholder, size, submitButton, submitVariant, landmark, inputDescribedBy, labels: labelsProp, name, maxLength, inputId, inputRef, autoFocus, disabled, className, ...props }: SearchBarProps): react.JSX.Element;
67
69
 
68
70
  export { SearchBar, type SearchBarLabels, type SearchBarProps };
@@ -25,6 +25,7 @@ function SearchBar({
25
25
  inputDescribedBy,
26
26
  labels: labelsProp,
27
27
  name,
28
+ maxLength,
28
29
  inputId,
29
30
  inputRef,
30
31
  autoFocus,
@@ -83,6 +84,7 @@ function SearchBar({
83
84
  id: fieldId,
84
85
  type: "search",
85
86
  name,
87
+ maxLength,
86
88
  className: "uix-input",
87
89
  value,
88
90
  placeholder,
@@ -20,6 +20,10 @@ declare const TableWrap: react.ForwardRefExoticComponent<TableWrapProps & react.
20
20
  type TableDensity = 'compact' | 'standard' | 'comfortable';
21
21
  type SortDirection = 'ascending' | 'descending' | 'none';
22
22
  interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
23
+ /**
24
+ * The row height. Named, the table holds it, `standard` included, whatever the page sets
25
+ * `--uix-row-h` to (a density preference); left out, the table follows `--uix-row-h`.
26
+ */
23
27
  density?: TableDensity;
24
28
  zebra?: boolean;
25
29
  pinnedCol?: boolean;
@@ -40,9 +44,14 @@ interface ThProps extends ThHTMLAttributes<HTMLTableCellElement> {
40
44
  sortDirection?: SortDirection;
41
45
  /** 1-based ordinal shown when this column is a secondary key in a multi-sort */
42
46
  sortOrder?: number;
47
+ /**
48
+ * The ordinal in words for assistive tech, e.g. "Sort priority 2" (2.50, HAR-2623). The visible
49
+ * ordinal is CSS content with empty alt text, so without this it is not announced at all.
50
+ */
51
+ sortOrderLabel?: string;
43
52
  onSort?: () => void;
44
53
  }
45
- declare function Th({ sortable, sortDirection, sortOrder, onSort, className, children, onClick, ...props }: ThProps): react.JSX.Element;
54
+ declare function Th({ sortable, sortDirection, sortOrder, sortOrderLabel, onSort, className, children, onClick, ...props }: ThProps): react.JSX.Element;
46
55
  interface TdProps extends TdHTMLAttributes<HTMLTableCellElement> {
47
56
  pinned?: boolean;
48
57
  }
@@ -13,7 +13,7 @@ const TableWrap = forwardRef(function TableWrap2({ announcement, refreshing, chi
13
13
  ] });
14
14
  });
15
15
  function Table({
16
- density = "standard",
16
+ density,
17
17
  zebra = true,
18
18
  pinnedCol,
19
19
  pinnedActions,
@@ -35,7 +35,7 @@ function Table({
35
35
  fixed && "uix-table--fixed",
36
36
  className
37
37
  ),
38
- "data-density": density !== "standard" ? density : void 0,
38
+ "data-density": density,
39
39
  "data-uix-rowpeek": rowPeek || void 0,
40
40
  "data-uix-table-v2": v2 || void 0,
41
41
  ...props,
@@ -43,17 +43,25 @@ function Table({
43
43
  }
44
44
  );
45
45
  }
46
- function Th({ sortable, sortDirection, sortOrder, onSort, className, children, onClick, ...props }) {
46
+ function Th({ sortable, sortDirection, sortOrder, sortOrderLabel, onSort, className, children, onClick, ...props }) {
47
+ const ordinal = sortOrder != null && sortOrder > 0 && sortOrderLabel ? /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: `, ${sortOrderLabel}` }) : null;
47
48
  return /* @__PURE__ */ jsx(
48
49
  "th",
49
50
  {
50
51
  className,
51
52
  "data-sort": sortable || void 0,
52
53
  "data-sort-order": sortOrder != null && sortOrder > 0 ? sortOrder : void 0,
54
+ "data-sort-labelled": ordinal != null || void 0,
53
55
  "aria-sort": sortDirection && sortDirection !== "none" ? sortDirection : void 0,
54
56
  onClick: sortable && onSort ? void 0 : onClick,
55
57
  ...props,
56
- children: sortable ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-th__sortbtn", onClick: onSort, children }) : children
58
+ children: sortable ? /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-th__sortbtn", onClick: onSort, children: [
59
+ children,
60
+ ordinal
61
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
62
+ children,
63
+ ordinal
64
+ ] })
57
65
  }
58
66
  );
59
67
  }
@@ -15,9 +15,15 @@ type NumberFilterOperator = 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte' | 'betwee
15
15
  * `dateFilterBounds` turns each into the half-open range a query needs.
16
16
  */
17
17
  type DateFilterOperator = 'on' | 'before' | 'after' | 'between';
18
+ /**
19
+ * An `enum` condition (HAR-2631): the row's value is one of the chosen values, or none of them
20
+ * ("status is none of Withdrawn, Lost"). Absent means `is-any-of`.
21
+ */
22
+ type EnumFilterOperator = 'is-any-of' | 'is-none-of';
18
23
  type FilterValue = {
19
24
  kind: 'enum';
20
25
  values: string[];
26
+ operator?: EnumFilterOperator;
21
27
  } | {
22
28
  kind: 'text';
23
29
  operator: TextFilterOperator;
@@ -79,6 +85,11 @@ interface FilterField {
79
85
  operators?: readonly (TextFilterOperator | NumberFilterOperator)[];
80
86
  /** `date`: the conditions offered, in order. Default: on, before, after, between. */
81
87
  dateOperators?: readonly DateFilterOperator[];
88
+ /**
89
+ * `enum`: the conditions offered, in order (HAR-2631). Default: `is-any-of` alone, so the editor
90
+ * shows no condition control; with both, it shows one above the options.
91
+ */
92
+ enumOperators?: readonly EnumFilterOperator[];
82
93
  /** `number`: a unit shown after the value, e.g. "days". */
83
94
  unit?: string;
84
95
  /** `boolean`: words for true and false, e.g. Escalated / Not escalated. Default from labels. */
@@ -105,6 +116,8 @@ interface FilterSummaryLabels {
105
116
  more: string;
106
117
  /** An `enum` value equal to a preset: `{label}` (the preset), `{field}`. Default `{label}`. */
107
118
  preset?: string;
119
+ /** An `enum` value that excludes (`is-none-of`, HAR-2631): `{values}`. Default "not {values}" ("Status: not Open, Closed"). */
120
+ noneOf?: string;
108
121
  /** `date` conditions (HAR-1832), `{date}`. Default "{date}", "before {date}", "after {date}". */
109
122
  on?: string;
110
123
  before?: string;
@@ -114,6 +127,7 @@ declare const DEFAULT_FILTER_SUMMARY_LABELS: FilterSummaryLabels;
114
127
  declare const TEXT_FILTER_OPERATORS: readonly TextFilterOperator[];
115
128
  declare const NUMBER_FILTER_OPERATORS: readonly NumberFilterOperator[];
116
129
  declare const DATE_FILTER_OPERATORS: readonly DateFilterOperator[];
130
+ declare const ENUM_FILTER_OPERATORS: readonly EnumFilterOperator[];
117
131
  /** The empty value for a field kind (what Clear resets to). */
118
132
  declare function emptyFilterValue(kind: FilterValueKind): FilterValue;
119
133
  /** True when the value filters nothing (so the chip goes away). */
@@ -165,4 +179,4 @@ interface SummarizeFilterOptions {
165
179
  */
166
180
  declare function summarizeFilter(field: FilterField, input: FilterValue | undefined, options?: SummarizeFilterOptions): string;
167
181
 
168
- export { DATE_FILTER_OPERATORS, DEFAULT_FILTER_SUMMARY_LABELS, type DateFilterOperator, type FilterChoice, type FilterField, type FilterPreset, type FilterSummaryLabels, type FilterValue, type FilterValueKind, NUMBER_FILTER_OPERATORS, type NumberFilterOperator, type SummarizeFilterOptions, TEXT_FILTER_OPERATORS, type TextFilterOperator, dateFilterBounds, emptyFilterValue, isFilterEmpty, isUnresolvedChoice, matchFilterPreset, orderFilterRange, resolveFilterLabels, summarizeFilter };
182
+ export { DATE_FILTER_OPERATORS, DEFAULT_FILTER_SUMMARY_LABELS, type DateFilterOperator, ENUM_FILTER_OPERATORS, type EnumFilterOperator, type FilterChoice, type FilterField, type FilterPreset, type FilterSummaryLabels, type FilterValue, type FilterValueKind, NUMBER_FILTER_OPERATORS, type NumberFilterOperator, type SummarizeFilterOptions, TEXT_FILTER_OPERATORS, type TextFilterOperator, dateFilterBounds, emptyFilterValue, isFilterEmpty, isUnresolvedChoice, matchFilterPreset, orderFilterRange, resolveFilterLabels, summarizeFilter };
@@ -20,6 +20,7 @@ const DEFAULT_FILTER_SUMMARY_LABELS = {
20
20
  until: "until {to}",
21
21
  more: "{first} +{count}",
22
22
  preset: "{label}",
23
+ noneOf: "not {values}",
23
24
  on: "{date}",
24
25
  before: "before {date}",
25
26
  after: "after {date}"
@@ -27,6 +28,7 @@ const DEFAULT_FILTER_SUMMARY_LABELS = {
27
28
  const TEXT_FILTER_OPERATORS = ["contains", "not-contains", "is", "is-not", "starts-with"];
28
29
  const NUMBER_FILTER_OPERATORS = ["eq", "neq", "lt", "lte", "gt", "gte", "between"];
29
30
  const DATE_FILTER_OPERATORS = ["on", "before", "after", "between"];
31
+ const ENUM_FILTER_OPERATORS = ["is-any-of", "is-none-of"];
30
32
  function emptyFilterValue(kind) {
31
33
  switch (kind) {
32
34
  case "enum":
@@ -108,7 +110,7 @@ async function resolveFilterLabels(field, value) {
108
110
  return { kind: "reference", values: value.values.map((v) => resolved.has(v.value) ? { value: v.value, label: resolved.get(v.value) } : v) };
109
111
  }
110
112
  function matchFilterPreset(field, value) {
111
- if (value?.kind !== "enum" || !field.presets?.length) return void 0;
113
+ if (value?.kind !== "enum" || value.operator === "is-none-of" || !field.presets?.length) return void 0;
112
114
  const chosen = new Set(value.values);
113
115
  return field.presets.find((preset) => {
114
116
  const wanted = new Set(preset.values);
@@ -131,7 +133,10 @@ function summarizeFilter(field, input, options = {}) {
131
133
  const preset = matchFilterPreset(field, value);
132
134
  if (preset) return fill(labels.preset ?? DEFAULT_FILTER_SUMMARY_LABELS.preset, { label: preset.label, field: field.label });
133
135
  const byValue = new Map((field.options ?? []).map((o) => [o.value, o.label]));
134
- return `${field.label}: ${list(value.values.map((v) => byValue.get(v) ?? v))}`;
136
+ const named = list(value.values.map((v) => byValue.get(v) ?? v));
137
+ const condition = value.operator ?? (field.enumOperators?.length === 1 ? field.enumOperators[0] : "is-any-of");
138
+ if (condition === "is-none-of") return `${field.label}: ${fill(labels.noneOf ?? DEFAULT_FILTER_SUMMARY_LABELS.noneOf, { values: named, field: field.label })}`;
139
+ return `${field.label}: ${named}`;
135
140
  }
136
141
  case "reference":
137
142
  return `${field.label}: ${list(value.values.map((v) => v.label))}`;
@@ -155,6 +160,7 @@ function summarizeFilter(field, input, options = {}) {
155
160
  export {
156
161
  DATE_FILTER_OPERATORS,
157
162
  DEFAULT_FILTER_SUMMARY_LABELS,
163
+ ENUM_FILTER_OPERATORS,
158
164
  NUMBER_FILTER_OPERATORS,
159
165
  TEXT_FILTER_OPERATORS,
160
166
  dateFilterBounds,