@k2b/ui 0.51.0 → 0.52.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.
Files changed (206) hide show
  1. package/dist/base.css +3 -0
  2. package/dist/browser/actions/Toolbar.js +8 -2
  3. package/dist/browser/actions/Toolbar.js.map +1 -1
  4. package/dist/browser/charts/core/bar-labels.js +5 -0
  5. package/dist/browser/charts/core/bar-labels.js.map +1 -0
  6. package/dist/browser/charts/core/bubble.js +166 -0
  7. package/dist/browser/charts/core/bubble.js.map +1 -0
  8. package/dist/browser/charts/core/default-styles.js +83 -0
  9. package/dist/browser/charts/core/default-styles.js.map +1 -0
  10. package/dist/browser/charts/core/funnel-layout.js +79 -0
  11. package/dist/browser/charts/core/funnel-layout.js.map +1 -0
  12. package/dist/browser/charts/core/funnel.js +27 -0
  13. package/dist/browser/charts/core/funnel.js.map +1 -0
  14. package/dist/browser/charts/core/index.js +6 -1
  15. package/dist/browser/charts/core/index.js.map +1 -1
  16. package/dist/browser/charts/core/pareto.js +120 -0
  17. package/dist/browser/charts/core/pareto.js.map +1 -0
  18. package/dist/browser/charts/core/radar.js +101 -0
  19. package/dist/browser/charts/core/radar.js.map +1 -0
  20. package/dist/browser/charts/core/registry.js +11 -1
  21. package/dist/browser/charts/core/registry.js.map +1 -1
  22. package/dist/browser/charts/core/render-size.js +3 -1
  23. package/dist/browser/charts/core/render-size.js.map +1 -1
  24. package/dist/browser/charts/core/render.js +19 -0
  25. package/dist/browser/charts/core/render.js.map +1 -1
  26. package/dist/browser/charts/core/snapshot.js +1 -1
  27. package/dist/browser/charts/core/snapshot.js.map +1 -1
  28. package/dist/browser/charts/core/svg-barGauge.js +67 -0
  29. package/dist/browser/charts/core/svg-barGauge.js.map +1 -0
  30. package/dist/browser/charts/core/svg-boxplot.js +127 -0
  31. package/dist/browser/charts/core/svg-boxplot.js.map +1 -0
  32. package/dist/browser/charts/core/svg-common.js +1065 -0
  33. package/dist/browser/charts/core/svg-common.js.map +1 -0
  34. package/dist/browser/charts/core/svg-gauge.js +185 -0
  35. package/dist/browser/charts/core/svg-gauge.js.map +1 -0
  36. package/dist/browser/charts/core/svg-heatmap.js +80 -0
  37. package/dist/browser/charts/core/svg-heatmap.js.map +1 -0
  38. package/dist/browser/charts/core/svg-histogram.js +73 -0
  39. package/dist/browser/charts/core/svg-histogram.js.map +1 -0
  40. package/dist/browser/charts/core/svg-renderers.js +9 -1777
  41. package/dist/browser/charts/core/svg-renderers.js.map +1 -1
  42. package/dist/browser/charts/core/svg-scatter.js +114 -0
  43. package/dist/browser/charts/core/svg-scatter.js.map +1 -0
  44. package/dist/browser/charts/core/treemap.js +138 -0
  45. package/dist/browser/charts/core/treemap.js.map +1 -0
  46. package/dist/browser/charts/description.js.map +1 -1
  47. package/dist/browser/content/ChartRoot.js +21 -7
  48. package/dist/browser/content/ChartRoot.js.map +1 -1
  49. package/dist/browser/content/ChartSnapshotView.js +4 -1
  50. package/dist/browser/content/ChartSnapshotView.js.map +1 -1
  51. package/dist/browser/content/DataTable.js +186 -193
  52. package/dist/browser/content/DataTable.js.map +1 -1
  53. package/dist/browser/content/Lightbox.js +55 -23
  54. package/dist/browser/content/Lightbox.js.map +1 -1
  55. package/dist/browser/content/ThumbnailStrip.js +174 -0
  56. package/dist/browser/content/ThumbnailStrip.js.map +1 -0
  57. package/dist/browser/content/chart-bar.js +98 -0
  58. package/dist/browser/content/chart-bar.js.map +1 -1
  59. package/dist/browser/content/chart-inspection-core.js +28 -8
  60. package/dist/browser/content/chart-inspection-core.js.map +1 -1
  61. package/dist/browser/content/chart-message-catalog.js +18 -0
  62. package/dist/browser/content/chart-message-catalog.js.map +1 -1
  63. package/dist/browser/content/index.js +1 -0
  64. package/dist/browser/content/index.js.map +1 -1
  65. package/dist/browser/content/thumbnail-strip-messages.js +24 -0
  66. package/dist/browser/content/thumbnail-strip-messages.js.map +1 -0
  67. package/dist/browser/inputs/ChoiceInputs.js +132 -101
  68. package/dist/browser/inputs/ChoiceInputs.js.map +1 -1
  69. package/dist/browser/inputs/index.js +1 -0
  70. package/dist/browser/inputs/index.js.map +1 -1
  71. package/dist/browser/inputs/markdown/MarkdownSelectionToolbar.js +330 -0
  72. package/dist/browser/inputs/markdown/MarkdownSelectionToolbar.js.map +1 -0
  73. package/dist/browser/internal/field.js +96 -77
  74. package/dist/browser/internal/field.js.map +1 -1
  75. package/dist/browser/layout/AppWorkspace.js +7 -5
  76. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  77. package/dist/browser/layout/PanelDialog.js +92 -68
  78. package/dist/browser/layout/PanelDialog.js.map +1 -1
  79. package/dist/browser/surfaces/StatCell.js +82 -70
  80. package/dist/browser/surfaces/StatCell.js.map +1 -1
  81. package/dist/browser/surfaces/stat-cell-messages.js +4 -2
  82. package/dist/browser/surfaces/stat-cell-messages.js.map +1 -1
  83. package/dist/browser/widgets/DashboardFrame.js +93 -88
  84. package/dist/browser/widgets/DashboardFrame.js.map +1 -1
  85. package/dist/ssr/actions/Toolbar.js +8 -2
  86. package/dist/ssr/actions/Toolbar.js.map +1 -1
  87. package/dist/ssr/charts/core/bar-labels.js +5 -0
  88. package/dist/ssr/charts/core/bar-labels.js.map +1 -0
  89. package/dist/ssr/charts/core/bubble.js +166 -0
  90. package/dist/ssr/charts/core/bubble.js.map +1 -0
  91. package/dist/ssr/charts/core/default-styles.js +83 -0
  92. package/dist/ssr/charts/core/default-styles.js.map +1 -0
  93. package/dist/ssr/charts/core/funnel-layout.js +79 -0
  94. package/dist/ssr/charts/core/funnel-layout.js.map +1 -0
  95. package/dist/ssr/charts/core/funnel.js +27 -0
  96. package/dist/ssr/charts/core/funnel.js.map +1 -0
  97. package/dist/ssr/charts/core/index.js +6 -1
  98. package/dist/ssr/charts/core/index.js.map +1 -1
  99. package/dist/ssr/charts/core/pareto.js +120 -0
  100. package/dist/ssr/charts/core/pareto.js.map +1 -0
  101. package/dist/ssr/charts/core/radar.js +101 -0
  102. package/dist/ssr/charts/core/radar.js.map +1 -0
  103. package/dist/ssr/charts/core/registry.js +11 -1
  104. package/dist/ssr/charts/core/registry.js.map +1 -1
  105. package/dist/ssr/charts/core/render-size.js +3 -1
  106. package/dist/ssr/charts/core/render-size.js.map +1 -1
  107. package/dist/ssr/charts/core/render.js +19 -0
  108. package/dist/ssr/charts/core/render.js.map +1 -1
  109. package/dist/ssr/charts/core/snapshot.js +1 -1
  110. package/dist/ssr/charts/core/snapshot.js.map +1 -1
  111. package/dist/ssr/charts/core/svg-barGauge.js +67 -0
  112. package/dist/ssr/charts/core/svg-barGauge.js.map +1 -0
  113. package/dist/ssr/charts/core/svg-boxplot.js +127 -0
  114. package/dist/ssr/charts/core/svg-boxplot.js.map +1 -0
  115. package/dist/ssr/charts/core/svg-common.js +1065 -0
  116. package/dist/ssr/charts/core/svg-common.js.map +1 -0
  117. package/dist/ssr/charts/core/svg-gauge.js +185 -0
  118. package/dist/ssr/charts/core/svg-gauge.js.map +1 -0
  119. package/dist/ssr/charts/core/svg-heatmap.js +80 -0
  120. package/dist/ssr/charts/core/svg-heatmap.js.map +1 -0
  121. package/dist/ssr/charts/core/svg-histogram.js +73 -0
  122. package/dist/ssr/charts/core/svg-histogram.js.map +1 -0
  123. package/dist/ssr/charts/core/svg-renderers.js +9 -1777
  124. package/dist/ssr/charts/core/svg-renderers.js.map +1 -1
  125. package/dist/ssr/charts/core/svg-scatter.js +114 -0
  126. package/dist/ssr/charts/core/svg-scatter.js.map +1 -0
  127. package/dist/ssr/charts/core/treemap.js +138 -0
  128. package/dist/ssr/charts/core/treemap.js.map +1 -0
  129. package/dist/ssr/charts/description.js.map +1 -1
  130. package/dist/ssr/content/ChartRoot.js +21 -7
  131. package/dist/ssr/content/ChartRoot.js.map +1 -1
  132. package/dist/ssr/content/ChartSnapshotView.js +1 -0
  133. package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
  134. package/dist/ssr/content/DataTable.js +12 -16
  135. package/dist/ssr/content/DataTable.js.map +1 -1
  136. package/dist/ssr/content/Lightbox.js +32 -3
  137. package/dist/ssr/content/Lightbox.js.map +1 -1
  138. package/dist/ssr/content/ThumbnailStrip.js +103 -0
  139. package/dist/ssr/content/ThumbnailStrip.js.map +1 -0
  140. package/dist/ssr/content/chart-bar.js +98 -0
  141. package/dist/ssr/content/chart-bar.js.map +1 -1
  142. package/dist/ssr/content/chart-inspection-core.js +28 -8
  143. package/dist/ssr/content/chart-inspection-core.js.map +1 -1
  144. package/dist/ssr/content/chart-message-catalog.js +18 -0
  145. package/dist/ssr/content/chart-message-catalog.js.map +1 -1
  146. package/dist/ssr/content/index.js +1 -0
  147. package/dist/ssr/content/index.js.map +1 -1
  148. package/dist/ssr/content/thumbnail-strip-messages.js +24 -0
  149. package/dist/ssr/content/thumbnail-strip-messages.js.map +1 -0
  150. package/dist/ssr/inputs/ChoiceInputs.js +55 -40
  151. package/dist/ssr/inputs/ChoiceInputs.js.map +1 -1
  152. package/dist/ssr/inputs/index.js +1 -0
  153. package/dist/ssr/inputs/index.js.map +1 -1
  154. package/dist/ssr/inputs/markdown/MarkdownSelectionToolbar.js +293 -0
  155. package/dist/ssr/inputs/markdown/MarkdownSelectionToolbar.js.map +1 -0
  156. package/dist/ssr/internal/field.js +36 -24
  157. package/dist/ssr/internal/field.js.map +1 -1
  158. package/dist/ssr/layout/AppWorkspace.js +5 -1
  159. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  160. package/dist/ssr/layout/PanelDialog.js +40 -26
  161. package/dist/ssr/layout/PanelDialog.js.map +1 -1
  162. package/dist/ssr/surfaces/StatCell.js +29 -18
  163. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  164. package/dist/ssr/surfaces/stat-cell-messages.js +4 -2
  165. package/dist/ssr/surfaces/stat-cell-messages.js.map +1 -1
  166. package/dist/ssr/widgets/DashboardFrame.js +6 -11
  167. package/dist/ssr/widgets/DashboardFrame.js.map +1 -1
  168. package/dist/styles.css +1 -1
  169. package/dist/types/actions/Toolbar.d.ts +2 -0
  170. package/dist/types/charts/core/bar-labels.d.ts +1 -0
  171. package/dist/types/charts/core/bubble.d.ts +66 -0
  172. package/dist/types/charts/core/default-styles.d.ts +1 -0
  173. package/dist/types/charts/core/funnel-layout.d.ts +43 -0
  174. package/dist/types/charts/core/funnel.d.ts +13 -0
  175. package/dist/types/charts/core/index.d.ts +6 -1
  176. package/dist/types/charts/core/pareto.d.ts +40 -0
  177. package/dist/types/charts/core/radar.d.ts +47 -0
  178. package/dist/types/charts/core/registry.d.ts +10 -0
  179. package/dist/types/charts/core/render-size.d.ts +1 -1
  180. package/dist/types/charts/core/svg-barGauge.d.ts +3 -0
  181. package/dist/types/charts/core/svg-boxplot.d.ts +18 -0
  182. package/dist/types/charts/core/svg-common.d.ts +612 -0
  183. package/dist/types/charts/core/svg-gauge.d.ts +6 -0
  184. package/dist/types/charts/core/svg-heatmap.d.ts +6 -0
  185. package/dist/types/charts/core/svg-histogram.d.ts +16 -0
  186. package/dist/types/charts/core/svg-renderers.d.ts +8 -612
  187. package/dist/types/charts/core/svg-scatter.d.ts +23 -0
  188. package/dist/types/charts/core/treemap.d.ts +45 -0
  189. package/dist/types/charts/description.d.ts +63 -0
  190. package/dist/types/content/ChartRoot.d.ts +1 -1
  191. package/dist/types/content/Lightbox.d.ts +2 -0
  192. package/dist/types/content/ThumbnailStrip.d.ts +29 -0
  193. package/dist/types/content/chart-bar.d.ts +38 -2
  194. package/dist/types/content/chart-inspection-core.d.ts +7 -0
  195. package/dist/types/content/chart-inspection.d.ts +1 -0
  196. package/dist/types/content/chart-message-catalog.d.ts +8 -0
  197. package/dist/types/content/index.d.ts +1 -0
  198. package/dist/types/content/thumbnail-strip-messages.d.ts +10 -0
  199. package/dist/types/inputs/ChoiceInputs.d.ts +2 -0
  200. package/dist/types/inputs/index.d.ts +2 -0
  201. package/dist/types/inputs/markdown/MarkdownSelectionToolbar.d.ts +19 -0
  202. package/dist/types/internal/field.d.ts +2 -0
  203. package/dist/types/layout/PanelDialog.d.ts +5 -0
  204. package/dist/types/surfaces/stat-cell-messages.d.ts +1 -0
  205. package/dist/types/widgets/DashboardFrame.d.ts +4 -1
  206. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["content/Lightbox.tsx"],"sourcesContent":["import { createEffect, createSignal, For, onCleanup, onMount, Show } from \"solid-js\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { useLightboxMessages } from \"./lightbox-messages\";\n\n/** One more thing to do with the shown image, beside Download: a short label, its icon class, and what it does. */\nexport type LightboxAction = {\n label: string;\n icon: string;\n onClick: () => void;\n};\n\nexport type LightboxImage = {\n src: string;\n alt?: string;\n downloadUrl?: string;\n /** Actions for this image, shown before Download in the same style. */\n actions?: readonly LightboxAction[];\n};\n\ntype LightboxProps = {\n images: LightboxImage[];\n initialIndex?: number;\n onClose: () => void;\n};\n\n/**\n * Minimal, accessible lightbox using native <dialog>.\n * Supports keyboard navigation, touch swipe gestures, and screen readers.\n */\nexport default function Lightbox(props: LightboxProps) {\n const messages = useUiMessages();\n const lightboxMessages = useLightboxMessages();\n const clampIndex = (value: number, images = props.images): number =>\n images.length === 0 ? 0 : Math.max(0, Math.min(value, images.length - 1));\n const [index, setIndex] = createSignal(clampIndex(props.initialIndex ?? 0));\n let dialogRef!: HTMLDialogElement;\n let opener: HTMLElement | null = null;\n let openerRing = true;\n\n createEffect(\n (previous: { images: LightboxImage[]; initialIndex: number | undefined }) => {\n const images = props.images;\n const initialIndex = props.initialIndex;\n setIndex((currentIndex) => {\n if (initialIndex !== previous.initialIndex) return clampIndex(initialIndex ?? 0, images);\n const previousImage = previous.images[currentIndex];\n const retained = previousImage ? images.findIndex((image) => image === previousImage || image.src === previousImage.src) : -1;\n return retained >= 0 ? retained : clampIndex(currentIndex, images);\n });\n return { images, initialIndex };\n },\n { images: props.images, initialIndex: props.initialIndex },\n );\n\n // Touch gesture tracking\n let touchStartX = 0;\n let touchStartY = 0;\n const SWIPE_THRESHOLD = 50;\n\n const current = () => props.images[index()];\n const isMultiple = () => props.images.length > 1;\n const prev = () => {\n if (!isMultiple()) return;\n setIndex((i) => (i - 1 + props.images.length) % props.images.length);\n };\n const next = () => {\n if (!isMultiple()) return;\n setIndex((i) => (i + 1) % props.images.length);\n };\n\n const close = () => {\n dialogRef.close();\n returnFocus(opener, openerRing);\n props.onClose();\n };\n\n // A key belongs to the Lightbox while focus is inside it, or on the page with no other dialog open. A dialog opened\n // from one of its actions keeps its own keys: Escape closes that dialog, and arrows stay in its fields.\n const ownsKey = (target: EventTarget | null) =>\n target instanceof Node &&\n (dialogRef.contains(target) ||\n (target === document.body && Array.from(document.querySelectorAll(\"dialog[open]\")).every((dialog) => dialog === dialogRef)));\n\n // Keyboard navigation\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!ownsKey(e.target)) return;\n switch (e.key) {\n case \"Escape\":\n // Handled here, so WebKit does not light the returned focus for this key.\n e.preventDefault();\n close();\n break;\n case \"ArrowLeft\":\n prev();\n break;\n case \"ArrowRight\":\n next();\n break;\n }\n };\n\n // Touch handlers for swipe gestures\n const handleTouchStart = (e: TouchEvent) => {\n const touch = e.touches[0];\n if (touch) {\n touchStartX = touch.clientX;\n touchStartY = touch.clientY;\n }\n };\n\n const handleTouchEnd = (e: TouchEvent) => {\n const touch = e.changedTouches[0];\n if (!touch) return;\n\n const deltaX = touch.clientX - touchStartX;\n const deltaY = touch.clientY - touchStartY;\n\n // Only trigger swipe if horizontal movement is dominant\n if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > SWIPE_THRESHOLD) {\n if (deltaX > 0) {\n prev();\n } else {\n next();\n }\n }\n };\n\n // Click on backdrop closes lightbox\n const handleBackdropClick = (e: MouseEvent) => {\n if (e.target === dialogRef) {\n close();\n }\n };\n\n // The browser cancels the dialog itself, as on the Android back gesture;\n // closing here still returns focus and tells the owner.\n const handleCancel = (e: Event) => {\n e.preventDefault();\n close();\n };\n\n // Registration and teardown both live in onMount: onCleanup also runs when an\n // SSR render is disposed, where `document` does not exist.\n onMount(() => {\n opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n openerRing = ringOnReturn(opener);\n // The native dialog would return focus with a ring of the browser's choosing.\n opener?.blur();\n dialogRef.showModal();\n document.addEventListener(\"keydown\", handleKeyDown);\n onCleanup(() => document.removeEventListener(\"keydown\", handleKeyDown));\n });\n\n return (\n <dialog\n ref={dialogRef}\n class=\"k2b-content-lightbox\"\n onMouseDown={handleBackdropClick}\n onCancel={handleCancel}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n aria-label={lightboxMessages().imageLightbox}\n >\n {/* Top bar */}\n <div class=\"k2b-content-lightbox__bar\">\n <div class=\"k2b-content-lightbox__meta\">\n <Show when={isMultiple()}>\n <span class=\"k2b-content-lightbox__counter k2b-overlay-surface\" aria-live=\"polite\">\n {index() + 1} / {props.images.length}\n </span>\n </Show>\n <Show when={current()?.alt}>\n <div class=\"k2b-content-lightbox__caption k2b-overlay-surface\">\n <div class=\"k2b-content-lightbox__caption-text\">{current()?.alt}</div>\n </div>\n </Show>\n </div>\n\n <div class=\"k2b-content-lightbox__actions\">\n <For each={current()?.actions ?? []}>\n {(action) => (\n <button\n type=\"button\"\n onClick={() => action.onClick()}\n class=\"k2b-content-lightbox__button k2b-overlay-surface\"\n aria-label={action.label}\n >\n <i class={action.icon} aria-hidden=\"true\" />\n <span class=\"k2b-content-lightbox__button-label\">{action.label}</span>\n </button>\n )}\n </For>\n <Show when={current()?.downloadUrl}>\n <a\n href={current()!.downloadUrl}\n download=\"\"\n class=\"k2b-content-lightbox__button k2b-overlay-surface\"\n aria-label={lightboxMessages().downloadImage}\n >\n <i class=\"ti ti-download\" aria-hidden=\"true\" />\n <span class=\"k2b-content-lightbox__button-label\">{messages().download}</span>\n </a>\n </Show>\n <button\n type=\"button\"\n onClick={close}\n class=\"k2b-content-lightbox__button k2b-overlay-surface\"\n aria-label={lightboxMessages().closeLightbox}\n >\n <i class=\"ti ti-x\" aria-hidden=\"true\" />\n <span class=\"k2b-content-lightbox__button-label\">{messages().close}</span>\n </button>\n </div>\n </div>\n\n <div class=\"k2b-content-lightbox__stage\">\n <Show when={current()}>\n {(image) => <img src={image().src} alt={image().alt ?? \"\"} class=\"k2b-content-lightbox__image\" draggable={false} />}\n </Show>\n </div>\n\n <Show when={isMultiple()}>\n <button\n type=\"button\"\n onClick={prev}\n class=\"k2b-content-lightbox__nav k2b-overlay-surface\"\n data-direction=\"previous\"\n aria-label={lightboxMessages().previousImage}\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={next}\n class=\"k2b-content-lightbox__nav k2b-overlay-surface\"\n data-direction=\"next\"\n aria-label={lightboxMessages().nextImage}\n >\n <i class=\"ti ti-chevron-right\" aria-hidden=\"true\" />\n </button>\n </Show>\n\n <Show when={isMultiple() && props.images.length <= 10}>\n <div class=\"k2b-content-lightbox__dots k2b-overlay-surface\" role=\"group\" aria-label={lightboxMessages().imageNavigation}>\n <For each={props.images}>\n {(_, i) => (\n <button\n type=\"button\"\n onClick={() => setIndex(i())}\n class=\"k2b-content-lightbox__dot\"\n aria-current={index() === i() ? \"true\" : undefined}\n aria-label={lightboxMessages().goToImage({ index: i() + 1 })}\n />\n )}\n </For>\n </div>\n </Show>\n </dialog>\n );\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["content/Lightbox.tsx"],"sourcesContent":["import { createEffect, createSignal, For, onCleanup, onMount, Show } from \"solid-js\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { useLightboxMessages } from \"./lightbox-messages\";\nimport { ThumbnailStrip } from \"./ThumbnailStrip\";\n\n/** One more thing to do with the shown image, beside Download: a short label, its icon class, and what it does. */\nexport type LightboxAction = {\n label: string;\n icon: string;\n onClick: () => void;\n};\n\nexport type LightboxImage = {\n src: string;\n alt?: string;\n downloadUrl?: string;\n /** Actions for this image, shown before Download in the same style. */\n actions?: readonly LightboxAction[];\n /** A small version for the thumbnail strip, shown from four images on; without it the strip loads `src`. */\n thumbnail?: string;\n};\n\n/** From this many images the gallery shows a thumbnail strip instead of dots. */\nconst STRIP_FROM = 4;\n\ntype LightboxProps = {\n images: LightboxImage[];\n initialIndex?: number;\n onClose: () => void;\n};\n\n/**\n * Minimal, accessible lightbox using native <dialog>.\n * Supports keyboard navigation, touch swipe gestures, and screen readers.\n */\nexport default function Lightbox(props: LightboxProps) {\n const messages = useUiMessages();\n const lightboxMessages = useLightboxMessages();\n const clampIndex = (value: number, images = props.images): number =>\n images.length === 0 ? 0 : Math.max(0, Math.min(value, images.length - 1));\n const [index, setIndex] = createSignal(clampIndex(props.initialIndex ?? 0));\n let dialogRef!: HTMLDialogElement;\n let opener: HTMLElement | null = null;\n let openerRing = true;\n\n createEffect(\n (previous: { images: LightboxImage[]; initialIndex: number | undefined }) => {\n const images = props.images;\n const initialIndex = props.initialIndex;\n setIndex((currentIndex) => {\n if (initialIndex !== previous.initialIndex) return clampIndex(initialIndex ?? 0, images);\n const previousImage = previous.images[currentIndex];\n const retained = previousImage ? images.findIndex((image) => image === previousImage || image.src === previousImage.src) : -1;\n return retained >= 0 ? retained : clampIndex(currentIndex, images);\n });\n return { images, initialIndex };\n },\n { images: props.images, initialIndex: props.initialIndex },\n );\n\n // Touch gesture tracking\n let touchStartX = 0;\n let touchStartY = 0;\n const SWIPE_THRESHOLD = 50;\n\n const current = () => props.images[index()];\n const isMultiple = () => props.images.length > 1;\n const prev = () => {\n if (!isMultiple()) return;\n setIndex((i) => (i - 1 + props.images.length) % props.images.length);\n };\n const next = () => {\n if (!isMultiple()) return;\n setIndex((i) => (i + 1) % props.images.length);\n };\n\n const close = () => {\n dialogRef.close();\n returnFocus(opener, openerRing);\n props.onClose();\n };\n\n // A key belongs to the Lightbox while focus is inside it, or on the page with no other dialog open. A dialog opened\n // from one of its actions keeps its own keys: Escape closes that dialog, and arrows stay in its fields.\n const ownsKey = (target: EventTarget | null) =>\n target instanceof Node &&\n (dialogRef.contains(target) ||\n (target === document.body && Array.from(document.querySelectorAll(\"dialog[open]\")).every((dialog) => dialog === dialogRef)));\n\n // Keyboard navigation\n const handleKeyDown = (e: KeyboardEvent) => {\n // A part that handled the key itself, such as the thumbnail strip moving its focus, keeps it.\n if (e.defaultPrevented || !ownsKey(e.target)) return;\n switch (e.key) {\n case \"Escape\":\n // Handled here, so WebKit does not light the returned focus for this key.\n e.preventDefault();\n close();\n break;\n case \"ArrowLeft\":\n prev();\n break;\n case \"ArrowRight\":\n next();\n break;\n }\n };\n\n // Touch handlers for swipe gestures\n const handleTouchStart = (e: TouchEvent) => {\n const touch = e.touches[0];\n if (touch) {\n touchStartX = touch.clientX;\n touchStartY = touch.clientY;\n }\n };\n\n const handleTouchEnd = (e: TouchEvent) => {\n const touch = e.changedTouches[0];\n if (!touch) return;\n\n const deltaX = touch.clientX - touchStartX;\n const deltaY = touch.clientY - touchStartY;\n\n // Only trigger swipe if horizontal movement is dominant\n if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > SWIPE_THRESHOLD) {\n if (deltaX > 0) {\n prev();\n } else {\n next();\n }\n }\n };\n\n // Click on backdrop closes lightbox\n const handleBackdropClick = (e: MouseEvent) => {\n if (e.target === dialogRef) {\n close();\n }\n };\n\n // The browser cancels the dialog itself, as on the Android back gesture;\n // closing here still returns focus and tells the owner.\n const handleCancel = (e: Event) => {\n e.preventDefault();\n close();\n };\n\n // Registration and teardown both live in onMount: onCleanup also runs when an\n // SSR render is disposed, where `document` does not exist.\n onMount(() => {\n opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n openerRing = ringOnReturn(opener);\n // The native dialog would return focus with a ring of the browser's choosing.\n opener?.blur();\n dialogRef.showModal();\n document.addEventListener(\"keydown\", handleKeyDown);\n onCleanup(() => document.removeEventListener(\"keydown\", handleKeyDown));\n });\n\n return (\n <dialog\n ref={dialogRef}\n class=\"k2b-content-lightbox\"\n onMouseDown={handleBackdropClick}\n onCancel={handleCancel}\n onTouchStart={handleTouchStart}\n onTouchEnd={handleTouchEnd}\n aria-label={lightboxMessages().imageLightbox}\n >\n {/* Top bar */}\n <div class=\"k2b-content-lightbox__bar\">\n <div class=\"k2b-content-lightbox__meta\">\n <Show when={isMultiple()}>\n <span class=\"k2b-content-lightbox__counter k2b-overlay-surface\" aria-live=\"polite\">\n {index() + 1} / {props.images.length}\n </span>\n </Show>\n <Show when={current()?.alt}>\n <div class=\"k2b-content-lightbox__caption k2b-overlay-surface\">\n <div class=\"k2b-content-lightbox__caption-text\">{current()?.alt}</div>\n </div>\n </Show>\n </div>\n\n <div class=\"k2b-content-lightbox__actions\">\n <For each={current()?.actions ?? []}>\n {(action) => (\n <button\n type=\"button\"\n onClick={() => action.onClick()}\n class=\"k2b-content-lightbox__button k2b-overlay-surface\"\n aria-label={action.label}\n >\n <i class={action.icon} aria-hidden=\"true\" />\n <span class=\"k2b-content-lightbox__button-label\">{action.label}</span>\n </button>\n )}\n </For>\n <Show when={current()?.downloadUrl}>\n <a\n href={current()!.downloadUrl}\n download=\"\"\n class=\"k2b-content-lightbox__button k2b-overlay-surface\"\n aria-label={lightboxMessages().downloadImage}\n >\n <i class=\"ti ti-download\" aria-hidden=\"true\" />\n <span class=\"k2b-content-lightbox__button-label\">{messages().download}</span>\n </a>\n </Show>\n <button\n type=\"button\"\n onClick={close}\n class=\"k2b-content-lightbox__button k2b-overlay-surface\"\n aria-label={lightboxMessages().closeLightbox}\n >\n <i class=\"ti ti-x\" aria-hidden=\"true\" />\n <span class=\"k2b-content-lightbox__button-label\">{messages().close}</span>\n </button>\n </div>\n </div>\n\n <div class=\"k2b-content-lightbox__stage\">\n <Show when={current()}>\n {(image) => <img src={image().src} alt={image().alt ?? \"\"} class=\"k2b-content-lightbox__image\" draggable={false} />}\n </Show>\n </div>\n\n <Show when={isMultiple()}>\n <button\n type=\"button\"\n onClick={prev}\n class=\"k2b-content-lightbox__nav k2b-overlay-surface\"\n data-direction=\"previous\"\n aria-label={lightboxMessages().previousImage}\n >\n <i class=\"ti ti-chevron-left\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={next}\n class=\"k2b-content-lightbox__nav k2b-overlay-surface\"\n data-direction=\"next\"\n aria-label={lightboxMessages().nextImage}\n >\n <i class=\"ti ti-chevron-right\" aria-hidden=\"true\" />\n </button>\n </Show>\n\n <Show when={props.images.length >= STRIP_FROM}>\n <ThumbnailStrip\n class=\"k2b-content-lightbox__strip k2b-overlay-surface\"\n label={lightboxMessages().imageNavigation}\n items={props.images.map((image, i) => ({\n id: String(i),\n name: image.alt || lightboxMessages().goToImage({ index: i + 1 }),\n thumbnail: image.thumbnail ?? image.src,\n }))}\n currentItem={String(index())}\n onSelectItem={(id) => setIndex(Number(id))}\n />\n </Show>\n\n <Show when={isMultiple() && props.images.length < STRIP_FROM}>\n <div class=\"k2b-content-lightbox__dots k2b-overlay-surface\" role=\"group\" aria-label={lightboxMessages().imageNavigation}>\n <For each={props.images}>\n {(_, i) => (\n <button\n type=\"button\"\n onClick={() => setIndex(i())}\n class=\"k2b-content-lightbox__dot\"\n aria-current={index() === i() ? \"true\" : undefined}\n aria-label={lightboxMessages().goToImage({ index: i() + 1 })}\n />\n )}\n </For>\n </div>\n </Show>\n </dialog>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,174 @@
1
+ import { template as _$template } from "solid-js/web";
2
+ import { delegateEvents as _$delegateEvents } from "solid-js/web";
3
+ import { runHydrationEvents as _$runHydrationEvents } from "solid-js/web";
4
+ import { setAttribute as _$setAttribute } from "solid-js/web";
5
+ import { className as _$className } from "solid-js/web";
6
+ import { effect as _$effect } from "solid-js/web";
7
+ import { getNextElement as _$getNextElement } from "solid-js/web";
8
+ import { getNextMarker as _$getNextMarker } from "solid-js/web";
9
+ import { insert as _$insert } from "solid-js/web";
10
+ import { createComponent as _$createComponent } from "solid-js/web";
11
+ import { addEventListener as _$addEventListener } from "solid-js/web";
12
+ import { use as _$use } from "solid-js/web";
13
+ var _tmpl$ = /*#__PURE__*/_$template(`<nav><ul data-scroll-fade-mode=both data-scroll-fade-axis=horizontal><!$><!/><!$><!/>`),
14
+ _tmpl$2 = /*#__PURE__*/_$template(`<span class=k2b-thumbnail-strip__dirty aria-hidden=true>`),
15
+ _tmpl$3 = /*#__PURE__*/_$template(`<li><button type=button class=k2b-thumbnail-strip__thumb><!$><!/><!$><!/>`),
16
+ _tmpl$4 = /*#__PURE__*/_$template(`<img alt loading=lazy decoding=async>`, true, false, false),
17
+ _tmpl$5 = /*#__PURE__*/_$template(`<i class="ti ti-plus"aria-hidden=true>`),
18
+ _tmpl$6 = /*#__PURE__*/_$template(`<li>`);
19
+ import { createEffect, For, on, Show } from "solid-js";
20
+ import { IconButton } from "../actions/Button";
21
+ import { createScrollFade } from "../layout/scroll-fade";
22
+ import { useThumbnailStripMessages } from "./thumbnail-strip-messages";
23
+
24
+ /** One image in a `ThumbnailStrip`. */
25
+
26
+ /** Room an item keeps from a fading edge when keyboard focus scrolls it into view. */
27
+ const EDGE = 48;
28
+
29
+ /**
30
+ * A row of image thumbnails, 44 px high, that scrolls sideways and fades softly only on the side where more images
31
+ * follow. The current image is ringed and is the strip's one tab stop; the arrow keys, Home, and End move focus between
32
+ * images, and Enter or a click shows one. A dot marks unsaved edits. The host decides where the strip sits, also on a
33
+ * phone, and whether it floats over a picture.
34
+ */
35
+ export function ThumbnailStrip(props) {
36
+ const messages = useThumbnailStripMessages();
37
+ let list;
38
+ createScrollFade(() => list, () => true);
39
+
40
+ // The current image scrolls into the middle whenever it or the list changes.
41
+ createEffect(on(() => [props.currentItem, props.items.length], () => queueMicrotask(() => {
42
+ const current = list?.querySelector('[aria-current="true"]');
43
+ if (!list || !current) return;
44
+ list.style.scrollBehavior = "auto";
45
+ list.scrollLeft = current.offsetLeft - (list.clientWidth - current.offsetWidth) / 2;
46
+ list.style.scrollBehavior = "";
47
+ })));
48
+
49
+ // A native listener, so a host that handles arrow keys itself, such as a lightbox, does not also see them.
50
+ const keys = event => {
51
+ // The "+" tile takes part in the arrow keys, so the keyboard reaches it without a tab stop of its own.
52
+ const thumbs = [...(list?.querySelectorAll(".k2b-thumbnail-strip__thumb, .k2b-thumbnail-strip__add") ?? [])];
53
+ const at = thumbs.findIndex(thumb => thumb === document.activeElement);
54
+ if (at < 0 || !list) return;
55
+ const to = {
56
+ ArrowRight: Math.min(thumbs.length - 1, at + 1),
57
+ ArrowLeft: Math.max(0, at - 1),
58
+ Home: 0,
59
+ End: thumbs.length - 1
60
+ }[event.key];
61
+ if (to === undefined) return;
62
+ event.preventDefault();
63
+ event.stopPropagation();
64
+ thumbs.forEach((thumb, index) => {
65
+ thumb.tabIndex = index === to ? 0 : -1;
66
+ });
67
+ const target = thumbs[to];
68
+ if (!target) return;
69
+ target.focus({
70
+ preventScroll: true
71
+ });
72
+ if (target.offsetLeft < list.scrollLeft + EDGE) list.scrollLeft = target.offsetLeft - EDGE;else if (target.offsetLeft + target.offsetWidth > list.scrollLeft + list.clientWidth - EDGE) list.scrollLeft = target.offsetLeft + target.offsetWidth - list.clientWidth + EDGE;
73
+ };
74
+ return (() => {
75
+ var _el$ = _$getNextElement(_tmpl$),
76
+ _el$2 = _el$.firstChild,
77
+ _el$3 = _el$2.firstChild,
78
+ [_el$4, _co$] = _$getNextMarker(_el$3.nextSibling),
79
+ _el$5 = _el$4.nextSibling,
80
+ [_el$6, _co$2] = _$getNextMarker(_el$5.nextSibling);
81
+ _$addEventListener(_el$2, "keydown", keys);
82
+ var _ref$ = list;
83
+ typeof _ref$ === "function" ? _$use(_ref$, _el$2) : list = _el$2;
84
+ _$insert(_el$2, _$createComponent(For, {
85
+ get each() {
86
+ return props.items;
87
+ },
88
+ children: item => {
89
+ const current = () => item.id === props.currentItem;
90
+ return (() => {
91
+ var _el$7 = _$getNextElement(_tmpl$3),
92
+ _el$8 = _el$7.firstChild,
93
+ _el$0 = _el$8.firstChild,
94
+ [_el$1, _co$3] = _$getNextMarker(_el$0.nextSibling),
95
+ _el$10 = _el$1.nextSibling,
96
+ [_el$11, _co$4] = _$getNextMarker(_el$10.nextSibling);
97
+ _el$8.$$click = () => {
98
+ if (!current()) props.onSelectItem(item.id);
99
+ };
100
+ _$insert(_el$8, () => {
101
+ const thumbnail = item.thumbnail;
102
+ if (typeof thumbnail === "function") return thumbnail();
103
+ return thumbnail ? (() => {
104
+ var _el$12 = _$getNextElement(_tmpl$4);
105
+ _$setAttribute(_el$12, "src", thumbnail);
106
+ _$setAttribute(_el$12, "draggable", false);
107
+ return _el$12;
108
+ })() : null;
109
+ }, _el$1, _co$3);
110
+ _$insert(_el$8, _$createComponent(Show, {
111
+ get when() {
112
+ return item.dirty;
113
+ },
114
+ get children() {
115
+ return _$getNextElement(_tmpl$2);
116
+ }
117
+ }), _el$11, _co$4);
118
+ _$effect(_p$ => {
119
+ var _v$3 = current(),
120
+ _v$4 = current() ? 0 : -1,
121
+ _v$5 = `${item.name}${current() ? `, ${messages().thumbnailCurrent}` : ""}${item.dirty ? `, ${messages().thumbnailUnsaved}` : ""}`,
122
+ _v$6 = item.name;
123
+ _v$3 !== _p$.e && _$setAttribute(_el$8, "aria-current", _p$.e = _v$3);
124
+ _v$4 !== _p$.t && _$setAttribute(_el$8, "tabindex", _p$.t = _v$4);
125
+ _v$5 !== _p$.a && _$setAttribute(_el$8, "aria-label", _p$.a = _v$5);
126
+ _v$6 !== _p$.o && _$setAttribute(_el$8, "title", _p$.o = _v$6);
127
+ return _p$;
128
+ }, {
129
+ e: undefined,
130
+ t: undefined,
131
+ a: undefined,
132
+ o: undefined
133
+ });
134
+ _$runHydrationEvents();
135
+ return _el$7;
136
+ })();
137
+ }
138
+ }), _el$4, _co$);
139
+ _$insert(_el$2, _$createComponent(Show, {
140
+ get when() {
141
+ return props.onAddItems;
142
+ },
143
+ children: add => (() => {
144
+ var _el$13 = _$getNextElement(_tmpl$6);
145
+ _$insert(_el$13, _$createComponent(IconButton, {
146
+ "class": "k2b-thumbnail-strip__add",
147
+ size: "sm",
148
+ tabindex: "-1",
149
+ get label() {
150
+ return messages().thumbnailAdd;
151
+ },
152
+ onClick: () => add()(),
153
+ get children() {
154
+ return _$getNextElement(_tmpl$5);
155
+ }
156
+ }));
157
+ return _el$13;
158
+ })()
159
+ }), _el$6, _co$2);
160
+ _$effect(_p$ => {
161
+ var _v$ = `k2b-thumbnail-strip ${props.class ?? ""}`,
162
+ _v$2 = props.label;
163
+ _v$ !== _p$.e && _$className(_el$, _p$.e = _v$);
164
+ _v$2 !== _p$.t && _$setAttribute(_el$, "aria-label", _p$.t = _v$2);
165
+ return _p$;
166
+ }, {
167
+ e: undefined,
168
+ t: undefined
169
+ });
170
+ return _el$;
171
+ })();
172
+ }
173
+ _$delegateEvents(["click"]);
174
+ //# sourceMappingURL=ThumbnailStrip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["content/ThumbnailStrip.tsx"],"sourcesContent":["import { createEffect, For, type JSX, on, Show } from \"solid-js\";\nimport { IconButton } from \"../actions/Button\";\nimport { createScrollFade } from \"../layout/scroll-fade\";\nimport { useThumbnailStripMessages } from \"./thumbnail-strip-messages\";\n\n/** One image in a `ThumbnailStrip`. */\nexport type ThumbnailStripItem = {\n id: string;\n name: string;\n /** Small image URL, or a component that renders the caller's own thumbnail; a neutral tile shows until it loads. */\n thumbnail?: string | (() => JSX.Element);\n /** Marks an image with unsaved edits. */\n dirty?: boolean;\n};\n\nexport type ThumbnailStripProps = {\n items: readonly ThumbnailStripItem[];\n /** The `id` of the shown image; it is ringed, scrolled into view, and the strip's one tab stop. */\n currentItem?: string;\n onSelectItem: (id: string) => void;\n /** Adds a \"+\" tile at the end. */\n onAddItems?: () => void;\n /** Accessible name of the strip, such as \"Images\". */\n label: string;\n /** The host's layer class, such as `k2b-overlay-surface` where the strip floats over a picture. */\n class?: string;\n};\n\n/** Room an item keeps from a fading edge when keyboard focus scrolls it into view. */\nconst EDGE = 48;\n\n/**\n * A row of image thumbnails, 44 px high, that scrolls sideways and fades softly only on the side where more images\n * follow. The current image is ringed and is the strip's one tab stop; the arrow keys, Home, and End move focus between\n * images, and Enter or a click shows one. A dot marks unsaved edits. The host decides where the strip sits, also on a\n * phone, and whether it floats over a picture.\n */\nexport function ThumbnailStrip(props: ThumbnailStripProps): JSX.Element {\n const messages = useThumbnailStripMessages();\n let list: HTMLUListElement | undefined;\n createScrollFade(\n () => list,\n () => true,\n );\n\n // The current image scrolls into the middle whenever it or the list changes.\n createEffect(\n on(\n () => [props.currentItem, props.items.length] as const,\n () =>\n queueMicrotask(() => {\n const current = list?.querySelector<HTMLElement>('[aria-current=\"true\"]');\n if (!list || !current) return;\n list.style.scrollBehavior = \"auto\";\n list.scrollLeft = current.offsetLeft - (list.clientWidth - current.offsetWidth) / 2;\n list.style.scrollBehavior = \"\";\n }),\n ),\n );\n\n // A native listener, so a host that handles arrow keys itself, such as a lightbox, does not also see them.\n const keys = (event: KeyboardEvent) => {\n // The \"+\" tile takes part in the arrow keys, so the keyboard reaches it without a tab stop of its own.\n const thumbs = [...(list?.querySelectorAll<HTMLButtonElement>(\".k2b-thumbnail-strip__thumb, .k2b-thumbnail-strip__add\") ?? [])];\n const at = thumbs.findIndex((thumb) => thumb === document.activeElement);\n if (at < 0 || !list) return;\n const to = { ArrowRight: Math.min(thumbs.length - 1, at + 1), ArrowLeft: Math.max(0, at - 1), Home: 0, End: thumbs.length - 1 }[\n event.key\n ];\n if (to === undefined) return;\n event.preventDefault();\n event.stopPropagation();\n thumbs.forEach((thumb, index) => {\n thumb.tabIndex = index === to ? 0 : -1;\n });\n const target = thumbs[to];\n if (!target) return;\n target.focus({ preventScroll: true });\n if (target.offsetLeft < list.scrollLeft + EDGE) list.scrollLeft = target.offsetLeft - EDGE;\n else if (target.offsetLeft + target.offsetWidth > list.scrollLeft + list.clientWidth - EDGE)\n list.scrollLeft = target.offsetLeft + target.offsetWidth - list.clientWidth + EDGE;\n };\n\n return (\n <nav class={`k2b-thumbnail-strip ${props.class ?? \"\"}`} aria-label={props.label}>\n <ul ref={list} data-scroll-fade-mode=\"both\" data-scroll-fade-axis=\"horizontal\" on:keydown={keys}>\n <For each={props.items}>\n {(item) => {\n const current = () => item.id === props.currentItem;\n return (\n <li>\n <button\n type=\"button\"\n class=\"k2b-thumbnail-strip__thumb\"\n aria-current={current()}\n tabindex={current() ? 0 : -1}\n aria-label={`${item.name}${current() ? `, ${messages().thumbnailCurrent}` : \"\"}${item.dirty ? `, ${messages().thumbnailUnsaved}` : \"\"}`}\n title={item.name}\n onClick={() => {\n if (!current()) props.onSelectItem(item.id);\n }}\n >\n {(() => {\n const thumbnail = item.thumbnail;\n if (typeof thumbnail === \"function\") return thumbnail();\n return thumbnail ? <img src={thumbnail} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} /> : null;\n })()}\n <Show when={item.dirty}>\n <span class=\"k2b-thumbnail-strip__dirty\" aria-hidden=\"true\" />\n </Show>\n </button>\n </li>\n );\n }}\n </For>\n <Show when={props.onAddItems}>\n {(add) => (\n <li>\n <IconButton class=\"k2b-thumbnail-strip__add\" size=\"sm\" tabindex=\"-1\" label={messages().thumbnailAdd} onClick={() => add()()}>\n <i class=\"ti ti-plus\" aria-hidden=\"true\" />\n </IconButton>\n </li>\n )}\n </Show>\n </ul>\n </nav>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,3 +1,4 @@
1
+ import { barPercent } from "../charts/core/bar-labels";
1
2
  import { chartDatumSvg, computeDomain, escapeXml, extendDomainToNice, MAX_AXIS_TICKS, mapLog, mapRange, niceLogTicks, niceStep, referenceLineAttributes, svgRoot } from "../charts/core/svg-renderers";
2
3
  import { chartMessages } from "./chart-message-catalog";
3
4
 
@@ -148,6 +149,15 @@ const columnsFit = (opts, finite, width) => {
148
149
  /** The renderer's plan for a chart: its layout and logical height at `width`, derived from the data alone. */
149
150
  const barChartPlan = (opts, width = 480) => {
150
151
  const finite = finiteItems(opts.data ?? []);
152
+ if (opts.layout === "rows" && !opts.series) return {
153
+ layout: "rows",
154
+ height: rankedBarLayout(opts, {
155
+ width,
156
+ height: 280
157
+ }).height,
158
+ finite,
159
+ header: headerHeight(opts)
160
+ };
151
161
  if (opts.series) return {
152
162
  layout: "columns",
153
163
  height: 280,
@@ -460,6 +470,7 @@ const renderRows = (opts, plan, width, inspect) => {
460
470
  */
461
471
  export const renderBarChartSvg = opts => {
462
472
  if (opts.series) return renderSeriesBars(opts);
473
+ if (opts.layout === "rows") return renderRankedBars(opts);
463
474
  const plan = barChartPlan(opts, opts.width);
464
475
  // The empty drawing of stdlib's bar chart, written here so bundles leave out stdlib's whole bar renderer.
465
476
  if (!plan.finite.length) return svgRoot({
@@ -703,4 +714,91 @@ const visibleCategoryLabels = (labels, left, slot, width, units) => {
703
714
  const first = Math.max(0, labels.findIndex((_, i) => inside(i)));
704
715
  return new Set(labels.flatMap((_, i) => i >= first && (i - first) % step === 0 && inside(i) ? [i] : []));
705
716
  };
717
+
718
+ /** Ranked rows retain original indices. A limit changes the drawing, never the denominator or source data. */
719
+ export function rankedBarLayout(opts, size = {
720
+ width: 480,
721
+ height: 280
722
+ }) {
723
+ if (opts.limit !== undefined && (!Number.isInteger(opts.limit) || opts.limit < 1)) throw new RangeError("Bar list limit must be a positive integer");
724
+ if (opts.data?.some(d => Number.isFinite(d.value) && d.value < 0)) throw new RangeError("Ranked bar lists require nonnegative values");
725
+ const sorted = (opts.data ?? []).map((item, index) => ({
726
+ ...item,
727
+ index
728
+ })).sort((a, b) => (Number.isFinite(b.value) ? b.value : -Infinity) - (Number.isFinite(a.value) ? a.value : -Infinity));
729
+ const total = sorted.reduce((sum, d) => sum + (Number.isFinite(d.value) ? d.value : 0), 0);
730
+ const shown = sorted.slice(0, opts.limit),
731
+ rest = sorted.slice(shown.length);
732
+ const top = headerHeight(opts),
733
+ unit = size.width / narrow(opts),
734
+ format = opts.valueFormat ?? opts.yAxis?.format ?? (v => new Intl.NumberFormat(opts.locale ?? "en").format(v)),
735
+ percent = barPercent(opts.locale);
736
+ const shareWidth = opts.share ? 48 * unit : 0;
737
+ const valueWidth = Math.max(20, ...shown.map(d => chartTextWidth(Number.isFinite(d.value) ? format(d.value) : "–"))) * unit;
738
+ const labelWidth = Math.max(8, (size.width - shareWidth - valueWidth - 36 * unit) / unit);
739
+ const max = Math.max(0, ...sorted.map(d => Number.isFinite(d.value) ? d.value : 0));
740
+ const rows = shown.map((d, rank) => ({
741
+ ...d,
742
+ rank: rank + 1,
743
+ y: top + rank * 36,
744
+ width: Number.isFinite(d.value) && max > 0 ? d.value / max * size.width : 0,
745
+ share: Number.isFinite(d.value) && total > 0 ? d.value / total : null
746
+ }));
747
+ return {
748
+ ...size,
749
+ height: Math.max(size.height, top + rows.length * 36 + (rest.length ? 24 : 0)),
750
+ rows,
751
+ total,
752
+ rest,
753
+ restValue: rest.some(d => Number.isFinite(d.value)) ? rest.reduce((sum, d) => sum + (Number.isFinite(d.value) ? d.value : 0), 0) : null,
754
+ top,
755
+ unit,
756
+ format,
757
+ percent,
758
+ shareWidth,
759
+ labelWidth
760
+ };
761
+ }
762
+ const renderRankedBars = opts => {
763
+ const g = rankedBarLayout(opts, opts),
764
+ labels = chartMessages.resolve([opts.locale ?? "en"]).t;
765
+ const parts = [renderHeader(opts, opts.width)];
766
+ const color = opts.tone ? `var(--k2b-flow-${opts.tone}-mark,var(--stdlib-chart-c1,#3b82f6))` : "var(--stdlib-chart-c1,#3b82f6)";
767
+ const text = (x, y, value, anchor = "start", cls = "stdlib-chart-tick-label") => `<text class="${cls}" x="${x}" y="${y}" text-anchor="${anchor}">${escapeXml(value)}</text>`;
768
+ for (const row of g.rows) {
769
+ const value = Number.isFinite(row.value) ? row.value : null,
770
+ written = value === null ? "–" : g.format(value);
771
+ const share = row.share === null ? "–" : g.percent.format(row.share);
772
+ const label = wrapLabel(row.label, g.labelWidth * 11 / 13, 1).lines[0] ?? row.label;
773
+ const marks = `<rect class="k2b-chart-category-band" fill="none" pointer-events="all" x="0" y="${row.y}" width="${g.width}" height="32" rx="6"/><rect x="0" y="${row.y}" width="${row.width}" height="32" rx="6" style="fill:${color};fill-opacity:.16;pointer-events:none"/>${text(10 * g.unit, row.y + 20, label)}${text(g.width - 10 * g.unit - g.shareWidth, row.y + 20, written, "end", "stdlib-chart-bar-value")}${opts.share ? text(g.width - 10 * g.unit, row.y + 20, share, "end") : ""}`;
774
+ parts.push(chartDatumSvg(marks, {
775
+ role: "item",
776
+ index: row.index,
777
+ label: row.label,
778
+ anchor: [g.width / 2, row.y],
779
+ values: [{
780
+ key: "value",
781
+ value: value ?? "–",
782
+ formatted: written
783
+ }, {
784
+ key: "percent",
785
+ value: row.share === null ? "–" : row.share * 100,
786
+ formatted: share
787
+ }, {
788
+ key: "rank",
789
+ value: row.rank
790
+ }]
791
+ }, opts.inspect ?? false, `${row.label} · ${written} · ${share} · ${labels.chartFields({
792
+ key: "rank"
793
+ })}: ${row.rank}`));
794
+ }
795
+ if (g.rest.length) parts.push(text(10 * g.unit, g.top + g.rows.length * 36 + 16, `${labels.chartBarMore({
796
+ n: g.rest.length
797
+ })} · ${g.restValue === null ? "–" : g.format(g.restValue)}`));
798
+ return svgRoot({
799
+ ...opts,
800
+ height: g.height,
801
+ className: [opts.className, "k2b-chart-ranked"].filter(Boolean).join(" ")
802
+ }, parts.join("")).replace("</style>", ".k2b-chart-ranked :is(.stdlib-chart-tick-label,.stdlib-chart-bar-value){font-size:13px}</style>").replace("<svg ", '<svg data-bar-layout="rows" ');
803
+ };
706
804
  //# sourceMappingURL=chart-bar.js.map