@apliteni/apliteni-ui 0.66.0 → 0.68.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.66.0",
3
+ "version": "0.68.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
@@ -483,8 +483,8 @@ declare function Segmented({ options, value, onChange, label, appearance, disabl
483
483
 
484
484
  type Filter = {
485
485
  id: string;
486
- label: string;
487
- value: string;
486
+ label: string; /** empty or absent while nothing is chosen; the chip then shows `label` */
487
+ value?: string;
488
488
  items: DropdownEntry[];
489
489
  disabled?: boolean;
490
490
  open?: boolean;
@@ -1938,6 +1938,7 @@ function Segmented({ options, value, onChange, label, appearance = "pill", disab
1938
1938
 
1939
1939
  // src/FilterBar.tsx
1940
1940
  import { useRef as useRef11, useState as useState7 } from "react";
1941
+ import { filterChipText, filterChipName, filterChipUnset } from "@apliteni/apliteni-ui";
1941
1942
  import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
1942
1943
  function FilterBar({ filters, label = "Filters", clearLabel = "Clear all filters", disabled = false, busy = false, onRemove, onClear, onChange }) {
1943
1944
  const bar = useRef11(null);
@@ -1983,11 +1984,10 @@ function FilterBar({ filters, label = "Filters", clearLabel = "Clear all filters
1983
1984
  /* @__PURE__ */ jsx23(
1984
1985
  Dropdown,
1985
1986
  {
1986
- label: filter.label,
1987
- value: filter.value,
1988
1987
  items: filter.items,
1989
1988
  variant: "select",
1990
- ariaLabel: `${filter.label}: ${filter.value}`,
1989
+ ariaLabel: filterChipName(filter),
1990
+ triggerContent: /* @__PURE__ */ jsx23("span", { className: filterChipUnset(filter) ? "ui-dropdown__value is-placeholder" : "ui-dropdown__value", children: filterChipText(filter) }),
1991
1991
  open: !blocked && !filter.disabled && (opened[filter.id] && opened[filter.id].against === filter.open ? opened[filter.id].open : !!filter.open),
1992
1992
  onOpenChange: (open) => setOpened((previous) => ({ ...previous, [filter.id]: { open, against: filter.open } })),
1993
1993
  onSelect: (value) => {
@@ -1,12 +1,17 @@
1
1
  import { button, esc } from './index.js';
2
2
  import { dropdown, wireDropdown } from './dropdown.js';
3
+ import { filterChipText, filterChipName, filterChipUnset } from '../logic/filter-bar.js';
3
4
 
4
5
  export function filterBar({ filters = [], label = 'Filters', clearLabel = 'Clear all filters', disabled = false, busy = false } = {}) {
5
6
  return `<fieldset class="ui-filter-bar" data-filter-bar${disabled || busy ? ' disabled' : ''}${busy ? ' aria-busy="true"' : ''}>`
6
7
  + `<legend class="ui-filter-bar__legend">${esc(label)}</legend>`
7
8
  + filters.map(filter => `<fieldset class="ui-filter-bar__chip" data-filter-id="${esc(filter.id)}"${filter.disabled ? ' disabled' : ''}>`
8
9
  + `<legend class="ui-filter-bar__legend">${esc(filter.label)}</legend>`
9
- + dropdown({ label: filter.label, value: filter.value, items: filter.items || [], variant: 'select', ariaLabel: `${filter.label}: ${filter.value}`, open: !!filter.open && !disabled && !busy && !filter.disabled })
10
+ + dropdown({ items: filter.items || [], variant: 'select',
11
+ // The chip prints one line; the field's name reaches a reader through the
12
+ // trigger's name and the chip's own legend. why: docs/specification.md#dense-financial-tables
13
+ triggerContent: `<span class="${filterChipUnset(filter) ? 'ui-dropdown__value is-placeholder' : 'ui-dropdown__value'}">${esc(filterChipText(filter))}</span>`,
14
+ ariaLabel: filterChipName(filter), open: !!filter.open && !disabled && !busy && !filter.disabled })
10
15
  + `<button type="button" class="ui-filter-bar__remove" data-filter-remove aria-label="${esc(`Remove ${filter.label} filter`)}">×</button></fieldset>`).join('')
11
16
  + `<span data-filter-clear>${button({ label: clearLabel, size: 'sm', variant: 'ghost', disabled: !filters.length })}</span></fieldset>`;
12
17
  }
@@ -46,7 +51,18 @@ export function initFilterBar(host, options = {}) {
46
51
  if (item.getAttribute('aria-disabled') !== 'true') {
47
52
  const id = chip?.dataset.filterId, value = item.dataset.value;
48
53
  // Dropdown completes its own close/focus before the consumer replaces the markup.
49
- queueMicrotask(() => emit('ui-filter-change', { id, value }));
54
+ queueMicrotask(() => {
55
+ // Dropdown writes the picked row's label into the trigger; the name it is
56
+ // read by follows it, so the two cannot disagree while the consumer answers.
57
+ const filter = (current.filters || []).find(f => f.id === id);
58
+ const trigger = chip?.querySelector('[data-dropdown-trigger]');
59
+ const span = trigger?.querySelector('.ui-dropdown__value');
60
+ const shown = span?.textContent;
61
+ if (filter && trigger) trigger.setAttribute('aria-label', filterChipName({ ...filter, value: shown }));
62
+ // A chip that was empty stops being a placeholder the moment it shows a pick.
63
+ if (span) span.classList.toggle('is-placeholder', filterChipUnset({ value: shown }));
64
+ emit('ui-filter-change', { id, value });
65
+ });
50
66
  }
51
67
  }
52
68
  };
package/src/index.d.ts CHANGED
@@ -7,6 +7,9 @@ export declare function numericValue(opts?: Record<string, unknown>): string;
7
7
  export declare function deltaValue(opts?: Record<string, unknown>): string;
8
8
  export declare function filterBar(opts?: Record<string, unknown>): string;
9
9
  export declare function initFilterBar(host: Element, opts?: Record<string, unknown>): { update: (opts: Record<string, unknown>) => void; destroy: () => void };
10
+ export declare function filterChipText(filter?: { label?: string; value?: string }): string;
11
+ export declare function filterChipName(filter?: { label?: string; value?: string }): string;
12
+ export declare function filterChipUnset(filter?: { value?: string }): boolean;
10
13
  export declare function segmentedNextIndex(key: string, index: number, length: number): number | null;
11
14
  export declare function icon(name: string, className?: string): string;
12
15
  export declare function snippet(opts?: Record<string, unknown>): string;
package/src/index.js CHANGED
@@ -30,6 +30,7 @@ export * from './components/filter-bar.js';
30
30
  export { dropdownMatch, dropdownFiltering } from './logic/dropdown.js';
31
31
  export { SCORE, rankGroups, rankCommands, scoreCommand, paletteHotkey } from './logic/command-palette.js';
32
32
  export { segmentedNextIndex } from './logic/segmented.js';
33
+ export { filterChipText, filterChipName, filterChipUnset } from './logic/filter-bar.js';
33
34
  export { PAGE_SIZES, DEFAULT_PAGE_SIZE } from './logic/pagination.js';
34
35
  export { calloutIcons } from './logic/callout.js';
35
36
  export { formatNumericValue, formatDeltaValue } from './logic/table-values.js';
@@ -0,0 +1,29 @@
1
+ /* What a filter chip shows, what it is called, and when it counts as unset.
2
+ * The chip prints the chosen value by itself — never the field's name beside it
3
+ * — and the field's name stands in while nothing is chosen. Both faces ask
4
+ * these, so a chip reads the same in vanilla and in React.
5
+ *
6
+ * `value` is display text: it is the chip's only visible line, so a consumer
7
+ * answers a change with text a reader can read, not a raw code.
8
+ * why: docs/specification.md#dense-financial-tables
9
+ */
10
+
11
+ const text = (v) => (v == null ? '' : String(v));
12
+
13
+ /** No value chosen: absent, null or the empty string. */
14
+ export function filterChipUnset({ value } = {}) {
15
+ return text(value) === '';
16
+ }
17
+
18
+ /** The one line the chip prints: the value, or the field's name while unset. */
19
+ export function filterChipText(filter = {}) {
20
+ return filterChipUnset(filter) ? text(filter.label) : text(filter.value);
21
+ }
22
+
23
+ /** What a reader hears: the field, and its value or the empty state. A filter
24
+ * with no label is named by its value alone — "undefined" is not a name. */
25
+ export function filterChipName(filter = {}) {
26
+ const field = text(filter.label);
27
+ if (!field) return text(filter.value);
28
+ return filterChipUnset(filter) ? `${field}: any` : `${field}: ${text(filter.value)}`;
29
+ }
@@ -7,4 +7,19 @@
7
7
  .ui-filter-bar:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
8
8
  .ui-filter-bar button:disabled { /* muted-ink: state — disabled filters */ color: var(--disabled-ink); cursor: not-allowed; }
9
9
 
10
- .ui-filter-bar .ui-dropdown__pre, .ui-filter-bar .ui-dropdown__value { color: inherit; }
10
+ .ui-filter-bar .ui-dropdown__value { color: inherit; }
11
+ /* A chip with nothing chosen prints the field's name in the slot the value will
12
+ take. `:enabled` leaves a disabled chip its own ink. why: guidelines/labels-and-titles.md */
13
+ .ui-filter-bar button:enabled .ui-dropdown__value.is-placeholder { /* muted-ink: placeholder — a filter chip with nothing chosen */ color: var(--muted); }
14
+
15
+ /* A chip's panel is as wide as the chip's trigger, never the 240px floor
16
+ .ui-dropdown__panel gives a standalone dropdown. A panel is positioned at its
17
+ trigger's inline start, so on a narrow row a later chip put its panel past the
18
+ screen — shut as well as open, a `visibility: hidden` box still being laid out.
19
+ The percentage resolves against .ui-dropdown, the panel's containing block,
20
+ which is the trigger's width because the trigger is its one in-flow child.
21
+ `overflow-wrap` keeps that a bound rather than a clip: a panel is
22
+ `overflow: visible`, so a value with no break opportunity would run out of it
23
+ and off the page. It inherits, reaching an option's label and description.
24
+ why: docs/specification.md#a-filter-row-holds-its-panels */
25
+ .ui-filter-bar .ui-dropdown__panel { min-width: 100%; max-width: 100%; overflow-wrap: anywhere; }