@astronox/core 0.4.18 → 0.5.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": "@astronox/core",
3
- "version": "0.4.18",
3
+ "version": "0.5.0",
4
4
  "description": "Reusable Astro components, patterns, styles, and infrastructure for AstroNox sites.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -8,12 +8,15 @@ import {
8
8
  hasTableProgressive,
9
9
  setTableProgressiveFilters,
10
10
  } from './tableProgressiveController';
11
+ import {
12
+ matchesFilterSelection,
13
+ normalizeFilterSelection,
14
+ type TableFilterValues,
15
+ } from './tableFilterSelection';
11
16
  import { revealTableStart } from './tableViewport';
12
17
 
13
- type FilterValues = Readonly<Record<string, readonly string[]>>;
14
-
15
18
  type CachedFilterValues = {
16
- values: FilterValues;
19
+ values: TableFilterValues;
17
20
  normalizedSearch: readonly string[];
18
21
  };
19
22
 
@@ -92,7 +95,7 @@ export function initTableFilters(root: HTMLElement): () => void {
92
95
  .normalize('NFD')
93
96
  .replace(/\p{Diacritic}/gu, '')
94
97
  .toLocaleLowerCase(locale);
95
- const selections = new Map<string, string>();
98
+ const selections = new Map<string, readonly string[]>();
96
99
  const filterValuesCache = new WeakMap<HTMLElement, CachedFilterValues>();
97
100
  const rows: HTMLElement[] = [];
98
101
  let scheduledFrame: number | null = null;
@@ -118,7 +121,7 @@ export function initTableFilters(root: HTMLElement): () => void {
118
121
  const applyFilters = (): void => {
119
122
  const query = normalize(search?.value.trim() ?? '');
120
123
  const activeSelections = [...selections.entries()].filter(
121
- ([, value]) => value.length > 0,
124
+ ([, values]) => values.length > 0,
122
125
  );
123
126
  const hasExternalFilter =
124
127
  root.hasAttribute(externalFiltersAttribute) ||
@@ -144,8 +147,10 @@ export function initTableFilters(root: HTMLElement): () => void {
144
147
  query.length === 0 ||
145
148
  cached.normalizedSearch.some((value) => value.includes(query));
146
149
  const matchesSelections = activeSelections.every(
147
- ([field, selectedValue]) =>
148
- (cached.values[field] ?? []).includes(selectedValue),
150
+ ([field, selectedValues]) => {
151
+ const rowValues = cached.values[field] ?? [];
152
+ return matchesFilterSelection(rowValues, selectedValues);
153
+ },
149
154
  );
150
155
  const visible =
151
156
  matchesSearch &&
@@ -190,7 +195,9 @@ export function initTableFilters(root: HTMLElement): () => void {
190
195
 
191
196
  const field = target.dataset.tableFilterField;
192
197
  if (!field) return;
193
- selections.set(field, customEvent.detail.values[0] ?? '');
198
+ const values = normalizeFilterSelection(customEvent.detail.values);
199
+ if (values.length > 0) selections.set(field, values);
200
+ else selections.delete(field);
194
201
  revealTableStart(table);
195
202
  scheduleFilters();
196
203
  };
@@ -215,8 +222,10 @@ export function initTableFilters(root: HTMLElement): () => void {
215
222
 
216
223
  for (const field of fields) {
217
224
  const fieldName = field.dataset.tableFilterField;
218
- const selectedValue = getComboboxValues(field)[0];
219
- if (fieldName && selectedValue) selections.set(fieldName, selectedValue);
225
+ const selectedValues = normalizeFilterSelection(getComboboxValues(field));
226
+ if (fieldName && selectedValues.length > 0) {
227
+ selections.set(fieldName, selectedValues);
228
+ }
220
229
  }
221
230
  registerRows(
222
231
  Array.from(
@@ -248,12 +257,14 @@ export function initTableFilters(root: HTMLElement): () => void {
248
257
  };
249
258
  }
250
259
 
251
- function parseFilterValues(value: string | undefined): FilterValues {
260
+ function parseFilterValues(value: string | undefined): TableFilterValues {
252
261
  if (!value) return {};
253
262
 
254
263
  try {
255
264
  const parsed = JSON.parse(value);
256
- return parsed && typeof parsed === 'object' ? (parsed as FilterValues) : {};
265
+ return parsed && typeof parsed === 'object'
266
+ ? (parsed as TableFilterValues)
267
+ : {};
257
268
  } catch {
258
269
  return {};
259
270
  }
@@ -0,0 +1,47 @@
1
+ export type TableFilterValues = Readonly<Record<string, readonly string[]>>;
2
+
3
+ export function normalizeFilterSelection(
4
+ values: readonly string[],
5
+ ): readonly string[] {
6
+ return [...new Set(values.filter((value) => value.length > 0))].sort();
7
+ }
8
+
9
+ export function normalizeFilterSelections(
10
+ selections: TableFilterValues,
11
+ ): TableFilterValues {
12
+ const normalized: Record<string, readonly string[]> = {};
13
+
14
+ for (const [field, values] of Object.entries(selections)) {
15
+ const selection = normalizeFilterSelection(values);
16
+ if (selection.length > 0) normalized[field] = selection;
17
+ }
18
+
19
+ return normalized;
20
+ }
21
+
22
+ export function matchesFilterSelection(
23
+ values: readonly string[],
24
+ selection: readonly string[],
25
+ ): boolean {
26
+ return selection.some((value) => values.includes(value));
27
+ }
28
+
29
+ export function areFilterSelectionsEqual(
30
+ left: TableFilterValues,
31
+ right: TableFilterValues,
32
+ ): boolean {
33
+ const leftEntries = Object.entries(left);
34
+ const rightEntries = Object.entries(right);
35
+
36
+ return (
37
+ leftEntries.length === rightEntries.length &&
38
+ leftEntries.every(([field, values]) => {
39
+ const rightValues = right[field];
40
+ return (
41
+ rightValues !== undefined &&
42
+ values.length === rightValues.length &&
43
+ values.every((value, index) => value === rightValues[index])
44
+ );
45
+ })
46
+ );
47
+ }
@@ -3,6 +3,12 @@ import type {
3
3
  TableProgressiveModel,
4
4
  } from '../../display/contracts';
5
5
  import { tableProgressiveBatchSize } from '../../display/tableProgressive';
6
+ import {
7
+ areFilterSelectionsEqual,
8
+ matchesFilterSelection,
9
+ normalizeFilterSelections,
10
+ type TableFilterValues,
11
+ } from './tableFilterSelection';
6
12
  import { resolveTableScrollRoot, revealTableStart } from './tableViewport';
7
13
 
8
14
  const initializedAttribute = 'data-table-progressive-initialized';
@@ -20,7 +26,7 @@ export type TableRowsAppendedDetail = {
20
26
 
21
27
  export type TableProgressiveFilterState = {
22
28
  search: string;
23
- selections: Readonly<Record<string, string>>;
29
+ selections: TableFilterValues;
24
30
  };
25
31
 
26
32
  export type TableProgressiveSortState = {
@@ -250,9 +256,10 @@ export function initTableProgressive(root: HTMLElement): () => void {
250
256
  ) {
251
257
  return false;
252
258
  }
253
- return selections.every(([field, value]) =>
254
- (entry.filters?.[field] ?? []).includes(value),
255
- );
259
+ return selections.every(([field, selectedValues]) => {
260
+ const entryValues = entry.filters?.[field] ?? [];
261
+ return matchesFilterSelection(entryValues, selectedValues);
262
+ });
256
263
  });
257
264
 
258
265
  if (sortState) {
@@ -455,8 +462,9 @@ export function initTableProgressive(root: HTMLElement): () => void {
455
462
  progressive: true,
456
463
  refresh,
457
464
  setFilters(state) {
458
- if (areFilterStatesEqual(filterState, state)) return;
459
- filterState = state;
465
+ const normalizedState = normalizeFilterState(state);
466
+ if (areFilterStatesEqual(filterState, normalizedState)) return;
467
+ filterState = normalizedState;
460
468
  if (prepared) prepareWindow(0, 'refreshing');
461
469
  },
462
470
  setSort(state) {
@@ -656,15 +664,21 @@ function areFilterStatesEqual(
656
664
  left: TableProgressiveFilterState,
657
665
  right: TableProgressiveFilterState,
658
666
  ): boolean {
659
- if (left.search !== right.search) return false;
660
- const leftEntries = Object.entries(left.selections);
661
- const rightEntries = Object.entries(right.selections);
662
667
  return (
663
- leftEntries.length === rightEntries.length &&
664
- leftEntries.every(([key, value]) => right.selections[key] === value)
668
+ left.search === right.search &&
669
+ areFilterSelectionsEqual(left.selections, right.selections)
665
670
  );
666
671
  }
667
672
 
673
+ function normalizeFilterState(
674
+ state: TableProgressiveFilterState,
675
+ ): TableProgressiveFilterState {
676
+ return {
677
+ search: state.search,
678
+ selections: normalizeFilterSelections(state.selections),
679
+ };
680
+ }
681
+
668
682
  function areSortStatesEqual(
669
683
  left: TableProgressiveSortState,
670
684
  right: TableProgressiveSortState,