@seed-ship/mcp-ui-solid 4.3.6 → 4.3.8

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": "@seed-ship/mcp-ui-solid",
3
- "version": "4.3.6",
3
+ "version": "4.3.8",
4
4
  "description": "SolidJS components for rendering MCP-generated UI resources",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -241,6 +241,25 @@ function ChartRenderer(props: {
241
241
  /**
242
242
  * Smart cell value renderer that handles markdown links and other formats
243
243
  */
244
+ /**
245
+ * Wrap matches of `query` in <mark> tags within an HTML string.
246
+ * Case-insensitive. Skips content inside HTML tag attributes to avoid corruption.
247
+ * v4.3.8
248
+ */
249
+ export function highlightQuery(html: string, query: string): string {
250
+ const q = query.trim()
251
+ if (!q) return html
252
+ // Escape regex metacharacters
253
+ const escaped = q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
254
+ const regex = new RegExp(`(${escaped})`, 'gi')
255
+ // Process text segments only (skip inside tags)
256
+ return html.replace(/(<[^>]+>)|([^<]+)/g, (_m, tag, text) => {
257
+ if (tag) return tag
258
+ if (!text) return ''
259
+ return text.replace(regex, '<mark class="bg-yellow-200 dark:bg-[#222F49] text-inherit rounded px-0.5">$1</mark>')
260
+ })
261
+ }
262
+
244
263
  export function renderCellValue(value: any): string {
245
264
  // Handle null/undefined
246
265
  if (value === null || value === undefined) {
@@ -345,7 +364,6 @@ function TableRenderer(props: {
345
364
  setSortDir('asc')
346
365
  }
347
366
  setClientPage(0)
348
- setProgressivePages(1)
349
367
  }
350
368
 
351
369
  const sortedRows = createMemo(() => {
@@ -389,7 +407,6 @@ function TableRenderer(props: {
389
407
  searchTimer = setTimeout(() => {
390
408
  setDebouncedQuery(value)
391
409
  setClientPage(0)
392
- setProgressivePages(1)
393
410
  }, 200)
394
411
  }
395
412
 
@@ -409,39 +426,47 @@ function TableRenderer(props: {
409
426
  )
410
427
  })
411
428
 
412
- // ─── Client-side pagination (v4.0.4, progressive v4.3.2, context-aware v4.3.4) ─────
429
+ // ─── Client-side pagination (v4.0.4, context-aware v4.3.4, selector v4.3.7) ─────
413
430
  const isExpanded = useExpanded()
414
- const fullPageSize = () => tableParams.pageSize ?? 25
415
- const chatPageSize = () => tableParams.chatPageSize ?? Math.min(10, fullPageSize())
416
- const clientPageSize = () => isExpanded() ? fullPageSize() : chatPageSize()
431
+ const defaultPageSize = () => tableParams.pageSize ?? 25
432
+ const chatDefault = () => tableParams.chatPageSize ?? Math.min(10, defaultPageSize())
433
+ const [userPageSize, setUserPageSize] = createSignal<number | null>(null) // null = use default
434
+ const clientPageSize = () => {
435
+ const ups = userPageSize()
436
+ if (ups !== null) return ups // user chose a size
437
+ return isExpanded() ? defaultPageSize() : chatDefault()
438
+ }
439
+ const showAll = () => userPageSize() === 0
417
440
  const hasServerPagination = () => !!tableParams.pagination
418
- const isProgressiveMode = () => !!tableParams.showAllLabel
419
441
  const needsClientPagination = () =>
420
- !hasServerPagination() && clientPageSize() > 0 && filteredRows().length > clientPageSize()
442
+ !hasServerPagination() && !showAll() && clientPageSize() > 0 && filteredRows().length > clientPageSize()
421
443
  const [clientPage, setClientPage] = createSignal(tableParams.initialPage ?? 0)
422
- // Progressive mode: track how many pages to show (append)
423
- const [progressivePages, setProgressivePages] = createSignal(1)
424
444
  const clientTotalPages = () => needsClientPagination() ? Math.ceil(filteredRows().length / clientPageSize()) : 1
425
445
  const clientVisibleRows = createMemo(() => {
426
- if (!needsClientPagination()) return filteredRows()
427
- if (isProgressiveMode()) {
428
- // Progressive: show first N * pageSize rows
429
- return filteredRows().slice(0, progressivePages() * clientPageSize())
430
- }
446
+ if (showAll() || !needsClientPagination()) return filteredRows()
431
447
  const start = clientPage() * clientPageSize()
432
448
  return filteredRows().slice(start, start + clientPageSize())
433
449
  })
434
- const clientRangeStart = () => needsClientPagination()
435
- ? (isProgressiveMode() ? 1 : clientPage() * clientPageSize() + 1)
436
- : 1
450
+ const clientRangeStart = () => needsClientPagination() ? clientPage() * clientPageSize() + 1 : 1
437
451
  const clientRangeEnd = () => needsClientPagination()
438
- ? (isProgressiveMode()
439
- ? Math.min(progressivePages() * clientPageSize(), filteredRows().length)
440
- : Math.min((clientPage() + 1) * clientPageSize(), filteredRows().length))
452
+ ? Math.min((clientPage() + 1) * clientPageSize(), filteredRows().length)
441
453
  : filteredRows().length
442
- const progressiveHasMore = () => isProgressiveMode() && needsClientPagination() && progressivePages() < clientTotalPages()
443
- const progressiveRemaining = () => filteredRows().length - progressivePages() * clientPageSize()
444
- const showMoreLabel = () => tableParams.showAllLabel || 'Show more'
454
+
455
+ // Page size options for selector (fullscreen)
456
+ const pageSizeOptions = () => {
457
+ const total = filteredRows().length
458
+ const opts: Array<{ value: number; label: string }> = []
459
+ for (const n of [10, 30, 60, 100]) {
460
+ if (n < total) opts.push({ value: n, label: String(n) })
461
+ }
462
+ opts.push({ value: 0, label: 'All' })
463
+ return opts
464
+ }
465
+
466
+ const handlePageSizeChange = (val: number) => {
467
+ setUserPageSize(val === 0 ? 0 : val)
468
+ setClientPage(0)
469
+ }
445
470
 
446
471
  // ─── Virtualization ──────────────────────────────────────
447
472
  const [virtualizer, setVirtualizer] = createSignal<any>(null)
@@ -586,7 +611,7 @@ function TableRenderer(props: {
586
611
  <For each={tableParams.columns}>
587
612
  {(column: any) => (
588
613
  <td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
589
- <div innerHTML={renderCellValue(row[column.key])} />
614
+ <div innerHTML={highlightQuery(renderCellValue(row[column.key]), debouncedQuery())} />
590
615
  </td>
591
616
  )}
592
617
  </For>
@@ -625,7 +650,7 @@ function TableRenderer(props: {
625
650
  <For each={tableParams.columns}>
626
651
  {(column: any) => (
627
652
  <td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
628
- <div innerHTML={renderCellValue(row[column.key])} />
653
+ <div innerHTML={highlightQuery(renderCellValue(row[column.key]), debouncedQuery())} />
629
654
  </td>
630
655
  )}
631
656
  </For>
@@ -639,7 +664,7 @@ function TableRenderer(props: {
639
664
 
640
665
  return (
641
666
  <ExpandableWrapper title={tableParams.title || 'Table'} copyData={getTableCSV()} copyLabel="Copy table (CSV)">
642
- <div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden group">
667
+ <div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden group ${isExpanded() ? '' : 'h-full'}`}>
643
668
  <Show when={exportable} fallback={<CopyButton getText={getTableCSV} title="Copy table (CSV)" position="top-right" />}>
644
669
  <div class="absolute right-10 top-2 z-10">
645
670
  <button
@@ -710,8 +735,8 @@ function TableRenderer(props: {
710
735
  style={
711
736
  isVirtualizing()
712
737
  ? { 'max-height': '500px', 'overflow-y': 'auto' }
713
- : clientVisibleRows().length > 8
714
- ? { 'max-height': isExpanded() ? '70vh' : '400px', 'overflow-y': 'auto' }
738
+ : !isExpanded() && clientVisibleRows().length > 8
739
+ ? { 'max-height': '400px', 'overflow-y': 'auto' }
715
740
  : {}
716
741
  }
717
742
  role="region"
@@ -722,13 +747,13 @@ function TableRenderer(props: {
722
747
  class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border-separate border-spacing-0"
723
748
  aria-labelledby={tableParams.title ? `${tableId}-title` : undefined}
724
749
  >
725
- <thead class="bg-gray-50 dark:bg-gray-900 sticky top-0 z-10">
750
+ <thead class="bg-gray-100 dark:bg-gray-900 sticky top-0 z-10">
726
751
  <tr>
727
752
  <For each={tableParams.columns}>
728
753
  {(column: any) => (
729
754
  <th
730
755
  scope="col"
731
- 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 cursor-pointer select-none hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
756
+ 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-100 dark:bg-gray-900 cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors"
732
757
  style={column.width ? { width: column.width } : {}}
733
758
  on:click={() => handleSort(column.key)}
734
759
  title={`Sort by ${column.label}`}
@@ -770,48 +795,44 @@ function TableRenderer(props: {
770
795
  </div>
771
796
  </Show>
772
797
 
773
- {/* Client-side paged pagination (v4.0.4) */}
774
- <Show when={needsClientPagination() && !isProgressiveMode()}>
798
+ {/* Client-side pagination (v4.3.7) */}
799
+ <Show when={needsClientPagination()}>
775
800
  <div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
776
801
  <span>
777
- Showing {clientRangeStart()}&ndash;{clientRangeEnd()} of {filteredRows().length.toLocaleString('fr-FR')}
802
+ {clientRangeStart()}&ndash;{clientRangeEnd()} / {filteredRows().length.toLocaleString('fr-FR')}
778
803
  </span>
779
- <div class="flex items-center gap-1">
804
+ <div class="flex items-center gap-2">
780
805
  <button
781
806
  class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
782
807
  disabled={clientPage() === 0}
783
808
  onClick={() => setClientPage(p => p - 1)}
784
809
  >
785
- &#x25C0; Prev
810
+ &#x25C0;
786
811
  </button>
787
- <span class="px-2">Page {clientPage() + 1} / {clientTotalPages()}</span>
812
+ <span>{clientPage() + 1} / {clientTotalPages()}</span>
788
813
  <button
789
814
  class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
790
815
  disabled={clientPage() >= clientTotalPages() - 1}
791
816
  onClick={() => setClientPage(p => p + 1)}
792
817
  >
793
- Next &#x25B6;
818
+ &#x25B6;
794
819
  </button>
820
+ {/* Page size selector — fullscreen only */}
821
+ <Show when={isExpanded() && filteredRows().length > 10}>
822
+ <select
823
+ class="ml-2 px-1 py-0.5 text-xs border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300"
824
+ value={clientPageSize()}
825
+ onChange={(e) => handlePageSizeChange(Number(e.currentTarget.value))}
826
+ >
827
+ <For each={pageSizeOptions()}>
828
+ {(opt) => <option value={opt.value}>{opt.label}</option>}
829
+ </For>
830
+ </select>
831
+ <span class="text-gray-400">/ page</span>
832
+ </Show>
795
833
  </div>
796
834
  </div>
797
835
  </Show>
798
-
799
- {/* Client-side progressive pagination (v4.3.2) */}
800
- <Show when={needsClientPagination() && isProgressiveMode()}>
801
- <div class="mt-3 flex flex-col items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
802
- <span>
803
- {clientRangeStart()}&ndash;{clientRangeEnd()} of {filteredRows().length.toLocaleString('fr-FR')}
804
- </span>
805
- <Show when={progressiveHasMore()}>
806
- <button
807
- class="px-4 py-1.5 rounded-md bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 transition-colors"
808
- onClick={() => setProgressivePages(p => p + 1)}
809
- >
810
- {showMoreLabel()} ({Math.min(progressiveRemaining(), clientPageSize())} suivant{Math.min(progressiveRemaining(), clientPageSize()) > 1 ? 'es' : 'e'})
811
- </button>
812
- </Show>
813
- </div>
814
- </Show>
815
836
  </div>
816
837
  </div>
817
838
  </ExpandableWrapper>