@panneau/lists 5.0.0-alpha.0 → 5.0.0-alpha.1

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.
@@ -4,10 +4,14 @@ import Filters_module_default from "./Filters.module.js";
4
4
  import { c } from "react/compiler-runtime";
5
5
  import { Fieldset, Select, Switch, TextInput, usePanneauIntl } from "@panneau/ui";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { useFieldsContext } from "@panneau/fields";
8
+ import { useEffect } from "react";
7
9
  import { DateField } from "@panneau/fields/date";
8
- import { RelationField } from "@panneau/fields/relation";
10
+ import { RelationField, useRelationReferences } from "@panneau/fields/relation";
11
+ import { isFilterRange } from "@panneau/protocol/runtime";
9
12
  //#region src/filters/controls.tsx
10
13
  function asStrings(value) {
14
+ if (isFilterRange(value)) return [];
11
15
  if (Array.isArray(value)) return value.map(String);
12
16
  return value === void 0 || value === null || value === "" ? [] : [String(value)];
13
17
  }
@@ -206,8 +210,8 @@ function DateFilter(t0) {
206
210
  return t3;
207
211
  }
208
212
  /**
209
- * A range of days, sent as `[from, to]` (`filter[published_at][]=2026-01-01&…`). An open end is an
210
- * empty string.
213
+ * A range of days, sent as its bounds (`filter[published_at][from]=2026-01-01&…[to]=…`). Either end
214
+ * can be open: it is left out.
211
215
  */
212
216
  function DateRangeFilter(t0) {
213
217
  const $ = c(26);
@@ -215,85 +219,88 @@ function DateRangeFilter(t0) {
215
219
  const intl = usePanneauIntl();
216
220
  let t1;
217
221
  if ($[0] !== value) {
218
- t1 = asStrings(value);
222
+ t1 = isFilterRange(value) ? value : {};
219
223
  $[0] = value;
220
224
  $[1] = t1;
221
225
  } else t1 = $[1];
222
- const [t2, t3] = t1;
223
- const from = t2 === void 0 ? "" : t2;
224
- const to = t3 === void 0 ? "" : t3;
225
- let t4;
226
+ const range = t1;
227
+ const from = range.from !== void 0 ? String(range.from) : "";
228
+ const to = range.to !== void 0 ? String(range.to) : "";
229
+ let t2;
226
230
  if ($[2] !== onChange) {
227
- t4 = (nextFrom, nextTo) => onChange(nextFrom === "" && nextTo === "" ? void 0 : [nextFrom, nextTo]);
231
+ t2 = (nextFrom, nextTo) => onChange(nextFrom === "" && nextTo === "" ? void 0 : {
232
+ ...nextFrom !== "" ? { from: nextFrom } : {},
233
+ ...nextTo !== "" ? { to: nextTo } : {}
234
+ });
228
235
  $[2] = onChange;
229
- $[3] = t4;
230
- } else t4 = $[3];
231
- const set = t4;
232
- let t5;
236
+ $[3] = t2;
237
+ } else t2 = $[3];
238
+ const set = t2;
239
+ let t3;
233
240
  if ($[4] !== intl) {
234
- t5 = intl.formatMessage(messages.from);
241
+ t3 = intl.formatMessage(messages.from);
235
242
  $[4] = intl;
236
- $[5] = t5;
237
- } else t5 = $[5];
238
- const t6 = from !== "" ? from : null;
239
- let t7;
243
+ $[5] = t3;
244
+ } else t3 = $[5];
245
+ const t4 = from !== "" ? from : null;
246
+ let t5;
240
247
  if ($[6] !== set || $[7] !== to) {
241
- t7 = (next) => set(next ?? "", to);
248
+ t5 = (next) => set(next ?? "", to);
242
249
  $[6] = set;
243
250
  $[7] = to;
244
- $[8] = t7;
245
- } else t7 = $[8];
246
- let t8;
247
- if ($[9] !== t5 || $[10] !== t6 || $[11] !== t7) {
248
- t8 = /* @__PURE__ */ jsx(DateField, {
249
- label: t5,
250
- value: t6,
251
- onChange: t7
251
+ $[8] = t5;
252
+ } else t5 = $[8];
253
+ let t6;
254
+ if ($[9] !== t3 || $[10] !== t4 || $[11] !== t5) {
255
+ t6 = /* @__PURE__ */ jsx(DateField, {
256
+ label: t3,
257
+ value: t4,
258
+ onChange: t5
252
259
  });
253
- $[9] = t5;
254
- $[10] = t6;
255
- $[11] = t7;
256
- $[12] = t8;
257
- } else t8 = $[12];
258
- let t9;
260
+ $[9] = t3;
261
+ $[10] = t4;
262
+ $[11] = t5;
263
+ $[12] = t6;
264
+ } else t6 = $[12];
265
+ let t7;
259
266
  if ($[13] !== intl) {
260
- t9 = intl.formatMessage(messages.to);
267
+ t7 = intl.formatMessage(messages.to);
261
268
  $[13] = intl;
262
- $[14] = t9;
263
- } else t9 = $[14];
264
- const t10 = to !== "" ? to : null;
265
- let t11;
269
+ $[14] = t7;
270
+ } else t7 = $[14];
271
+ const t8 = to !== "" ? to : null;
272
+ let t9;
266
273
  if ($[15] !== from || $[16] !== set) {
267
- t11 = (next_0) => set(from, next_0 ?? "");
274
+ t9 = (next_0) => set(from, next_0 ?? "");
268
275
  $[15] = from;
269
276
  $[16] = set;
270
- $[17] = t11;
271
- } else t11 = $[17];
272
- let t12;
273
- if ($[18] !== t10 || $[19] !== t11 || $[20] !== t9) {
274
- t12 = /* @__PURE__ */ jsx(DateField, {
275
- label: t9,
276
- value: t10,
277
- onChange: t11
277
+ $[17] = t9;
278
+ } else t9 = $[17];
279
+ let t10;
280
+ if ($[18] !== t7 || $[19] !== t8 || $[20] !== t9) {
281
+ t10 = /* @__PURE__ */ jsx(DateField, {
282
+ label: t7,
283
+ value: t8,
284
+ onChange: t9
278
285
  });
279
- $[18] = t10;
280
- $[19] = t11;
286
+ $[18] = t7;
287
+ $[19] = t8;
281
288
  $[20] = t9;
282
- $[21] = t12;
283
- } else t12 = $[21];
284
- let t13;
285
- if ($[22] !== label || $[23] !== t12 || $[24] !== t8) {
286
- t13 = /* @__PURE__ */ jsxs(Fieldset, {
289
+ $[21] = t10;
290
+ } else t10 = $[21];
291
+ let t11;
292
+ if ($[22] !== label || $[23] !== t10 || $[24] !== t6) {
293
+ t11 = /* @__PURE__ */ jsxs(Fieldset, {
287
294
  legend: label,
288
295
  className: Filters_module_default.range,
289
- children: [t8, t12]
296
+ children: [t6, t10]
290
297
  });
291
298
  $[22] = label;
292
- $[23] = t12;
293
- $[24] = t8;
294
- $[25] = t13;
295
- } else t13 = $[25];
296
- return t13;
299
+ $[23] = t10;
300
+ $[24] = t6;
301
+ $[25] = t11;
302
+ } else t11 = $[25];
303
+ return t11;
297
304
  }
298
305
  /**
299
306
  * An item of another resource (`resource`), or several with `multiple`, picked with the relation
@@ -301,95 +308,114 @@ function DateRangeFilter(t0) {
301
308
  * chips.
302
309
  */
303
310
  function RelationFilter(t0) {
304
- const $ = c(23);
311
+ const $ = c(27);
305
312
  const { filter, label, value, onChange, onLabel, getValueLabel } = t0;
306
- let t1;
313
+ const resource = filter.resource ?? filter.id;
314
+ const t1 = useFieldsContext().relationSource;
315
+ let t2;
307
316
  if ($[0] !== getValueLabel || $[1] !== value) {
308
- let t2;
317
+ let t3;
309
318
  if ($[3] !== getValueLabel) {
310
- t2 = (id) => ({
319
+ t3 = (id) => ({
311
320
  id,
312
321
  label: getValueLabel(id) ?? id
313
322
  });
314
323
  $[3] = getValueLabel;
315
- $[4] = t2;
316
- } else t2 = $[4];
317
- t1 = asStrings(value).map(t2);
324
+ $[4] = t3;
325
+ } else t3 = $[4];
326
+ t2 = asStrings(value).map(t3);
318
327
  $[0] = getValueLabel;
319
328
  $[1] = value;
320
- $[2] = t1;
321
- } else t1 = $[2];
322
- const references = t1;
323
- let t2;
329
+ $[2] = t2;
330
+ } else t2 = $[2];
331
+ const references = useRelationReferences(t1, resource, t2);
332
+ let t3;
324
333
  if ($[5] !== onLabel) {
325
- t2 = (next) => next.forEach((reference) => onLabel(reference.id, reference.label));
334
+ t3 = (next) => next.forEach((reference) => onLabel(reference.id, reference.label));
326
335
  $[5] = onLabel;
327
- $[6] = t2;
328
- } else t2 = $[6];
329
- const remember = t2;
336
+ $[6] = t3;
337
+ } else t3 = $[6];
338
+ const remember = t3;
339
+ let t4;
340
+ let t5;
341
+ if ($[7] !== onLabel || $[8] !== references) {
342
+ t4 = () => {
343
+ references.filter(_temp2).forEach((reference_1) => onLabel(reference_1.id, reference_1.label));
344
+ };
345
+ t5 = [references, onLabel];
346
+ $[7] = onLabel;
347
+ $[8] = references;
348
+ $[9] = t4;
349
+ $[10] = t5;
350
+ } else {
351
+ t4 = $[9];
352
+ t5 = $[10];
353
+ }
354
+ useEffect(t4, t5);
330
355
  if (filter.multiple === true) {
331
- const t3 = filter.resource ?? filter.id;
332
- let t4;
333
- if ($[7] !== onChange || $[8] !== remember) {
334
- t4 = (next_0) => {
356
+ let t6;
357
+ if ($[11] !== onChange || $[12] !== remember) {
358
+ t6 = (next_0) => {
335
359
  remember(next_0 ?? []);
336
- const ids = next_0 !== null && next_0.length > 0 ? next_0.map(_temp2) : void 0;
360
+ const ids = next_0 !== null && next_0.length > 0 ? next_0.map(_temp3) : void 0;
337
361
  onChange(ids);
338
362
  };
339
- $[7] = onChange;
340
- $[8] = remember;
341
- $[9] = t4;
342
- } else t4 = $[9];
343
- let t5;
344
- if ($[10] !== label || $[11] !== references || $[12] !== t3 || $[13] !== t4) {
345
- t5 = /* @__PURE__ */ jsx(RelationField, {
363
+ $[11] = onChange;
364
+ $[12] = remember;
365
+ $[13] = t6;
366
+ } else t6 = $[13];
367
+ let t7;
368
+ if ($[14] !== label || $[15] !== references || $[16] !== resource || $[17] !== t6) {
369
+ t7 = /* @__PURE__ */ jsx(RelationField, {
346
370
  multiple: true,
347
371
  label,
348
- resource: t3,
372
+ resource,
349
373
  value: references,
350
- onChange: t4,
374
+ onChange: t6,
351
375
  className: Filters_module_default.control
352
376
  });
353
- $[10] = label;
354
- $[11] = references;
355
- $[12] = t3;
356
- $[13] = t4;
357
- $[14] = t5;
358
- } else t5 = $[14];
359
- return t5;
377
+ $[14] = label;
378
+ $[15] = references;
379
+ $[16] = resource;
380
+ $[17] = t6;
381
+ $[18] = t7;
382
+ } else t7 = $[18];
383
+ return t7;
360
384
  }
361
- const t3 = filter.resource ?? filter.id;
362
- const t4 = references[0] ?? null;
363
- let t5;
364
- if ($[15] !== onChange || $[16] !== remember) {
365
- t5 = (next_1) => {
385
+ const t6 = references[0] ?? null;
386
+ let t7;
387
+ if ($[19] !== onChange || $[20] !== remember) {
388
+ t7 = (next_1) => {
366
389
  remember(next_1 !== null ? [next_1] : []);
367
390
  onChange(next_1?.id);
368
391
  };
369
- $[15] = onChange;
370
- $[16] = remember;
371
- $[17] = t5;
372
- } else t5 = $[17];
373
- let t6;
374
- if ($[18] !== label || $[19] !== t3 || $[20] !== t4 || $[21] !== t5) {
375
- t6 = /* @__PURE__ */ jsx(RelationField, {
392
+ $[19] = onChange;
393
+ $[20] = remember;
394
+ $[21] = t7;
395
+ } else t7 = $[21];
396
+ let t8;
397
+ if ($[22] !== label || $[23] !== resource || $[24] !== t6 || $[25] !== t7) {
398
+ t8 = /* @__PURE__ */ jsx(RelationField, {
376
399
  label,
377
- resource: t3,
378
- value: t4,
379
- onChange: t5,
400
+ resource,
401
+ value: t6,
402
+ onChange: t7,
380
403
  className: Filters_module_default.control
381
404
  });
382
- $[18] = label;
383
- $[19] = t3;
384
- $[20] = t4;
385
- $[21] = t5;
386
- $[22] = t6;
387
- } else t6 = $[22];
388
- return t6;
405
+ $[22] = label;
406
+ $[23] = resource;
407
+ $[24] = t6;
408
+ $[25] = t7;
409
+ $[26] = t8;
410
+ } else t8 = $[26];
411
+ return t8;
389
412
  }
390
413
  /** The built-in filter controls, by filter type. Unknown types are text filters. */
414
+ function _temp3(reference_2) {
415
+ return reference_2.id;
416
+ }
391
417
  function _temp2(reference_0) {
392
- return reference_0.id;
418
+ return reference_0.label !== reference_0.id;
393
419
  }
394
420
  var builtInFilters = {
395
421
  text: TextFilter,
@@ -1 +1 @@
1
- {"version":3,"file":"controls.js","names":["label","draft","setDraft","flush","options","placeholder","option","next","references","reference","_c","styles"],"sources":["../../src/filters/controls.tsx"],"sourcesContent":["import { DateField } from '@panneau/fields/date';\nimport { RelationField } from '@panneau/fields/relation';\nimport type { FilterValue } from '@panneau/protocol';\nimport { Fieldset, type OptionValue, Select, Switch, TextInput, usePanneauIntl } from '@panneau/ui';\n\nimport { messages } from '../messages';\nimport type { FilterControlProps, FilterEntry } from '../types';\nimport { useDraft } from '../useDraft';\nimport styles from './Filters.module.css';\n\nfunction asStrings(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.map(String);\n }\n return value === undefined || value === null || value === '' ? [] : [String(value)];\n}\n\n/** A text filter: the list changes once the user stops typing. */\nexport function TextFilter({ label, value, onChange }: FilterControlProps) {\n const current = typeof value === 'string' || typeof value === 'number' ? String(value) : '';\n const { draft, setDraft, flush } = useDraft(current, (next) =>\n onChange(next !== '' ? next : undefined),\n );\n return (\n <TextInput\n label={label}\n value={draft}\n onChange={setDraft}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n flush();\n }\n }}\n onBlur={flush}\n className={styles.control}\n />\n );\n}\n\n/** A choice among the options, or several with `multiple`. */\nexport function SelectFilter({ filter, label, options, value, onChange }: FilterControlProps) {\n const intl = usePanneauIntl();\n const selected = asStrings(value);\n // Values from the URL are strings: match them to the options' values.\n const toOptions = (values: readonly string[]): OptionValue[] =>\n options\n .filter((option) => values.includes(String(option.value)))\n .map((option) => option.value);\n const placeholder = intl.formatMessage(messages.any);\n if (filter.multiple === true) {\n return (\n <Select\n multiple\n label={label}\n options={options}\n placeholder={placeholder}\n value={toOptions(selected)}\n onChange={(next) => onChange(next.length > 0 ? next : undefined)}\n className={styles.control}\n />\n );\n }\n return (\n <Select\n label={label}\n options={options}\n placeholder={placeholder}\n value={toOptions(selected)[0] ?? null}\n onChange={(next) => onChange(next ?? undefined)}\n className={styles.control}\n />\n );\n}\n\n/** On or off: on filters with `true`, off removes the filter. */\nexport function ToggleFilter({ label, value, onChange }: FilterControlProps) {\n return (\n <Switch\n isSelected={String(value) === 'true'}\n onChange={(checked) => onChange(checked ? true : undefined)}\n className={styles.toggle}\n >\n {label}\n </Switch>\n );\n}\n\n/** A day (`2026-10-15`). */\nexport function DateFilter({ label, value, onChange }: FilterControlProps) {\n return (\n <DateField\n label={label}\n value={typeof value === 'string' && value !== '' ? value : null}\n onChange={(next) => onChange(next ?? undefined)}\n className={styles.control}\n />\n );\n}\n\n/**\n * A range of days, sent as `[from, to]` (`filter[published_at][]=2026-01-01&…`). An open end is an\n * empty string.\n */\nexport function DateRangeFilter({ label, value, onChange }: FilterControlProps) {\n const intl = usePanneauIntl();\n const [from = '', to = ''] = asStrings(value);\n const set = (nextFrom: string, nextTo: string) =>\n onChange(nextFrom === '' && nextTo === '' ? undefined : [nextFrom, nextTo]);\n return (\n <Fieldset legend={label} className={styles.range}>\n <DateField\n label={intl.formatMessage(messages.from)}\n value={from !== '' ? from : null}\n onChange={(next) => set(next ?? '', to)}\n />\n <DateField\n label={intl.formatMessage(messages.to)}\n value={to !== '' ? to : null}\n onChange={(next) => set(from, next ?? '')}\n />\n </Fieldset>\n );\n}\n\n/**\n * An item of another resource (`resource`), or several with `multiple`, picked with the relation\n * source of the fields context. The filter keeps their ids; their labels are remembered for the\n * chips.\n */\nexport function RelationFilter({\n filter,\n label,\n value,\n onChange,\n onLabel,\n getValueLabel,\n}: FilterControlProps) {\n const references = asStrings(value).map((id) => ({ id, label: getValueLabel(id) ?? id }));\n const remember = (next: readonly { id: string; label: string }[]) =>\n next.forEach((reference) => onLabel(reference.id, reference.label));\n if (filter.multiple === true) {\n return (\n <RelationField\n multiple\n label={label}\n resource={filter.resource ?? filter.id}\n value={references}\n onChange={(next) => {\n remember(next ?? []);\n const ids: FilterValue | undefined =\n next !== null && next.length > 0\n ? next.map((reference) => reference.id)\n : undefined;\n onChange(ids);\n }}\n className={styles.control}\n />\n );\n }\n return (\n <RelationField\n label={label}\n resource={filter.resource ?? filter.id}\n value={references[0] ?? null}\n onChange={(next) => {\n remember(next !== null ? [next] : []);\n onChange(next?.id);\n }}\n className={styles.control}\n />\n );\n}\n\n/** The built-in filter controls, by filter type. Unknown types are text filters. */\nexport const builtInFilters: Readonly<Record<string, FilterEntry>> = {\n text: TextFilter,\n select: SelectFilter,\n toggle: ToggleFilter,\n date: DateFilter,\n 'date-range': DateRangeFilter,\n relation: RelationFilter,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"controls.js","names":["label","draft","setDraft","flush","options","placeholder","option","reference","next","resource","references","_c","styles"],"sources":["../../src/filters/controls.tsx"],"sourcesContent":["import { useEffect } from 'react';\n\nimport { useFieldsContext } from '@panneau/fields';\nimport { DateField } from '@panneau/fields/date';\nimport { RelationField, useRelationReferences } from '@panneau/fields/relation';\nimport type { FilterValue } from '@panneau/protocol';\nimport { isFilterRange } from '@panneau/protocol/runtime';\nimport { Fieldset, type OptionValue, Select, Switch, TextInput, usePanneauIntl } from '@panneau/ui';\n\nimport { messages } from '../messages';\nimport type { FilterControlProps, FilterEntry } from '../types';\nimport { useDraft } from '../useDraft';\nimport styles from './Filters.module.css';\n\nfunction asStrings(value: unknown): string[] {\n if (isFilterRange(value)) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map(String);\n }\n return value === undefined || value === null || value === '' ? [] : [String(value)];\n}\n\n/** A text filter: the list changes once the user stops typing. */\nexport function TextFilter({ label, value, onChange }: FilterControlProps) {\n const current = typeof value === 'string' || typeof value === 'number' ? String(value) : '';\n const { draft, setDraft, flush } = useDraft(current, (next) =>\n onChange(next !== '' ? next : undefined),\n );\n return (\n <TextInput\n label={label}\n value={draft}\n onChange={setDraft}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n flush();\n }\n }}\n onBlur={flush}\n className={styles.control}\n />\n );\n}\n\n/** A choice among the options, or several with `multiple`. */\nexport function SelectFilter({ filter, label, options, value, onChange }: FilterControlProps) {\n const intl = usePanneauIntl();\n const selected = asStrings(value);\n // Values from the URL are strings: match them to the options' values.\n const toOptions = (values: readonly string[]): OptionValue[] =>\n options\n .filter((option) => values.includes(String(option.value)))\n .map((option) => option.value);\n const placeholder = intl.formatMessage(messages.any);\n if (filter.multiple === true) {\n return (\n <Select\n multiple\n label={label}\n options={options}\n placeholder={placeholder}\n value={toOptions(selected)}\n onChange={(next) => onChange(next.length > 0 ? next : undefined)}\n className={styles.control}\n />\n );\n }\n return (\n <Select\n label={label}\n options={options}\n placeholder={placeholder}\n value={toOptions(selected)[0] ?? null}\n onChange={(next) => onChange(next ?? undefined)}\n className={styles.control}\n />\n );\n}\n\n/** On or off: on filters with `true`, off removes the filter. */\nexport function ToggleFilter({ label, value, onChange }: FilterControlProps) {\n return (\n <Switch\n isSelected={String(value) === 'true'}\n onChange={(checked) => onChange(checked ? true : undefined)}\n className={styles.toggle}\n >\n {label}\n </Switch>\n );\n}\n\n/** A day (`2026-10-15`). */\nexport function DateFilter({ label, value, onChange }: FilterControlProps) {\n return (\n <DateField\n label={label}\n value={typeof value === 'string' && value !== '' ? value : null}\n onChange={(next) => onChange(next ?? undefined)}\n className={styles.control}\n />\n );\n}\n\n/**\n * A range of days, sent as its bounds (`filter[published_at][from]=2026-01-01&…[to]=…`). Either end\n * can be open: it is left out.\n */\nexport function DateRangeFilter({ label, value, onChange }: FilterControlProps) {\n const intl = usePanneauIntl();\n const range = isFilterRange(value) ? value : {};\n const from = range.from !== undefined ? String(range.from) : '';\n const to = range.to !== undefined ? String(range.to) : '';\n const set = (nextFrom: string, nextTo: string) =>\n onChange(\n nextFrom === '' && nextTo === ''\n ? undefined\n : {\n ...(nextFrom !== '' ? { from: nextFrom } : {}),\n ...(nextTo !== '' ? { to: nextTo } : {}),\n },\n );\n return (\n <Fieldset legend={label} className={styles.range}>\n <DateField\n label={intl.formatMessage(messages.from)}\n value={from !== '' ? from : null}\n onChange={(next) => set(next ?? '', to)}\n />\n <DateField\n label={intl.formatMessage(messages.to)}\n value={to !== '' ? to : null}\n onChange={(next) => set(from, next ?? '')}\n />\n </Fieldset>\n );\n}\n\n/**\n * An item of another resource (`resource`), or several with `multiple`, picked with the relation\n * source of the fields context. The filter keeps their ids; their labels are remembered for the\n * chips.\n */\nexport function RelationFilter({\n filter,\n label,\n value,\n onChange,\n onLabel,\n getValueLabel,\n}: FilterControlProps) {\n const resource = filter.resource ?? filter.id;\n // Ids from the URL have no label: the relation source resolves them, for the picker and chips.\n const references = useRelationReferences(\n useFieldsContext().relationSource,\n resource,\n asStrings(value).map((id) => ({ id, label: getValueLabel(id) ?? id })),\n );\n const remember = (next: readonly { id: string; label: string }[]) =>\n next.forEach((reference) => onLabel(reference.id, reference.label));\n useEffect(() => {\n references\n .filter((reference) => reference.label !== reference.id)\n .forEach((reference) => onLabel(reference.id, reference.label));\n }, [references, onLabel]);\n if (filter.multiple === true) {\n return (\n <RelationField\n multiple\n label={label}\n resource={resource}\n value={references}\n onChange={(next) => {\n remember(next ?? []);\n const ids: FilterValue | undefined =\n next !== null && next.length > 0\n ? next.map((reference) => reference.id)\n : undefined;\n onChange(ids);\n }}\n className={styles.control}\n />\n );\n }\n return (\n <RelationField\n label={label}\n resource={resource}\n value={references[0] ?? null}\n onChange={(next) => {\n remember(next !== null ? [next] : []);\n onChange(next?.id);\n }}\n className={styles.control}\n />\n );\n}\n\n/** The built-in filter controls, by filter type. Unknown types are text filters. */\nexport const builtInFilters: Readonly<Record<string, FilterEntry>> = {\n text: TextFilter,\n select: SelectFilter,\n toggle: ToggleFilter,\n date: DateFilter,\n 'date-range': DateRangeFilter,\n relation: RelationFilter,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/filters/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,6EAA6E;AAC7E,wBAAgB,cAAc,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,MAAM,CAMR;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,KAAK,GAAG,IAAI,CAMd;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,SAAS,MAAM,EAAE,CAEnB;AAcD,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,EACI,IAAI,EACJ,OAAO,EACP,aAAa,GAChB,EAAE;IACC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACnD,GACF,MAAM,CAqBR"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/filters/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,6EAA6E;AAC7E,wBAAgB,cAAc,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,MAAM,CAMR;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,KAAK,GAAG,IAAI,CAMd;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,GACzB,SAAS,MAAM,EAAE,CAEnB;AAcD,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,EACI,IAAI,EACJ,OAAO,EACP,aAAa,GAChB,EAAE;IACC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACnD,GACF,MAAM,CAuBR"}
@@ -1,5 +1,6 @@
1
1
  import { messages } from "../messages.js";
2
2
  import { getFieldOptions, getOptionLabel } from "../values.js";
3
+ import { isFilterRange } from "@panneau/protocol/runtime";
3
4
  //#region src/filters/format.ts
4
5
  /** The label of a filter: its `label`, the label of its field, or its id. */
5
6
  function getFilterLabel(filter, columns, fields) {
@@ -27,7 +28,9 @@ function formatFilterValue(filter, value, { intl, options, getValueLabel }) {
27
28
  case "toggle": return intl.formatMessage(String(value) === "true" ? messages.yes : messages.no);
28
29
  case "date": return formatDay(String(value), intl);
29
30
  case "date-range": {
30
- const [from = "", to = ""] = values.map(String);
31
+ const range = isFilterRange(value) ? value : {};
32
+ const from = range.from !== void 0 ? String(range.from) : "";
33
+ const to = range.to !== void 0 ? String(range.to) : "";
31
34
  return intl.formatMessage(messages.dateRange, {
32
35
  from: from !== "" ? formatDay(from, intl) : "…",
33
36
  to: to !== "" ? formatDay(to, intl) : "…"
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":[],"sources":["../../src/filters/format.ts"],"sourcesContent":["import type { IntlShape } from 'react-intl';\n\nimport type { Field, Filter, Option } from '@panneau/protocol';\n\nimport { messages } from '../messages';\nimport type { ListColumn } from '../types';\nimport { getFieldOptions, getOptionLabel } from '../values';\n\n/** The label of a filter: its `label`, the label of its field, or its id. */\nexport function getFilterLabel(\n filter: Filter,\n columns: readonly ListColumn[],\n fields: readonly Field[],\n): string {\n if (filter.label !== undefined) {\n return filter.label;\n }\n const field = findFilterField(filter, columns, fields);\n return field?.label ?? filter.id;\n}\n\n/** The field a filter filters, when a column or the list's fields have one with its id. */\nexport function findFilterField(\n filter: Filter,\n columns: readonly ListColumn[],\n fields: readonly Field[],\n): Field | null {\n return (\n fields.find((field) => field.name === filter.id) ??\n columns.find((column) => column.field?.name === filter.id)?.field ??\n null\n );\n}\n\n/** The options of a filter: its own, or those of the field it filters. */\nexport function getFilterOptions(\n filter: Filter,\n columns: readonly ListColumn[],\n fields: readonly Field[],\n): readonly Option[] {\n return filter.options ?? getFieldOptions(findFilterField(filter, columns, fields));\n}\n\nconst DATE_ONLY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\nfunction formatDay(value: string, intl: IntlShape): string {\n const match = DATE_ONLY.exec(value);\n if (match === null) {\n return value;\n }\n return intl.formatDate(new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])), {\n dateStyle: 'medium',\n });\n}\n\n/** The value of an active filter, as the text of its chip. */\nexport function formatFilterValue(\n filter: Filter,\n value: unknown,\n {\n intl,\n options,\n getValueLabel,\n }: {\n intl: IntlShape;\n options: readonly Option[];\n getValueLabel: (value: string) => string | null;\n },\n): string {\n const values: unknown[] = Array.isArray(value) ? value : [value];\n switch (filter.type) {\n case 'toggle':\n return intl.formatMessage(String(value) === 'true' ? messages.yes : messages.no);\n case 'date':\n return formatDay(String(value), intl);\n case 'date-range': {\n const [from = '', to = ''] = values.map(String);\n return intl.formatMessage(messages.dateRange, {\n from: from !== '' ? formatDay(from, intl) : '…',\n to: to !== '' ? formatDay(to, intl) : '…',\n });\n }\n case 'relation':\n return values.map((entry) => getValueLabel(String(entry)) ?? String(entry)).join(', ');\n default:\n return values\n .map((entry) => getOptionLabel(options, entry) ?? String(entry))\n .join(', ');\n }\n}\n"],"mappings":";;;;AASA,SAAgB,eACZ,QACA,SACA,QACM;CACN,IAAI,OAAO,UAAU,KAAA,GACjB,OAAO,OAAO;CAGlB,OADc,gBAAgB,QAAQ,SAAS,MACxC,CAAA,EAAO,SAAS,OAAO;AAClC;;AAGA,SAAgB,gBACZ,QACA,SACA,QACY;CACZ,OACI,OAAO,MAAM,UAAU,MAAM,SAAS,OAAO,EAAE,KAC/C,QAAQ,MAAM,WAAW,OAAO,OAAO,SAAS,OAAO,EAAE,CAAC,EAAE,SAC5D;AAER;;AAGA,SAAgB,iBACZ,QACA,SACA,QACiB;CACjB,OAAO,OAAO,WAAW,gBAAgB,gBAAgB,QAAQ,SAAS,MAAM,CAAC;AACrF;AAEA,IAAM,YAAY;AAElB,SAAS,UAAU,OAAe,MAAyB;CACvD,MAAM,QAAQ,UAAU,KAAK,KAAK;CAClC,IAAI,UAAU,MACV,OAAO;CAEX,OAAO,KAAK,WAAW,IAAI,KAAK,OAAO,MAAM,EAAE,GAAG,OAAO,MAAM,EAAE,IAAI,GAAG,OAAO,MAAM,EAAE,CAAC,GAAG,EACvF,WAAW,SACf,CAAC;AACL;;AAGA,SAAgB,kBACZ,QACA,OACA,EACI,MACA,SACA,iBAME;CACN,MAAM,SAAoB,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAC/D,QAAQ,OAAO,MAAf;EACI,KAAK,UACD,OAAO,KAAK,cAAc,OAAO,KAAK,MAAM,SAAS,SAAS,MAAM,SAAS,EAAE;EACnF,KAAK,QACD,OAAO,UAAU,OAAO,KAAK,GAAG,IAAI;EACxC,KAAK,cAAc;GACf,MAAM,CAAC,OAAO,IAAI,KAAK,MAAM,OAAO,IAAI,MAAM;GAC9C,OAAO,KAAK,cAAc,SAAS,WAAW;IAC1C,MAAM,SAAS,KAAK,UAAU,MAAM,IAAI,IAAI;IAC5C,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI,IAAI;GAC1C,CAAC;EACL;EACA,KAAK,YACD,OAAO,OAAO,KAAK,UAAU,cAAc,OAAO,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;EACzF,SACI,OAAO,OACF,KAAK,UAAU,eAAe,SAAS,KAAK,KAAK,OAAO,KAAK,CAAC,CAAC,CAC/D,KAAK,IAAI;CACtB;AACJ"}
1
+ {"version":3,"file":"format.js","names":[],"sources":["../../src/filters/format.ts"],"sourcesContent":["import type { IntlShape } from 'react-intl';\n\nimport type { Field, Filter, Option } from '@panneau/protocol';\nimport { isFilterRange } from '@panneau/protocol/runtime';\n\nimport { messages } from '../messages';\nimport type { ListColumn } from '../types';\nimport { getFieldOptions, getOptionLabel } from '../values';\n\n/** The label of a filter: its `label`, the label of its field, or its id. */\nexport function getFilterLabel(\n filter: Filter,\n columns: readonly ListColumn[],\n fields: readonly Field[],\n): string {\n if (filter.label !== undefined) {\n return filter.label;\n }\n const field = findFilterField(filter, columns, fields);\n return field?.label ?? filter.id;\n}\n\n/** The field a filter filters, when a column or the list's fields have one with its id. */\nexport function findFilterField(\n filter: Filter,\n columns: readonly ListColumn[],\n fields: readonly Field[],\n): Field | null {\n return (\n fields.find((field) => field.name === filter.id) ??\n columns.find((column) => column.field?.name === filter.id)?.field ??\n null\n );\n}\n\n/** The options of a filter: its own, or those of the field it filters. */\nexport function getFilterOptions(\n filter: Filter,\n columns: readonly ListColumn[],\n fields: readonly Field[],\n): readonly Option[] {\n return filter.options ?? getFieldOptions(findFilterField(filter, columns, fields));\n}\n\nconst DATE_ONLY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\nfunction formatDay(value: string, intl: IntlShape): string {\n const match = DATE_ONLY.exec(value);\n if (match === null) {\n return value;\n }\n return intl.formatDate(new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])), {\n dateStyle: 'medium',\n });\n}\n\n/** The value of an active filter, as the text of its chip. */\nexport function formatFilterValue(\n filter: Filter,\n value: unknown,\n {\n intl,\n options,\n getValueLabel,\n }: {\n intl: IntlShape;\n options: readonly Option[];\n getValueLabel: (value: string) => string | null;\n },\n): string {\n const values: unknown[] = Array.isArray(value) ? value : [value];\n switch (filter.type) {\n case 'toggle':\n return intl.formatMessage(String(value) === 'true' ? messages.yes : messages.no);\n case 'date':\n return formatDay(String(value), intl);\n case 'date-range': {\n const range = isFilterRange(value) ? value : {};\n const from = range.from !== undefined ? String(range.from) : '';\n const to = range.to !== undefined ? String(range.to) : '';\n return intl.formatMessage(messages.dateRange, {\n from: from !== '' ? formatDay(from, intl) : '…',\n to: to !== '' ? formatDay(to, intl) : '…',\n });\n }\n case 'relation':\n return values.map((entry) => getValueLabel(String(entry)) ?? String(entry)).join(', ');\n default:\n return values\n .map((entry) => getOptionLabel(options, entry) ?? String(entry))\n .join(', ');\n }\n}\n"],"mappings":";;;;;AAUA,SAAgB,eACZ,QACA,SACA,QACM;CACN,IAAI,OAAO,UAAU,KAAA,GACjB,OAAO,OAAO;CAGlB,OADc,gBAAgB,QAAQ,SAAS,MACxC,CAAA,EAAO,SAAS,OAAO;AAClC;;AAGA,SAAgB,gBACZ,QACA,SACA,QACY;CACZ,OACI,OAAO,MAAM,UAAU,MAAM,SAAS,OAAO,EAAE,KAC/C,QAAQ,MAAM,WAAW,OAAO,OAAO,SAAS,OAAO,EAAE,CAAC,EAAE,SAC5D;AAER;;AAGA,SAAgB,iBACZ,QACA,SACA,QACiB;CACjB,OAAO,OAAO,WAAW,gBAAgB,gBAAgB,QAAQ,SAAS,MAAM,CAAC;AACrF;AAEA,IAAM,YAAY;AAElB,SAAS,UAAU,OAAe,MAAyB;CACvD,MAAM,QAAQ,UAAU,KAAK,KAAK;CAClC,IAAI,UAAU,MACV,OAAO;CAEX,OAAO,KAAK,WAAW,IAAI,KAAK,OAAO,MAAM,EAAE,GAAG,OAAO,MAAM,EAAE,IAAI,GAAG,OAAO,MAAM,EAAE,CAAC,GAAG,EACvF,WAAW,SACf,CAAC;AACL;;AAGA,SAAgB,kBACZ,QACA,OACA,EACI,MACA,SACA,iBAME;CACN,MAAM,SAAoB,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAC/D,QAAQ,OAAO,MAAf;EACI,KAAK,UACD,OAAO,KAAK,cAAc,OAAO,KAAK,MAAM,SAAS,SAAS,MAAM,SAAS,EAAE;EACnF,KAAK,QACD,OAAO,UAAU,OAAO,KAAK,GAAG,IAAI;EACxC,KAAK,cAAc;GACf,MAAM,QAAQ,cAAc,KAAK,IAAI,QAAQ,CAAC;GAC9C,MAAM,OAAO,MAAM,SAAS,KAAA,IAAY,OAAO,MAAM,IAAI,IAAI;GAC7D,MAAM,KAAK,MAAM,OAAO,KAAA,IAAY,OAAO,MAAM,EAAE,IAAI;GACvD,OAAO,KAAK,cAAc,SAAS,WAAW;IAC1C,MAAM,SAAS,KAAK,UAAU,MAAM,IAAI,IAAI;IAC5C,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI,IAAI;GAC1C,CAAC;EACL;EACA,KAAK,YACD,OAAO,OAAO,KAAK,UAAU,cAAc,OAAO,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;EACzF,SACI,OAAO,OACF,KAAK,UAAU,eAAe,SAAS,KAAK,KAAK,OAAO,KAAK,CAAC,CAAC,CAC/D,KAAK,IAAI;CACtB;AACJ"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { type ListActions, getActionHref, groupListActions, isAllowedOn } from './actions/actions';
2
2
  export { ConfirmDialog, type ConfirmDialogProps } from './actions/ConfirmDialog';
3
+ export { type ActionIcons, getActionIcon } from './actions/icons';
3
4
  export { BooleanDisplay, ColorDisplay, DateDisplay, DateTimeDisplay, EmailDisplay, EmptyValue, MediaDisplay, NumberDisplay, RelationDisplay, RichTextDisplay, SelectDisplay, TextDisplay, TimeDisplay, UrlDisplay, builtInDisplays, } from './displays/displays';
4
5
  export { DateFilter, DateRangeFilter, RelationFilter, SelectFilter, TextFilter, ToggleFilter, builtInFilters, } from './filters/controls';
5
6
  export { CardsLayout } from './layouts/CardsLayout';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,WAAW,EACX,WAAW,EACX,UAAU,EACV,eAAe,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,cAAc,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC3E,YAAY,EACR,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,WAAW,EACX,WAAW,EACX,UAAU,EACV,eAAe,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,UAAU,EACV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,cAAc,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC3E,YAAY,EACR,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { getActionHref, groupListActions, isAllowedOn } from "./actions/actions.js";
2
2
  import { ConfirmDialog } from "./actions/ConfirmDialog.js";
3
+ import { getActionIcon } from "./actions/icons.js";
3
4
  import { getLocalizedValue, toText } from "./values.js";
4
5
  import { BooleanDisplay, ColorDisplay, DateDisplay, DateTimeDisplay, EmailDisplay, EmptyValue, MediaDisplay, NumberDisplay, RelationDisplay, RichTextDisplay, SelectDisplay, TextDisplay, TimeDisplay, UrlDisplay, builtInDisplays } from "./displays/displays.js";
5
6
  import { DateFilter, DateRangeFilter, RelationFilter, SelectFilter, TextFilter, ToggleFilter, builtInFilters } from "./filters/controls.js";
@@ -14,4 +15,4 @@ import { ListView } from "./ListView.js";
14
15
  import { applyListQuery } from "./query.js";
15
16
  import { List } from "./List.js";
16
17
  import { listsPlugin } from "./plugin.js";
17
- export { BooleanDisplay, CardsLayout, ColorDisplay, ConfirmDialog, DateDisplay, DateFilter, DateRangeFilter, DateTimeDisplay, EmailDisplay, EmptyValue, List, ListView, MediaDisplay, NumberDisplay, Pagination, RelationDisplay, RelationFilter, RichTextDisplay, SearchField, SelectDisplay, SelectFilter, TableLayout, TextDisplay, TextFilter, TimeDisplay, ToggleFilter, UrlDisplay, applyListQuery, builtInDisplays, builtInFilters, builtInLayouts, countSelected, getActionHref, getLocalizedValue, getPageSelection, groupListActions, isAllowedOn, isItemSelected, listsPlugin, toText, useListState };
18
+ export { BooleanDisplay, CardsLayout, ColorDisplay, ConfirmDialog, DateDisplay, DateFilter, DateRangeFilter, DateTimeDisplay, EmailDisplay, EmptyValue, List, ListView, MediaDisplay, NumberDisplay, Pagination, RelationDisplay, RelationFilter, RichTextDisplay, SearchField, SelectDisplay, SelectFilter, TableLayout, TextDisplay, TextFilter, TimeDisplay, ToggleFilter, UrlDisplay, applyListQuery, builtInDisplays, builtInFilters, builtInLayouts, countSelected, getActionHref, getActionIcon, getLocalizedValue, getPageSelection, groupListActions, isAllowedOn, isItemSelected, listsPlugin, toText, useListState };
@@ -6,5 +6,5 @@ import type { ReactNode } from 'react';
6
6
  export declare function ItemTitle({ href, children }: {
7
7
  href: string | null;
8
8
  children: ReactNode;
9
- }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
9
+ }): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
10
10
  //# sourceMappingURL=ItemTitle.d.ts.map
package/dist/query.d.ts CHANGED
@@ -14,8 +14,8 @@ export interface LocalQueryResult<T extends ListItem> {
14
14
  }
15
15
  /**
16
16
  * Applies a list query to items in memory, as a backend would: filters (the value, or one of the
17
- * values, matches as a string), search in every text value, sort, then the page. For a product
18
- * that has every item at hand.
17
+ * values, matches as a string; a range matches the values within its bounds), search in every text
18
+ * value, sort, then the page. For a product that has every item at hand.
19
19
  */
20
20
  export declare function applyListQuery<T extends ListItem>(items: readonly T[], query: ListQuery, options?: LocalQueryOptions): LocalQueryResult<T>;
21
21
  //# sourceMappingURL=query.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGpD,MAAM,WAAW,iBAAiB;IAC9B,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,QAAQ;IAChD,6BAA6B;IAC7B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;CACjB;AAOD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,EAC7C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,iBAAsB,GAChC,gBAAgB,CAAC,CAAC,CAAC,CAuDrB"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGpD,MAAM,WAAW,iBAAiB;IAC9B,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,QAAQ;IAChD,6BAA6B;IAC7B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;CACjB;AAOD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,QAAQ,EAC7C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,iBAAsB,GAChC,gBAAgB,CAAC,CAAC,CAAC,CA6DrB"}
package/dist/query.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getColumnValue, toText } from "./values.js";
2
+ import { isFilterRange, matchesFilterRange } from "@panneau/protocol/runtime";
2
3
  //#region src/query.ts
3
4
  function valueOf(item, key, options) {
4
5
  const column = options.columns?.find((candidate) => candidate.id === key);
@@ -6,13 +7,17 @@ function valueOf(item, key, options) {
6
7
  }
7
8
  /**
8
9
  * Applies a list query to items in memory, as a backend would: filters (the value, or one of the
9
- * values, matches as a string), search in every text value, sort, then the page. For a product
10
- * that has every item at hand.
10
+ * values, matches as a string; a range matches the values within its bounds), search in every text
11
+ * value, sort, then the page. For a product that has every item at hand.
11
12
  */
12
13
  function applyListQuery(items, query, options = {}) {
13
14
  const locale = options.locale ?? "en";
14
15
  let result = [...items];
15
16
  Object.entries(query.filter ?? {}).forEach(([key, expected]) => {
17
+ if (isFilterRange(expected)) {
18
+ result = result.filter((item) => matchesFilterRange(valueOf(item, key, options), expected));
19
+ return;
20
+ }
16
21
  const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);
17
22
  result = result.filter((item) => {
18
23
  const value = valueOf(item, key, options);
package/dist/query.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import type { ListQuery } from '@panneau/protocol';\n\nimport type { ListColumn, ListItem } from './types';\nimport { getColumnValue, toText } from './values';\n\nexport interface LocalQueryOptions {\n /** The columns: values of localized fields are compared in `locale`. */\n columns?: readonly ListColumn[];\n /** Locale of localized values and of the text comparison. Defaults to `en`. */\n locale?: string;\n}\n\nexport interface LocalQueryResult<T extends ListItem> {\n /** The items of the page. */\n items: T[];\n /** How many items match the filters and the search, on every page. */\n total: number;\n}\n\nfunction valueOf(item: ListItem, key: string, options: LocalQueryOptions): unknown {\n const column = options.columns?.find((candidate) => candidate.id === key);\n return column !== undefined ? getColumnValue(item, column, options.locale ?? 'en') : item[key];\n}\n\n/**\n * Applies a list query to items in memory, as a backend would: filters (the value, or one of the\n * values, matches as a string), search in every text value, sort, then the page. For a product\n * that has every item at hand.\n */\nexport function applyListQuery<T extends ListItem>(\n items: readonly T[],\n query: ListQuery,\n options: LocalQueryOptions = {},\n): LocalQueryResult<T> {\n const locale = options.locale ?? 'en';\n let result = [...items];\n Object.entries(query.filter ?? {}).forEach(([key, expected]) => {\n const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);\n result = result.filter((item) => {\n const value = valueOf(item, key, options);\n const values: unknown[] = Array.isArray(value) ? value : [value];\n return values.some((candidate) => {\n const id =\n candidate !== null && typeof candidate === 'object' && 'id' in candidate\n ? candidate.id\n : candidate;\n return accepted.includes(String(id));\n });\n });\n });\n const q = query.q?.trim().toLocaleLowerCase(locale) ?? '';\n if (q !== '') {\n result = result.filter((item) =>\n Object.entries(item)\n .filter(([key]) => key !== 'id' && key !== 'abilities' && key !== 'version')\n .some(([key]) =>\n toText(valueOf(item, key, options), locale)\n .toLocaleLowerCase(locale)\n .includes(q),\n ),\n );\n }\n const sort = query.sort ?? [];\n if (sort.length > 0) {\n const collator = new Intl.Collator(locale, { numeric: true, sensitivity: 'base' });\n result.sort((a, b) => {\n for (const part of sort) {\n const descending = part.startsWith('-');\n const key = descending ? part.slice(1) : part;\n const left = valueOf(a, key, options);\n const right = valueOf(b, key, options);\n const order =\n typeof left === 'number' && typeof right === 'number'\n ? left - right\n : compareText(toText(left, locale), toText(right, locale), collator);\n if (order !== 0) {\n return descending ? -order : order;\n }\n }\n return 0;\n });\n }\n const total = result.length;\n if (query.perPage !== undefined) {\n const page = query.page ?? 1;\n result = result.slice((page - 1) * query.perPage, page * query.perPage);\n }\n return { items: result, total };\n}\n\n/** Compares texts, empty ones last. */\nfunction compareText(left: string, right: string, collator: Intl.Collator): number {\n if (left === right) {\n return 0;\n }\n if (left === '') {\n return 1;\n }\n if (right === '') {\n return -1;\n }\n return collator.compare(left, right);\n}\n"],"mappings":";;AAmBA,SAAS,QAAQ,MAAgB,KAAa,SAAqC;CAC/E,MAAM,SAAS,QAAQ,SAAS,MAAM,cAAc,UAAU,OAAO,GAAG;CACxE,OAAO,WAAW,KAAA,IAAY,eAAe,MAAM,QAAQ,QAAQ,UAAU,IAAI,IAAI,KAAK;AAC9F;;;;;;AAOA,SAAgB,eACZ,OACA,OACA,UAA6B,CAAC,GACX;CACnB,MAAM,SAAS,QAAQ,UAAU;CACjC,IAAI,SAAS,CAAC,GAAG,KAAK;CACtB,OAAO,QAAQ,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,cAAc;EAC5D,MAAM,YAAY,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ,EAAA,CAAG,IAAI,MAAM;EAC7E,SAAS,OAAO,QAAQ,SAAS;GAC7B,MAAM,QAAQ,QAAQ,MAAM,KAAK,OAAO;GAExC,QAD0B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAA,CACjD,MAAM,cAAc;IAC9B,MAAM,KACF,cAAc,QAAQ,OAAO,cAAc,YAAY,QAAQ,YACzD,UAAU,KACV;IACV,OAAO,SAAS,SAAS,OAAO,EAAE,CAAC;GACvC,CAAC;EACL,CAAC;CACL,CAAC;CACD,MAAM,IAAI,MAAM,GAAG,KAAK,CAAC,CAAC,kBAAkB,MAAM,KAAK;CACvD,IAAI,MAAM,IACN,SAAS,OAAO,QAAQ,SACpB,OAAO,QAAQ,IAAI,CAAC,CACf,QAAQ,CAAC,SAAS,QAAQ,QAAQ,QAAQ,eAAe,QAAQ,SAAS,CAAC,CAC3E,MAAM,CAAC,SACJ,OAAO,QAAQ,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC,CACtC,kBAAkB,MAAM,CAAC,CACzB,SAAS,CAAC,CACnB,CACR;CAEJ,MAAM,OAAO,MAAM,QAAQ,CAAA;CAC3B,IAAI,KAAK,SAAS,GAAG;EACjB,MAAM,WAAW,IAAI,KAAK,SAAS,QAAQ;GAAE,SAAS;GAAM,aAAa;EAAO,CAAC;EACjF,OAAO,MAAM,GAAG,MAAM;GAClB,KAAK,MAAM,QAAQ,MAAM;IACrB,MAAM,aAAa,KAAK,WAAW,GAAG;IACtC,MAAM,MAAM,aAAa,KAAK,MAAM,CAAC,IAAI;IACzC,MAAM,OAAO,QAAQ,GAAG,KAAK,OAAO;IACpC,MAAM,QAAQ,QAAQ,GAAG,KAAK,OAAO;IACrC,MAAM,QACF,OAAO,SAAS,YAAY,OAAO,UAAU,WACvC,OAAO,QACP,YAAY,OAAO,MAAM,MAAM,GAAG,OAAO,OAAO,MAAM,GAAG,QAAQ;IAC3E,IAAI,UAAU,GACV,OAAO,aAAa,CAAC,QAAQ;GAErC;GACA,OAAO;EACX,CAAC;CACL;CACA,MAAM,QAAQ,OAAO;CACrB,IAAI,MAAM,YAAY,KAAA,GAAW;EAC7B,MAAM,OAAO,MAAM,QAAQ;EAC3B,SAAS,OAAO,OAAO,OAAO,KAAK,MAAM,SAAS,OAAO,MAAM,OAAO;CAC1E;CACA,OAAO;EAAE,OAAO;EAAQ;CAAM;AAClC;;AAGA,SAAS,YAAY,MAAc,OAAe,UAAiC;CAC/E,IAAI,SAAS,OACT,OAAO;CAEX,IAAI,SAAS,IACT,OAAO;CAEX,IAAI,UAAU,IACV,OAAO;CAEX,OAAO,SAAS,QAAQ,MAAM,KAAK;AACvC"}
1
+ {"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import type { ListQuery } from '@panneau/protocol';\nimport { isFilterRange, matchesFilterRange } from '@panneau/protocol/runtime';\n\nimport type { ListColumn, ListItem } from './types';\nimport { getColumnValue, toText } from './values';\n\nexport interface LocalQueryOptions {\n /** The columns: values of localized fields are compared in `locale`. */\n columns?: readonly ListColumn[];\n /** Locale of localized values and of the text comparison. Defaults to `en`. */\n locale?: string;\n}\n\nexport interface LocalQueryResult<T extends ListItem> {\n /** The items of the page. */\n items: T[];\n /** How many items match the filters and the search, on every page. */\n total: number;\n}\n\nfunction valueOf(item: ListItem, key: string, options: LocalQueryOptions): unknown {\n const column = options.columns?.find((candidate) => candidate.id === key);\n return column !== undefined ? getColumnValue(item, column, options.locale ?? 'en') : item[key];\n}\n\n/**\n * Applies a list query to items in memory, as a backend would: filters (the value, or one of the\n * values, matches as a string; a range matches the values within its bounds), search in every text\n * value, sort, then the page. For a product that has every item at hand.\n */\nexport function applyListQuery<T extends ListItem>(\n items: readonly T[],\n query: ListQuery,\n options: LocalQueryOptions = {},\n): LocalQueryResult<T> {\n const locale = options.locale ?? 'en';\n let result = [...items];\n Object.entries(query.filter ?? {}).forEach(([key, expected]) => {\n if (isFilterRange(expected)) {\n result = result.filter((item) =>\n matchesFilterRange(valueOf(item, key, options), expected),\n );\n return;\n }\n const accepted = (Array.isArray(expected) ? expected : [expected]).map(String);\n result = result.filter((item) => {\n const value = valueOf(item, key, options);\n const values: unknown[] = Array.isArray(value) ? value : [value];\n return values.some((candidate) => {\n const id =\n candidate !== null && typeof candidate === 'object' && 'id' in candidate\n ? candidate.id\n : candidate;\n return accepted.includes(String(id));\n });\n });\n });\n const q = query.q?.trim().toLocaleLowerCase(locale) ?? '';\n if (q !== '') {\n result = result.filter((item) =>\n Object.entries(item)\n .filter(([key]) => key !== 'id' && key !== 'abilities' && key !== 'version')\n .some(([key]) =>\n toText(valueOf(item, key, options), locale)\n .toLocaleLowerCase(locale)\n .includes(q),\n ),\n );\n }\n const sort = query.sort ?? [];\n if (sort.length > 0) {\n const collator = new Intl.Collator(locale, { numeric: true, sensitivity: 'base' });\n result.sort((a, b) => {\n for (const part of sort) {\n const descending = part.startsWith('-');\n const key = descending ? part.slice(1) : part;\n const left = valueOf(a, key, options);\n const right = valueOf(b, key, options);\n const order =\n typeof left === 'number' && typeof right === 'number'\n ? left - right\n : compareText(toText(left, locale), toText(right, locale), collator);\n if (order !== 0) {\n return descending ? -order : order;\n }\n }\n return 0;\n });\n }\n const total = result.length;\n if (query.perPage !== undefined) {\n const page = query.page ?? 1;\n result = result.slice((page - 1) * query.perPage, page * query.perPage);\n }\n return { items: result, total };\n}\n\n/** Compares texts, empty ones last. */\nfunction compareText(left: string, right: string, collator: Intl.Collator): number {\n if (left === right) {\n return 0;\n }\n if (left === '') {\n return 1;\n }\n if (right === '') {\n return -1;\n }\n return collator.compare(left, right);\n}\n"],"mappings":";;;AAoBA,SAAS,QAAQ,MAAgB,KAAa,SAAqC;CAC/E,MAAM,SAAS,QAAQ,SAAS,MAAM,cAAc,UAAU,OAAO,GAAG;CACxE,OAAO,WAAW,KAAA,IAAY,eAAe,MAAM,QAAQ,QAAQ,UAAU,IAAI,IAAI,KAAK;AAC9F;;;;;;AAOA,SAAgB,eACZ,OACA,OACA,UAA6B,CAAC,GACX;CACnB,MAAM,SAAS,QAAQ,UAAU;CACjC,IAAI,SAAS,CAAC,GAAG,KAAK;CACtB,OAAO,QAAQ,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,cAAc;EAC5D,IAAI,cAAc,QAAQ,GAAG;GACzB,SAAS,OAAO,QAAQ,SACpB,mBAAmB,QAAQ,MAAM,KAAK,OAAO,GAAG,QAAQ,CAC5D;GACA;EACJ;EACA,MAAM,YAAY,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ,EAAA,CAAG,IAAI,MAAM;EAC7E,SAAS,OAAO,QAAQ,SAAS;GAC7B,MAAM,QAAQ,QAAQ,MAAM,KAAK,OAAO;GAExC,QAD0B,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAA,CACjD,MAAM,cAAc;IAC9B,MAAM,KACF,cAAc,QAAQ,OAAO,cAAc,YAAY,QAAQ,YACzD,UAAU,KACV;IACV,OAAO,SAAS,SAAS,OAAO,EAAE,CAAC;GACvC,CAAC;EACL,CAAC;CACL,CAAC;CACD,MAAM,IAAI,MAAM,GAAG,KAAK,CAAC,CAAC,kBAAkB,MAAM,KAAK;CACvD,IAAI,MAAM,IACN,SAAS,OAAO,QAAQ,SACpB,OAAO,QAAQ,IAAI,CAAC,CACf,QAAQ,CAAC,SAAS,QAAQ,QAAQ,QAAQ,eAAe,QAAQ,SAAS,CAAC,CAC3E,MAAM,CAAC,SACJ,OAAO,QAAQ,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC,CACtC,kBAAkB,MAAM,CAAC,CACzB,SAAS,CAAC,CACnB,CACR;CAEJ,MAAM,OAAO,MAAM,QAAQ,CAAA;CAC3B,IAAI,KAAK,SAAS,GAAG;EACjB,MAAM,WAAW,IAAI,KAAK,SAAS,QAAQ;GAAE,SAAS;GAAM,aAAa;EAAO,CAAC;EACjF,OAAO,MAAM,GAAG,MAAM;GAClB,KAAK,MAAM,QAAQ,MAAM;IACrB,MAAM,aAAa,KAAK,WAAW,GAAG;IACtC,MAAM,MAAM,aAAa,KAAK,MAAM,CAAC,IAAI;IACzC,MAAM,OAAO,QAAQ,GAAG,KAAK,OAAO;IACpC,MAAM,QAAQ,QAAQ,GAAG,KAAK,OAAO;IACrC,MAAM,QACF,OAAO,SAAS,YAAY,OAAO,UAAU,WACvC,OAAO,QACP,YAAY,OAAO,MAAM,MAAM,GAAG,OAAO,OAAO,MAAM,GAAG,QAAQ;IAC3E,IAAI,UAAU,GACV,OAAO,aAAa,CAAC,QAAQ;GAErC;GACA,OAAO;EACX,CAAC;CACL;CACA,MAAM,QAAQ,OAAO;CACrB,IAAI,MAAM,YAAY,KAAA,GAAW;EAC7B,MAAM,OAAO,MAAM,QAAQ;EAC3B,SAAS,OAAO,OAAO,OAAO,KAAK,MAAM,SAAS,OAAO,MAAM,OAAO;CAC1E;CACA,OAAO;EAAE,OAAO;EAAQ;CAAM;AAClC;;AAGA,SAAS,YAAY,MAAc,OAAe,UAAiC;CAC/E,IAAI,SAAS,OACT,OAAO;CAEX,IAAI,SAAS,IACT,OAAO;CAEX,IAAI,UAAU,IACV,OAAO;CAEX,OAAO,SAAS,QAAQ,MAAM,KAAK;AACvC"}
package/dist/types.d.ts CHANGED
@@ -55,7 +55,8 @@ export interface ListActionEvent {
55
55
  /**
56
56
  * Runs an action. Lists ask for confirmation first when the action has `confirm`. A returned
57
57
  * promise shows the action as pending until it settles; after a selection action succeeds, the
58
- * selection is cleared.
58
+ * selection is cleared, unless the handler gives `false` (the action didn't run, as when the user
59
+ * cancels its parameters). `useActionRunner()` of `@panneau/actions` is such a handler.
59
60
  */
60
61
  export type ListActionHandler = (event: ListActionEvent) => unknown;
61
62
  /** Props of a filter control. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}