@byline/host-tanstack-start 3.20.2 → 3.20.4

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.
@@ -15,8 +15,18 @@ export interface TableHeadingCellSortableProps extends TableHeadingCellProps {
15
15
  desc?: boolean;
16
16
  align?: 'left' | 'center' | 'right';
17
17
  className?: string;
18
+ /**
19
+ * The *effective* sort column/direction — URL params when present, else
20
+ * the collection's configured `defaultSort` (surfaced via the list
21
+ * response's `meta.order`/`meta.desc`). When provided, these drive the
22
+ * sort indicator instead of reading the URL directly, so a params-less
23
+ * landing still shows which column ordered the rows. Omit for the legacy
24
+ * URL-only behaviour.
25
+ */
26
+ activeOrder?: string;
27
+ activeDesc?: boolean;
18
28
  }
19
- export declare function TableHeadingCellSortable({ path: _path, fieldName, label, sortable, align, className, ...rest }: TableHeadingCellSortableProps & {
29
+ export declare function TableHeadingCellSortable({ path: _path, fieldName, label, sortable, align, className, activeOrder, activeDesc, ...rest }: TableHeadingCellSortableProps & {
20
30
  ref?: React.RefObject<HTMLTableCellElement>;
21
31
  }): React.JSX.Element;
22
32
  export {};
@@ -6,7 +6,7 @@ import classnames from "classnames";
6
6
  import { useNavigate } from "./loose-router.js";
7
7
  import { SortAscendingIcon, SortDescendingIcon, SortNeutralIcon } from "./sort-icons.js";
8
8
  import th_sortable_module from "./th-sortable.module.js";
9
- function TableHeadingCellSortable({ path: _path = '/', fieldName, label, sortable = false, align = 'left', className, ...rest }) {
9
+ function TableHeadingCellSortable({ path: _path = '/', fieldName, label, sortable = false, align = 'left', className, activeOrder, activeDesc, ...rest }) {
10
10
  const navigate = useNavigate();
11
11
  const location = useRouterState({
12
12
  select: (s)=>s.location
@@ -27,13 +27,15 @@ function TableHeadingCellSortable({ path: _path = '/', fieldName, label, sortabl
27
27
  };
28
28
  useEffect(()=>{
29
29
  if (null != fieldName) {
30
- const order = location.search.order;
31
- const d = location.search.desc;
30
+ const order = void 0 !== activeOrder ? activeOrder : location.search.order;
31
+ const d = void 0 !== activeOrder ? activeDesc : location.search.desc;
32
32
  order === fieldName ? setDesc(d ?? false) : setDesc(null);
33
33
  }
34
34
  }, [
35
35
  fieldName,
36
- location.search
36
+ location.search,
37
+ activeOrder,
38
+ activeDesc
37
39
  ]);
38
40
  const alignClasses = classnames({
39
41
  'byline-th-align-left': 'left' === align,
@@ -89,6 +89,8 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
89
89
  const searchParams = location.search;
90
90
  const isCanonicalView = !searchParams.order && !searchParams.desc && !searchParams.query && !searchParams.status;
91
91
  const dragEnabled = orderable && isCanonicalView && !!onReorder;
92
+ const activeOrder = searchParams.order ?? data?.meta.order;
93
+ const activeDesc = null != searchParams.order ? searchParams.desc : data?.meta.desc;
92
94
  const sensors = useSensors(useSensor(PointerSensor, {
93
95
  activationConstraint: {
94
96
  distance: 5
@@ -306,7 +308,9 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
306
308
  sortable: column.sortable,
307
309
  scope: "col",
308
310
  align: column.align,
309
- className: column.className
311
+ className: column.className,
312
+ activeOrder: activeOrder,
313
+ activeDesc: activeDesc
310
314
  }, String(column.fieldName)))
311
315
  ]
312
316
  })
@@ -351,7 +355,9 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
351
355
  sortable: column.sortable,
352
356
  scope: "col",
353
357
  align: column.align,
354
- className: column.className
358
+ className: column.className,
359
+ activeOrder: activeOrder,
360
+ activeDesc: activeDesc
355
361
  }, String(column.fieldName)))
356
362
  })
357
363
  }),
@@ -20,8 +20,15 @@ const getCollectionDocuments = createServerFn({
20
20
  const where = {};
21
21
  if (params.status) where.status = params.status;
22
22
  if (params.query) where.query = params.query;
23
+ const adminConfig = getCollectionAdminConfig(path);
24
+ const configuredSort = params.order || true === config.definition.orderable || adminConfig?.defaultSort == null ? void 0 : {
25
+ order: String(adminConfig.defaultSort.field),
26
+ desc: 'desc' === adminConfig.defaultSort.direction
27
+ };
23
28
  const defaultSort = true === config.definition.orderable ? {
24
29
  order_key: 'asc'
30
+ } : null != configuredSort ? {
31
+ [configuredSort.order]: configuredSort.desc ? 'desc' : 'asc'
25
32
  } : void 0;
26
33
  const sortSpec = params.order ? {
27
34
  [params.order]: false === params.desc ? 'asc' : 'desc'
@@ -57,8 +64,8 @@ const getCollectionDocuments = createServerFn({
57
64
  docs,
58
65
  meta: {
59
66
  ...result.meta,
60
- order: params.order,
61
- desc: params.desc
67
+ order: params.order ?? configuredSort?.order,
68
+ desc: params.desc ?? configuredSort?.desc
62
69
  },
63
70
  included: {
64
71
  collection: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "3.20.2",
6
+ "version": "3.20.4",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -119,13 +119,13 @@
119
119
  "react-swipeable": "^7.0.2",
120
120
  "uuid": "^14.0.1",
121
121
  "zod": "^4.4.3",
122
- "@byline/client": "3.20.2",
123
- "@byline/auth": "3.20.2",
124
- "@byline/i18n": "3.20.2",
125
- "@byline/ai": "3.20.2",
126
- "@byline/admin": "3.20.2",
127
- "@byline/core": "3.20.2",
128
- "@byline/ui": "3.20.2"
122
+ "@byline/admin": "3.20.4",
123
+ "@byline/auth": "3.20.4",
124
+ "@byline/ai": "3.20.4",
125
+ "@byline/i18n": "3.20.4",
126
+ "@byline/ui": "3.20.4",
127
+ "@byline/client": "3.20.4",
128
+ "@byline/core": "3.20.4"
129
129
  },
130
130
  "peerDependencies": {
131
131
  "@tanstack/react-router": "^1.167.0",
@@ -27,6 +27,16 @@ export interface TableHeadingCellSortableProps extends TableHeadingCellProps {
27
27
  desc?: boolean
28
28
  align?: 'left' | 'center' | 'right'
29
29
  className?: string
30
+ /**
31
+ * The *effective* sort column/direction — URL params when present, else
32
+ * the collection's configured `defaultSort` (surfaced via the list
33
+ * response's `meta.order`/`meta.desc`). When provided, these drive the
34
+ * sort indicator instead of reading the URL directly, so a params-less
35
+ * landing still shows which column ordered the rows. Omit for the legacy
36
+ * URL-only behaviour.
37
+ */
38
+ activeOrder?: string
39
+ activeDesc?: boolean
30
40
  }
31
41
 
32
42
  export function TableHeadingCellSortable({
@@ -36,6 +46,8 @@ export function TableHeadingCellSortable({
36
46
  sortable = false,
37
47
  align = 'left',
38
48
  className,
49
+ activeOrder,
50
+ activeDesc,
39
51
  ...rest
40
52
  }: TableHeadingCellSortableProps & {
41
53
  ref?: React.RefObject<HTMLTableCellElement>
@@ -61,15 +73,21 @@ export function TableHeadingCellSortable({
61
73
 
62
74
  useEffect(() => {
63
75
  if (fieldName != null) {
64
- const order = (location.search as Record<string, unknown>).order
65
- const d = (location.search as Record<string, unknown>).desc as boolean | undefined
76
+ // Prefer the effective sort passed down by the list view (which folds
77
+ // in a configured `defaultSort`); fall back to reading the URL.
78
+ const order =
79
+ activeOrder !== undefined ? activeOrder : (location.search as Record<string, unknown>).order
80
+ const d =
81
+ activeOrder !== undefined
82
+ ? activeDesc
83
+ : ((location.search as Record<string, unknown>).desc as boolean | undefined)
66
84
  if (order === fieldName) {
67
85
  setDesc(d ?? false)
68
86
  } else {
69
87
  setDesc(null)
70
88
  }
71
89
  }
72
- }, [fieldName, location.search])
90
+ }, [fieldName, location.search, activeOrder, activeDesc])
73
91
 
74
92
  const alignClasses = cx({
75
93
  'byline-th-align-left': align === 'left',
@@ -207,6 +207,14 @@ export const ListView = ({
207
207
  !searchParams.order && !searchParams.desc && !searchParams.query && !searchParams.status
208
208
  const dragEnabled = orderable && isCanonicalView && !!onReorder
209
209
 
210
+ // The *effective* sort for the header indicators: explicit URL params win;
211
+ // otherwise the server echoes a configured `defaultSort` (admin config)
212
+ // through `meta.order`/`meta.desc`, so a params-less landing still shows
213
+ // which column ordered the rows. (Orderable collections never set
214
+ // `meta.order` — their default is the drag order, indicated separately.)
215
+ const activeOrder = searchParams.order ?? data?.meta.order
216
+ const activeDesc = searchParams.order != null ? searchParams.desc : data?.meta.desc
217
+
210
218
  const sensors = useSensors(
211
219
  useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
212
220
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
@@ -418,6 +426,8 @@ export const ListView = ({
418
426
  scope="col"
419
427
  align={column.align}
420
428
  className={column.className}
429
+ activeOrder={activeOrder}
430
+ activeDesc={activeDesc}
421
431
  />
422
432
  )
423
433
  })}
@@ -496,6 +506,8 @@ export const ListView = ({
496
506
  scope="col"
497
507
  align={column.align}
498
508
  className={column.className}
509
+ activeOrder={activeOrder}
510
+ activeDesc={activeDesc}
499
511
  />
500
512
  )
501
513
  })}
@@ -69,11 +69,27 @@ export const getCollectionDocuments = createServerFn({ method: 'GET' })
69
69
  if (params.status) where.status = params.status
70
70
  if (params.query) where.query = params.query
71
71
 
72
- // Default sort for `orderable: true` collections is the fractional
73
- // `order_key` ascending. Caller's explicit `params.order` always wins
74
- // so the admin can re-sort by other columns without surprise.
72
+ // Sort precedence: the caller's explicit `params.order` always wins (a
73
+ // shared link opens exactly as sent) → `orderable: true` collections
74
+ // default to the fractional `order_key` ascending the admin config's
75
+ // `defaultSort` (boot-validated; mutually exclusive with `orderable`) →
76
+ // the storage fallback (`created_at desc`). `configuredSort` is also
77
+ // echoed through `meta.order`/`meta.desc` below so the list header can
78
+ // render the effective sort indicator on a params-less landing.
79
+ const adminConfig = getCollectionAdminConfig(path)
80
+ const configuredSort =
81
+ !params.order && config.definition.orderable !== true && adminConfig?.defaultSort != null
82
+ ? {
83
+ order: String(adminConfig.defaultSort.field),
84
+ desc: adminConfig.defaultSort.direction === 'desc',
85
+ }
86
+ : undefined
75
87
  const defaultSort: Record<string, 'asc' | 'desc'> | undefined =
76
- config.definition.orderable === true ? { order_key: 'asc' } : undefined
88
+ config.definition.orderable === true
89
+ ? { order_key: 'asc' }
90
+ : configuredSort != null
91
+ ? { [configuredSort.order]: configuredSort.desc ? 'desc' : 'asc' }
92
+ : undefined
77
93
  const sortSpec: Record<string, 'asc' | 'desc'> | undefined = params.order
78
94
  ? { [params.order]: params.desc === false ? 'asc' : 'desc' }
79
95
  : defaultSort
@@ -131,8 +147,12 @@ export const getCollectionDocuments = createServerFn({ method: 'GET' })
131
147
  docs,
132
148
  meta: {
133
149
  ...result.meta,
134
- order: params.order,
135
- desc: params.desc,
150
+ // The *effective* sort: explicit params, or the configured
151
+ // `defaultSort` when it filled in. The list header renders its
152
+ // sort indicator from this, so a params-less landing still shows
153
+ // which column ordered the rows.
154
+ order: params.order ?? configuredSort?.order,
155
+ desc: params.desc ?? configuredSort?.desc,
136
156
  },
137
157
  included: {
138
158
  collection: {