@seed-ship/mcp-ui-solid 4.0.4 → 4.0.6

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.
@@ -330,30 +330,74 @@ function TableRenderer(props: {
330
330
  const tableParams = props.component.params as any
331
331
  let scrollContainerRef: HTMLDivElement | undefined
332
332
 
333
- // Client-side pagination (v4.0.4) — auto-enabled when rows > pageSize, no server config needed
333
+ // ─── Client-side sorting (v4.0.5) ────────────────────────
334
+ const allRows = () => tableParams.rows || []
335
+ const columns = () => tableParams.columns || []
336
+ const [sortKey, setSortKey] = createSignal<string | null>(null)
337
+ const [sortDir, setSortDir] = createSignal<'asc' | 'desc' | null>(null)
338
+
339
+ const handleSort = (key: string) => {
340
+ if (sortKey() === key) {
341
+ if (sortDir() === 'asc') setSortDir('desc')
342
+ else { setSortKey(null); setSortDir(null) }
343
+ } else {
344
+ setSortKey(key)
345
+ setSortDir('asc')
346
+ }
347
+ setClientPage(0)
348
+ }
349
+
350
+ const sortedRows = createMemo(() => {
351
+ const r = allRows()
352
+ const key = sortKey()
353
+ const dir = sortDir()
354
+ if (!key || !dir) return r
355
+ const col = columns().find((c: any) => c.key === key)
356
+ const isNum = col?.type === 'number' || (r.length > 0 && typeof r[0]?.[key] === 'number')
357
+ return [...r].sort((a: any, b: any) => {
358
+ const va = a[key], vb = b[key]
359
+ if (va == null && vb == null) return 0
360
+ if (va == null) return 1
361
+ if (vb == null) return -1
362
+ let cmp: number
363
+ if (isNum) {
364
+ cmp = (Number(va) || 0) - (Number(vb) || 0)
365
+ } else {
366
+ cmp = String(va).localeCompare(String(vb), 'fr', { sensitivity: 'base' })
367
+ }
368
+ return dir === 'desc' ? -cmp : cmp
369
+ })
370
+ })
371
+
372
+ const sortIndicator = (key: string) => {
373
+ if (sortKey() !== key) return '\u2195'
374
+ return sortDir() === 'asc' ? '\u2191' : '\u2193'
375
+ }
376
+
377
+ // ─── Client-side pagination (v4.0.4) ─────────────────────
334
378
  const clientPageSize = () => tableParams.pageSize ?? 25
335
379
  const hasServerPagination = () => !!tableParams.pagination
336
- const allRows = () => tableParams.rows || []
337
380
  const needsClientPagination = () =>
338
- !hasServerPagination() && clientPageSize() > 0 && allRows().length > clientPageSize()
381
+ !hasServerPagination() && clientPageSize() > 0 && sortedRows().length > clientPageSize()
339
382
  const [clientPage, setClientPage] = createSignal(tableParams.initialPage ?? 0)
340
- const clientTotalPages = () => needsClientPagination() ? Math.ceil(allRows().length / clientPageSize()) : 1
383
+ const clientTotalPages = () => needsClientPagination() ? Math.ceil(sortedRows().length / clientPageSize()) : 1
341
384
  const clientVisibleRows = createMemo(() => {
342
- if (!needsClientPagination()) return allRows()
385
+ if (!needsClientPagination()) return sortedRows()
343
386
  const start = clientPage() * clientPageSize()
344
- return allRows().slice(start, start + clientPageSize())
387
+ return sortedRows().slice(start, start + clientPageSize())
345
388
  })
346
389
  const clientRangeStart = () => needsClientPagination() ? clientPage() * clientPageSize() + 1 : 1
347
390
  const clientRangeEnd = () => needsClientPagination()
348
- ? Math.min((clientPage() + 1) * clientPageSize(), allRows().length)
349
- : allRows().length
391
+ ? Math.min((clientPage() + 1) * clientPageSize(), sortedRows().length)
392
+ : sortedRows().length
350
393
 
351
- // Virtualization state
394
+ // ─── Virtualization ──────────────────────────────────────
352
395
  const [virtualizer, setVirtualizer] = createSignal<any>(null)
353
396
  const [isVirtualizing, setIsVirtualizing] = createSignal(false)
354
397
 
355
- // Determine if virtualization should be enabled
398
+ // Disable virtualization when client pagination is active (they conflict)
356
399
  const shouldVirtualize = createMemo(() => {
400
+ if (needsClientPagination()) return false // pagination handles slicing
357
401
  const opts = tableParams.virtualize
358
402
  if (opts === false) return false
359
403
  if (opts === true) return true
@@ -362,7 +406,6 @@ function TableRenderer(props: {
362
406
  const threshold = opts.threshold ?? 100
363
407
  return (tableParams.rows?.length ?? 0) > threshold
364
408
  }
365
- // Auto-enable if > 100 rows by default
366
409
  return (tableParams.rows?.length ?? 0) > 100
367
410
  })
368
411
 
@@ -600,10 +643,23 @@ function TableRenderer(props: {
600
643
  {(column: any) => (
601
644
  <th
602
645
  scope="col"
603
- class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 first:pl-6 last:pr-6 bg-gray-50 dark:bg-gray-900/50"
646
+ class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 first:pl-6 last:pr-6 bg-gray-50 dark:bg-gray-900/50 cursor-pointer select-none hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors"
604
647
  style={column.width ? { width: column.width } : {}}
648
+ on:click={() => handleSort(column.key)}
649
+ title={`Sort by ${column.label}`}
605
650
  >
606
- {column.label}
651
+ <span class="inline-flex items-center gap-1">
652
+ {column.label}
653
+ <span
654
+ class="text-[10px] leading-none"
655
+ classList={{
656
+ 'opacity-30': sortKey() !== column.key,
657
+ 'opacity-100 text-blue-600 dark:text-blue-400': sortKey() === column.key,
658
+ }}
659
+ >
660
+ {sortIndicator(column.key)}
661
+ </span>
662
+ </span>
607
663
  </th>
608
664
  )}
609
665
  </For>