@k2b/ui 0.18.0 → 0.19.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 (178) hide show
  1. package/README.md +3 -2
  2. package/dist/browser/actions/ContextMenu.js +3 -1
  3. package/dist/browser/actions/ContextMenu.js.map +1 -1
  4. package/dist/browser/actions/Dropdown.js +91 -46
  5. package/dist/browser/actions/Dropdown.js.map +1 -1
  6. package/dist/browser/actions/FilterChip.js +4 -1
  7. package/dist/browser/actions/FilterChip.js.map +1 -1
  8. package/dist/browser/chat/ChatComposer.js +1293 -534
  9. package/dist/browser/chat/ChatComposer.js.map +1 -1
  10. package/dist/browser/chat/chat-behavior.js +9 -0
  11. package/dist/browser/chat/chat-behavior.js.map +1 -1
  12. package/dist/browser/chat/index.js.map +1 -1
  13. package/dist/browser/chat/types.js.map +1 -1
  14. package/dist/browser/content/Calendar.js +579 -533
  15. package/dist/browser/content/Calendar.js.map +1 -1
  16. package/dist/browser/content/Lightbox.js +76 -40
  17. package/dist/browser/content/Lightbox.js.map +1 -1
  18. package/dist/browser/content/PdfPreview.js +146 -79
  19. package/dist/browser/content/PdfPreview.js.map +1 -1
  20. package/dist/browser/content/Timeline.js +1038 -0
  21. package/dist/browser/content/Timeline.js.map +1 -0
  22. package/dist/browser/content/VideoPlayer.js +29 -9
  23. package/dist/browser/content/VideoPlayer.js.map +1 -1
  24. package/dist/browser/content/chart-inspection.js +7 -2
  25. package/dist/browser/content/chart-inspection.js.map +1 -1
  26. package/dist/browser/content/index.js +1 -0
  27. package/dist/browser/content/index.js.map +1 -1
  28. package/dist/browser/content/timeline-model.js +579 -0
  29. package/dist/browser/content/timeline-model.js.map +1 -0
  30. package/dist/browser/feedback/dialog-core.js +26 -14
  31. package/dist/browser/feedback/dialog-core.js.map +1 -1
  32. package/dist/browser/inputs/ChoiceChips.js +66 -0
  33. package/dist/browser/inputs/ChoiceChips.js.map +1 -0
  34. package/dist/browser/inputs/ChoiceGroups.js +53 -12
  35. package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
  36. package/dist/browser/inputs/FileInputs.js +48 -33
  37. package/dist/browser/inputs/FileInputs.js.map +1 -1
  38. package/dist/browser/inputs/SignatureInput.js +558 -0
  39. package/dist/browser/inputs/SignatureInput.js.map +1 -0
  40. package/dist/browser/inputs/emoji/EmojiPicker.js +841 -0
  41. package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -0
  42. package/dist/browser/inputs/emoji/emoji-data.js +1903 -0
  43. package/dist/browser/inputs/emoji/emoji-data.js.map +1 -0
  44. package/dist/browser/inputs/emoji/emoji-index.js +209 -0
  45. package/dist/browser/inputs/emoji/emoji-index.js.map +1 -0
  46. package/dist/browser/inputs/emoji/emoji-messages.js +64 -0
  47. package/dist/browser/inputs/emoji/emoji-messages.js.map +1 -0
  48. package/dist/browser/inputs/index.js +5 -0
  49. package/dist/browser/inputs/index.js.map +1 -1
  50. package/dist/browser/inputs/markdown/actions.js +48 -2
  51. package/dist/browser/inputs/markdown/actions.js.map +1 -1
  52. package/dist/browser/inputs/markdown/code-zone.js +38 -2
  53. package/dist/browser/inputs/markdown/code-zone.js.map +1 -1
  54. package/dist/browser/inputs/signature-messages.js +39 -0
  55. package/dist/browser/inputs/signature-messages.js.map +1 -0
  56. package/dist/browser/inputs/signature.js +208 -0
  57. package/dist/browser/inputs/signature.js.map +1 -0
  58. package/dist/browser/internal/render-error.js +49 -0
  59. package/dist/browser/internal/render-error.js.map +1 -0
  60. package/dist/browser/intl/messages.js +108 -0
  61. package/dist/browser/intl/messages.js.map +1 -1
  62. package/dist/browser/layout/AppWorkspace.js +321 -222
  63. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  64. package/dist/browser/layout/FloatingWindow.js +4 -1
  65. package/dist/browser/layout/FloatingWindow.js.map +1 -1
  66. package/dist/browser/layout/SidebarItemPreview.js +5 -1
  67. package/dist/browser/layout/SidebarItemPreview.js.map +1 -1
  68. package/dist/browser/layout/index.js.map +1 -1
  69. package/dist/browser/surfaces/ResourceCard.js +212 -0
  70. package/dist/browser/surfaces/ResourceCard.js.map +1 -0
  71. package/dist/browser/surfaces/index.js +1 -0
  72. package/dist/browser/surfaces/index.js.map +1 -1
  73. package/dist/licenses/emojibase-data.txt +21 -0
  74. package/dist/ssr/actions/ContextMenu.js +3 -1
  75. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  76. package/dist/ssr/actions/Dropdown.js +49 -12
  77. package/dist/ssr/actions/Dropdown.js.map +1 -1
  78. package/dist/ssr/actions/FilterChip.js +4 -1
  79. package/dist/ssr/actions/FilterChip.js.map +1 -1
  80. package/dist/ssr/chat/ChatComposer.js +805 -214
  81. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  82. package/dist/ssr/chat/chat-behavior.js +9 -0
  83. package/dist/ssr/chat/chat-behavior.js.map +1 -1
  84. package/dist/ssr/chat/index.js.map +1 -1
  85. package/dist/ssr/chat/types.js.map +1 -1
  86. package/dist/ssr/content/Calendar.js +183 -142
  87. package/dist/ssr/content/Calendar.js.map +1 -1
  88. package/dist/ssr/content/Lightbox.js +19 -5
  89. package/dist/ssr/content/Lightbox.js.map +1 -1
  90. package/dist/ssr/content/PdfPreview.js +77 -36
  91. package/dist/ssr/content/PdfPreview.js.map +1 -1
  92. package/dist/ssr/content/Timeline.js +804 -0
  93. package/dist/ssr/content/Timeline.js.map +1 -0
  94. package/dist/ssr/content/VideoPlayer.js +29 -9
  95. package/dist/ssr/content/VideoPlayer.js.map +1 -1
  96. package/dist/ssr/content/chart-inspection.js +7 -2
  97. package/dist/ssr/content/chart-inspection.js.map +1 -1
  98. package/dist/ssr/content/index.js +1 -0
  99. package/dist/ssr/content/index.js.map +1 -1
  100. package/dist/ssr/content/timeline-model.js +579 -0
  101. package/dist/ssr/content/timeline-model.js.map +1 -0
  102. package/dist/ssr/feedback/dialog-core.js +26 -14
  103. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  104. package/dist/ssr/inputs/ChoiceChips.js +66 -0
  105. package/dist/ssr/inputs/ChoiceChips.js.map +1 -0
  106. package/dist/ssr/inputs/ChoiceGroups.js +12 -4
  107. package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
  108. package/dist/ssr/inputs/FileInputs.js +19 -4
  109. package/dist/ssr/inputs/FileInputs.js.map +1 -1
  110. package/dist/ssr/inputs/SignatureInput.js +404 -0
  111. package/dist/ssr/inputs/SignatureInput.js.map +1 -0
  112. package/dist/ssr/inputs/emoji/EmojiPicker.js +551 -0
  113. package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -0
  114. package/dist/ssr/inputs/emoji/emoji-data.js +1903 -0
  115. package/dist/ssr/inputs/emoji/emoji-data.js.map +1 -0
  116. package/dist/ssr/inputs/emoji/emoji-index.js +209 -0
  117. package/dist/ssr/inputs/emoji/emoji-index.js.map +1 -0
  118. package/dist/ssr/inputs/emoji/emoji-messages.js +64 -0
  119. package/dist/ssr/inputs/emoji/emoji-messages.js.map +1 -0
  120. package/dist/ssr/inputs/index.js +5 -0
  121. package/dist/ssr/inputs/index.js.map +1 -1
  122. package/dist/ssr/inputs/markdown/actions.js +48 -2
  123. package/dist/ssr/inputs/markdown/actions.js.map +1 -1
  124. package/dist/ssr/inputs/markdown/code-zone.js +38 -2
  125. package/dist/ssr/inputs/markdown/code-zone.js.map +1 -1
  126. package/dist/ssr/inputs/signature-messages.js +39 -0
  127. package/dist/ssr/inputs/signature-messages.js.map +1 -0
  128. package/dist/ssr/inputs/signature.js +208 -0
  129. package/dist/ssr/inputs/signature.js.map +1 -0
  130. package/dist/ssr/internal/render-error.js +49 -0
  131. package/dist/ssr/internal/render-error.js.map +1 -0
  132. package/dist/ssr/intl/messages.js +108 -0
  133. package/dist/ssr/intl/messages.js.map +1 -1
  134. package/dist/ssr/layout/AppWorkspace.js +112 -41
  135. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  136. package/dist/ssr/layout/FloatingWindow.js +4 -1
  137. package/dist/ssr/layout/FloatingWindow.js.map +1 -1
  138. package/dist/ssr/layout/SidebarItemPreview.js +5 -1
  139. package/dist/ssr/layout/SidebarItemPreview.js.map +1 -1
  140. package/dist/ssr/layout/index.js.map +1 -1
  141. package/dist/ssr/surfaces/ResourceCard.js +115 -0
  142. package/dist/ssr/surfaces/ResourceCard.js.map +1 -0
  143. package/dist/ssr/surfaces/index.js +1 -0
  144. package/dist/ssr/surfaces/index.js.map +1 -1
  145. package/dist/styles.css +1 -1
  146. package/dist/types/actions/Dropdown.d.ts +13 -1
  147. package/dist/types/actions/FilterChip.d.ts +5 -0
  148. package/dist/types/chat/ChatComposer.d.ts +24 -1
  149. package/dist/types/chat/chat-behavior.d.ts +15 -0
  150. package/dist/types/chat/index.d.ts +1 -1
  151. package/dist/types/chat/types.d.ts +34 -0
  152. package/dist/types/content/Calendar.d.ts +28 -8
  153. package/dist/types/content/Lightbox.d.ts +8 -0
  154. package/dist/types/content/Timeline.d.ts +49 -0
  155. package/dist/types/content/index.d.ts +4 -2
  156. package/dist/types/content/timeline-model.d.ts +148 -0
  157. package/dist/types/feedback/dialog-core.d.ts +1 -1
  158. package/dist/types/inputs/ChoiceChips.d.ts +20 -0
  159. package/dist/types/inputs/ChoiceGroups.d.ts +11 -2
  160. package/dist/types/inputs/FileInputs.d.ts +6 -0
  161. package/dist/types/inputs/SignatureInput.d.ts +22 -0
  162. package/dist/types/inputs/emoji/EmojiPicker.d.ts +42 -0
  163. package/dist/types/inputs/emoji/emoji-data.d.ts +2 -0
  164. package/dist/types/inputs/emoji/emoji-index.d.ts +109 -0
  165. package/dist/types/inputs/emoji/emoji-messages.d.ts +30 -0
  166. package/dist/types/inputs/index.d.ts +10 -0
  167. package/dist/types/inputs/markdown/actions.d.ts +4 -0
  168. package/dist/types/inputs/markdown/code-zone.d.ts +7 -0
  169. package/dist/types/inputs/signature-messages.d.ts +11 -0
  170. package/dist/types/inputs/signature.d.ts +72 -0
  171. package/dist/types/internal/render-error.d.ts +13 -0
  172. package/dist/types/intl/messages.d.ts +54 -0
  173. package/dist/types/layout/AppWorkspace.d.ts +17 -0
  174. package/dist/types/layout/SidebarItemPreview.d.ts +2 -0
  175. package/dist/types/layout/index.d.ts +1 -1
  176. package/dist/types/surfaces/ResourceCard.d.ts +31 -0
  177. package/dist/types/surfaces/index.d.ts +2 -0
  178. package/package.json +5 -4
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["inputs/SignatureInput.tsx"],"sourcesContent":["import { createEffect, createSignal, For, type JSX, on, Show, untrack } from \"solid-js\";\nimport { IconButton } from \"../actions/Button\";\nimport { SegmentedControl } from \"../actions/SegmentedControl\";\nimport { createFieldMeta, Field, fieldDescribedBy } from \"../internal/field\";\nimport { useUiMessages } from \"../intl/messages\";\nimport type { ValueFieldProps } from \"./field-contract\";\nimport { commitFieldValue, resolveMaybeAccessor } from \"./field-contract\";\nimport {\n drawingToSvg,\n type SignatureDrawing,\n type SignaturePoint,\n type SignatureValue,\n signatureName,\n strokeOutline,\n svgToDrawing,\n typedToSvg,\n} from \"./signature\";\nimport { useSignatureMessages } from \"./signature-messages\";\n\nexport type SignatureInputProps = ValueFieldProps<SignatureValue | null> & {\n /** Submits the value as JSON in a hidden input of this name. */\n name?: string;\n /** Shows the signature without allowing changes. */\n readOnly?: boolean;\n /**\n * Offers typing the name instead of drawing. Defaults to `true`: typing is the\n * keyboard and screen-reader path and helps people who cannot draw. Without\n * it, a typed value is still shown and can be cleared, but not edited.\n */\n allowTyped?: boolean;\n};\n\ntype SignatureMode = \"draw\" | \"type\";\n\n/** Base stroke width in drawing units (CSS pixels at the time of drawing). */\nconst STROKE_SIZE = 3.2;\n/** Points closer than this to the previous one add no shape, only path length. */\nconst MIN_POINT_DISTANCE = 1.2;\nconst EMPTY_SPACE = { x: 0, y: 0, width: 600, height: 200 };\n\nconst sameValue = (left: SignatureValue | null, right: SignatureValue | null) =>\n left === right ||\n (left !== null &&\n right !== null &&\n left.kind === right.kind &&\n left.svg === right.svg &&\n (left.kind === \"typed\" && right.kind === \"typed\" ? left.name === right.name : true));\n\nconst drawingOf = (value: SignatureValue | null): SignatureDrawing =>\n (value?.kind === \"drawn\" ? svgToDrawing(value.svg) : null) ?? { ...EMPTY_SPACE, strokes: [] };\n\nlet measureContext: CanvasRenderingContext2D | null | undefined;\nconst measureText = (text: string, font: string): number | undefined => {\n measureContext ??= document.createElement(\"canvas\").getContext(\"2d\");\n if (!measureContext) return undefined;\n measureContext.font = font;\n return measureContext.measureText(text).width;\n};\n\n/**\n * A signature field: draw with a finger, pen, or mouse, or type the name. The\n * value is an SVG document plus how it was given; `signatureToPng` turns it\n * into a bitmap. Legal evidence such as timestamps or hashes stays with the app.\n */\nexport function SignatureInput(props: SignatureInputProps): JSX.Element {\n const messages = useUiMessages();\n const text = useSignatureMessages();\n const meta = createFieldMeta(props.id);\n const requiredId = `${meta.controlId}-required`;\n const value = () => resolveMaybeAccessor(props.value) ?? null;\n const error = () => resolveMaybeAccessor(props.error);\n const allowTyped = () => props.allowTyped !== false;\n const editable = () => !props.disabled && !props.readOnly;\n\n const initial = untrack(value);\n const [mode, setMode] = createSignal<SignatureMode>(initial?.kind === \"typed\" ? \"type\" : \"draw\");\n const [drawing, setDrawing] = createSignal<SignatureDrawing>(drawingOf(initial));\n const [typedName, setTypedName] = createSignal(initial?.kind === \"typed\" ? initial.name : \"\");\n const [live, setLive] = createSignal(\"\");\n let svg: SVGSVGElement | undefined;\n let nameInput: HTMLInputElement | undefined;\n let lastReported: SignatureValue | null = initial;\n\n const drawnValue = (): SignatureValue | null => {\n const markup = drawingToSvg(drawing());\n return markup ? { kind: \"drawn\", svg: markup } : null;\n };\n const typedValue = (): SignatureValue | null => {\n const name = signatureName(typedName());\n if (!name) return null;\n const family = nameInput ? getComputedStyle(nameInput).fontFamily : \"cursive\";\n return { kind: \"typed\", name, svg: typedToSvg(name, family, measureText) };\n };\n const report = (next: SignatureValue | null, commit: boolean) => {\n if (sameValue(next, lastReported)) return;\n lastReported = next;\n if (commit) commitFieldValue(props, next);\n else props.onValueChange?.(next);\n };\n\n let stroke: { pointerId: number; pointerType: string; points: SignaturePoint[]; time: number } | null = null;\n /** Drops the stroke in progress without keeping its ink. */\n const abortStroke = () => {\n if (!stroke) return;\n const { pointerId } = stroke;\n stroke = null;\n setLive(\"\");\n if (svg?.hasPointerCapture(pointerId)) svg.releasePointerCapture(pointerId);\n };\n /** After Clear or the last Undo, keyboard focus moves from the now disabled button to the pad instead of the page. */\n const keepFocus = (button: HTMLElement) => {\n if (button.matches(\":focus-visible\")) queueMicrotask(() => svg?.focus());\n };\n\n // A value the parent sets (a stored signature, another record, a reset to null) replaces both inputs, so nothing of an\n // earlier signer stays behind the mode switch. Our own reports echo back unchanged.\n createEffect(\n on(\n value,\n (next) => {\n if (next === lastReported || sameValue(next, lastReported)) return;\n lastReported = next;\n abortStroke();\n setDrawing(drawingOf(next));\n setTypedName(next?.kind === \"typed\" ? next.name : \"\");\n if (next) setMode(next.kind === \"typed\" ? \"type\" : \"draw\");\n },\n { defer: true },\n ),\n );\n // A field that turns disabled or read-only, or leaves drawing, ends the stroke in progress without a value.\n createEffect(() => {\n if (!editable() || mode() !== \"draw\") abortStroke();\n });\n\n const switchMode = (next: SignatureMode) => {\n if (!editable() || next === mode()) return;\n setMode(next);\n report(next === \"draw\" ? drawnValue() : typedValue(), true);\n };\n const undo = (event: MouseEvent & { currentTarget: HTMLButtonElement }) => {\n if (!editable()) return;\n setDrawing((current) => ({ ...current, strokes: current.strokes.slice(0, -1) }));\n if (drawing().strokes.length === 0) keepFocus(event.currentTarget);\n report(drawnValue(), true);\n };\n const clear = (event: MouseEvent & { currentTarget: HTMLButtonElement }) => {\n if (!editable()) return;\n abortStroke();\n // Clear starts over in both modes. Without the Type option, a typed value it removes gives way to drawing.\n setTypedName(\"\");\n setDrawing((current) => ({ ...current, strokes: [] }));\n if (mode() === \"type\" && allowTyped()) {\n nameInput?.focus();\n } else {\n setMode(\"draw\");\n keepFocus(event.currentTarget);\n }\n report(null, true);\n };\n\n const toPoint = (event: PointerEvent): { x: number; y: number } | null => {\n const matrix = svg?.getScreenCTM();\n if (!matrix) return null;\n const point = new DOMPoint(event.clientX, event.clientY).matrixTransform(matrix.inverse());\n return { x: point.x, y: point.y };\n };\n const addPoint = (event: PointerEvent, force = false) => {\n if (!stroke) return;\n const position = toPoint(event);\n if (!position) return;\n const previous = stroke.points[stroke.points.length - 1];\n const distance = previous ? Math.hypot(position.x - previous.x, position.y - previous.y) : 0;\n if (previous && distance < MIN_POINT_DISTANCE && !force) return;\n let pressure: number;\n if (event.pointerType === \"pen\" && event.pressure > 0) {\n pressure = event.pressure;\n } else {\n // Without real pressure, faster movement draws a thinner line, as ink does.\n const elapsed = Math.max(1, event.timeStamp - stroke.time);\n const target = Math.min(1, Math.max(0.3, 1.1 - (distance / elapsed) * 0.3));\n pressure = previous ? previous.pressure * 0.6 + target * 0.4 : 0.6;\n }\n stroke.time = event.timeStamp;\n stroke.points.push({ ...position, pressure });\n setLive(strokeOutline(stroke.points, STROKE_SIZE));\n };\n /**\n * The drawing space after a stroke. It stays as it is while the ink lies inside it. Ink beyond it, drawn after the\n * pad changed its shape (a rotated phone, a resized window, a drawing from another device), grows the space to the\n * whole visible pad: the stored SVG then holds everything the signer saw, and the ink on the pad does not move.\n */\n const spaceAfter = (points: readonly SignaturePoint[]): Omit<SignatureDrawing, \"strokes\"> => {\n const { strokes: _, ...space } = drawing();\n const matrix = svg?.getScreenCTM();\n if (!svg || !matrix) return space;\n const box = svg.getBoundingClientRect();\n const inverse = matrix.inverse();\n const start = new DOMPoint(box.left, box.top).matrixTransform(inverse);\n const end = new DOMPoint(box.right, box.bottom).matrixTransform(inverse);\n // Ink reaches half the widest stroke past its points; what lies outside the pad was never visible and does not count.\n const reach = STROKE_SIZE / 2;\n let left = end.x;\n let top = end.y;\n let right = start.x;\n let bottom = start.y;\n for (const point of points) {\n left = Math.min(left, Math.max(start.x, point.x - reach));\n top = Math.min(top, Math.max(start.y, point.y - reach));\n right = Math.max(right, Math.min(end.x, point.x + reach));\n bottom = Math.max(bottom, Math.min(end.y, point.y + reach));\n }\n if (left >= space.x && top >= space.y && right <= space.x + space.width && bottom <= space.y + space.height) return space;\n const x = Math.min(space.x, start.x);\n const y = Math.min(space.y, start.y);\n return {\n x,\n y,\n width: Math.max(space.x + space.width, end.x) - x,\n height: Math.max(space.y + space.height, end.y) - y,\n };\n };\n const startStroke = (event: PointerEvent) => {\n // Only the primary contact draws: not a mouse's other buttons, a pen's barrel button, or its eraser.\n if (!editable() || !svg || event.button !== 0) return;\n // A pen takes over from a touch that is still down, such as a palm resting on a tablet.\n if (stroke?.pointerType === \"touch\" && event.pointerType === \"pen\") abortStroke();\n if (stroke) return;\n event.preventDefault();\n try {\n // Capture keeps the stroke when the pointer leaves the pad; a pointer that already ended cannot be captured.\n svg.setPointerCapture(event.pointerId);\n } catch {}\n if (drawing().strokes.length === 0) {\n // An empty pad adopts its current size, so one drawing unit is one CSS pixel while drawing.\n const box = svg.getBoundingClientRect();\n setDrawing({ x: 0, y: 0, width: Math.max(1, box.width), height: Math.max(1, box.height), strokes: [] });\n }\n stroke = { pointerId: event.pointerId, pointerType: event.pointerType, points: [], time: event.timeStamp };\n addPoint(event, true);\n };\n const moveStroke = (event: PointerEvent) => {\n if (stroke?.pointerId !== event.pointerId) return;\n event.preventDefault();\n const events = event.getCoalescedEvents?.() ?? [];\n for (const sample of events.length > 0 ? events : [event]) addPoint(sample);\n };\n const endStroke = (event: PointerEvent) => {\n if (!stroke || stroke.pointerId !== event.pointerId) return;\n addPoint(event, false);\n const { points } = stroke;\n const outline = strokeOutline(points, STROKE_SIZE);\n stroke = null;\n setLive(\"\");\n if (!outline) return;\n const space = spaceAfter(points);\n setDrawing((current) => ({ ...space, strokes: [...current.strokes, outline] }));\n report(drawnValue(), true);\n };\n // A cancelled pointer (an OS gesture, palm rejection) or lost capture is not a finished stroke.\n const cancelStroke = (event: PointerEvent) => {\n if (stroke?.pointerId === event.pointerId) abortStroke();\n };\n\n const empty = () => (mode() === \"draw\" ? drawing().strokes.length === 0 && !live() : !signatureName(typedName()));\n const serialized = () => {\n const current = value();\n return current ? JSON.stringify(current) : \"\";\n };\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={error()}\n meta={meta}\n labelFor={false}\n required={props.required}\n disabled={props.disabled}\n >\n <div\n id={meta.controlId}\n class=\"k2b-signature-input\"\n role=\"group\"\n aria-label={props.label ? undefined : props[\"aria-label\"]}\n aria-labelledby={props.label ? meta.labelId : undefined}\n aria-describedby={[props.required ? requiredId : undefined, fieldDescribedBy(meta, props)].filter(Boolean).join(\" \") || undefined}\n aria-invalid={error() ? \"true\" : undefined}\n data-mode={mode()}\n data-empty={empty() ? \"true\" : undefined}\n data-invalid={error() ? \"true\" : undefined}\n data-disabled={props.disabled ? \"true\" : undefined}\n data-readonly={props.readOnly ? \"true\" : undefined}\n >\n {/* A group cannot be marked required, so the required state is part of its description. */}\n <Show when={props.required}>\n <span id={requiredId} hidden>\n {messages().required}\n </span>\n </Show>\n <div class=\"k2b-signature-input__toolbar\">\n <Show when={allowTyped()}>\n <SegmentedControl\n class=\"k2b-signature-input__modes\"\n size=\"sm\"\n ariaLabel={text().method}\n options={[\n { value: \"draw\", label: text().draw, icon: \"ti ti-signature\" },\n { value: \"type\", label: text().type, icon: \"ti ti-keyboard\" },\n ]}\n value={mode}\n onValueChange={switchMode}\n disabled={!editable()}\n />\n </Show>\n <div class=\"k2b-signature-input__actions\">\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n label={text().undo}\n data-hidden={mode() === \"type\" ? \"true\" : undefined}\n disabled={!editable() || mode() !== \"draw\" || drawing().strokes.length === 0}\n onClick={undo}\n >\n <i class=\"ti ti-arrow-back-up\" aria-hidden=\"true\" />\n </IconButton>\n <IconButton size=\"sm\" variant=\"ghost\" label={messages().clear} disabled={!editable() || empty()} onClick={clear}>\n <i class=\"ti ti-eraser\" aria-hidden=\"true\" />\n </IconButton>\n </div>\n </div>\n <div class=\"k2b-signature-input__pad\">\n <span class=\"k2b-signature-input__line\" aria-hidden=\"true\" />\n <Show\n when={mode() === \"draw\"}\n fallback={\n <input\n ref={nameInput}\n class=\"k2b-signature-input__name\"\n type=\"text\"\n value={typedName()}\n placeholder={text().typeName}\n aria-label={text().typeName}\n aria-describedby={fieldDescribedBy(meta, props)}\n aria-invalid={error() ? \"true\" : undefined}\n aria-required={props.required || undefined}\n autocomplete=\"name\"\n autocapitalize=\"words\"\n spellcheck={false}\n disabled={props.disabled}\n readOnly={props.readOnly || !allowTyped()}\n onInput={(event) => {\n setTypedName(event.currentTarget.value);\n report(typedValue(), false);\n }}\n onChange={() => props.onValueCommit?.(typedValue())}\n />\n }\n >\n <span class=\"k2b-signature-input__placeholder\" aria-hidden=\"true\">\n <i class=\"ti ti-writing-sign\" aria-hidden=\"true\" />\n {text().signHere}\n </span>\n <svg\n ref={svg}\n class=\"k2b-signature-input__canvas\"\n role=\"img\"\n tabindex=\"-1\"\n aria-label={drawing().strokes.length > 0 ? text().drawn : text().drawArea}\n viewBox={`${drawing().x} ${drawing().y} ${drawing().width} ${drawing().height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n onPointerDown={startStroke}\n onPointerMove={moveStroke}\n onPointerUp={endStroke}\n onPointerCancel={cancelStroke}\n onLostPointerCapture={(event) => {\n // A child path that held a touch's implicit capture hands it to the pad; only the pad's own loss counts.\n if (event.target === event.currentTarget) cancelStroke(event);\n }}\n >\n <g fill=\"currentColor\">\n <For each={drawing().strokes}>{(outline) => <path d={outline} />}</For>\n <Show when={live()}>\n <path d={live()} />\n </Show>\n </g>\n </svg>\n </Show>\n </div>\n </div>\n <Show when={props.name}>{(name) => <input type=\"hidden\" name={name()} value={serialized()} />}</Show>\n </Field>\n );\n}\n\nexport default SignatureInput;\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,551 @@
1
+ import { ssrStyleProperty as _$ssrStyleProperty } from "solid-js/web";
2
+ import { memo as _$memo } from "solid-js/web";
3
+ import { createComponent as _$createComponent } from "solid-js/web";
4
+ import { ssr as _$ssr } from "solid-js/web";
5
+ import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
6
+ import { escape as _$escape } from "solid-js/web";
7
+ var _tmpl$ = ["<button", " type=\"button\" role=\"option\" tabindex=\"-1\" class=\"k2b-emoji-picker__option\"", ">", "</button>"],
8
+ _tmpl$2 = ["<button type=\"button\" class=\"k2b-emoji-picker__tone-button\"", " aria-haspopup=\"true\"", " title=\"", "\"", "><span aria-hidden=\"true\">", "</span></button>"],
9
+ _tmpl$3 = ["<div class=\"k2b-emoji-picker__tones\" role=\"radiogroup\"", ">", "</div>"],
10
+ _tmpl$4 = ["<div role=\"group\"", ">", "</div>"],
11
+ _tmpl$5 = ["<div id=\"", "\" role=\"listbox\" class=\"k2b-emoji-picker__list\"", ">", "<div class=\"k2b-emoji-picker__all\"", ">", "</div></div>"],
12
+ _tmpl$6 = ["<div class=\"k2b-emoji-picker__state\" role=\"status\">", "</div>"],
13
+ _tmpl$7 = ["<div class=\"", "\" role=\"group\"", "><div class=\"k2b-emoji-picker__header\"><div class=\"k2b-emoji-picker__search\"", "><i class=\"ti ti-search\" aria-hidden=\"true\"></i><input type=\"search\" role=\"combobox\"", " aria-expanded=\"true\" aria-controls=\"", "\" aria-autocomplete=\"list\"", " autocomplete=\"off\" spellcheck=\"false\" enterkeyhint=\"done\"", "></div>", "</div><div class=\"k2b-emoji-picker__groups\" role=\"toolbar\"", ">", "</div><div class=\"k2b-emoji-picker__scroll\">", "</div><div class=\"k2b-emoji-picker__preview\" aria-hidden=\"true\">", "</div></div>"],
14
+ _tmpl$8 = ["<button type=\"button\" role=\"radio\"", "><span aria-hidden=\"true\">", "</span></button>"],
15
+ _tmpl$9 = ["<button type=\"button\"", "", "><i", " aria-hidden=\"true\"></i></button>"],
16
+ _tmpl$0 = ["<span>", "</span>"],
17
+ _tmpl$1 = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
18
+ _tmpl$10 = ["<span class=\"k2b-sr-only\">", "</span>"],
19
+ _tmpl$11 = ["<div class=\"k2b-emoji-picker__grid k2b-emoji-picker__block\" style=\"", "\">", "</div>"],
20
+ _tmpl$12 = ["<div role=\"group\" class=\"k2b-emoji-picker__section\"", " aria-labelledby=\"", "\" style=\"", "\"><div id=\"", "\" class=\"k2b-emoji-picker__heading\" data-emoji-heading>", "</div><div class=\"k2b-emoji-picker__grid\">", "</div></div>"],
21
+ _tmpl$13 = ["<span class=\"k2b-emoji-picker__preview-emoji\">", "</span>"],
22
+ _tmpl$14 = ["<span class=\"k2b-emoji-picker__preview-name\">", "</span>"],
23
+ _tmpl$15 = ["<span class=\"k2b-emoji-picker__preview-code\">:", ":</span>"],
24
+ _tmpl$16 = ["<div popover=\"auto\" class=\"k2b-emoji-picker-popover\">", "</div>"];
25
+ import { createEffect, createMemo, createSelector, createSignal, createUniqueId, For, on, onCleanup, onMount, Show, untrack } from "solid-js";
26
+ import { Button } from "../../actions/Button";
27
+ import { returnFocus, ringOnReturn } from "../../internal/focus-return";
28
+ import { useLocale } from "../../intl/locale";
29
+ import { useUiMessages } from "../../intl/messages";
30
+ import { EMOJI_GROUPS, EMOJI_SKIN_TONES, emojiInTone, emojiName, findEmoji, loadEmojiIndex, RECENT_EMOJI_LIMIT, searchEmoji } from "./emoji-index";
31
+ import { useEmojiMessages } from "./emoji-messages";
32
+
33
+ /** Emoji per row. Keyboard navigation moves by rows of this length. */
34
+ const COLUMNS = 8;
35
+ /** Search results render in blocks of this many rows, so the browser lays out only the blocks in view. */
36
+ const RESULT_BLOCK = 8 * COLUMNS;
37
+ const TONE_SAMPLE = "✋";
38
+ const TONE_SAMPLE_LIGHT = "✋\u{1F3FB}";
39
+ const groupTitle = (messages, key) => {
40
+ switch (key) {
41
+ case "recent":
42
+ return messages.emojiRecent;
43
+ case "smileys":
44
+ return messages.emojiSmileys;
45
+ case "people":
46
+ return messages.emojiPeople;
47
+ case "animals":
48
+ return messages.emojiAnimals;
49
+ case "food":
50
+ return messages.emojiFood;
51
+ case "travel":
52
+ return messages.emojiTravel;
53
+ case "activities":
54
+ return messages.emojiActivities;
55
+ case "objects":
56
+ return messages.emojiObjects;
57
+ case "symbols":
58
+ return messages.emojiSymbols;
59
+ case "flags":
60
+ return messages.emojiFlags;
61
+ }
62
+ };
63
+ const toneLabel = (messages, tone) => [messages.emojiToneDefault, messages.emojiToneLight, messages.emojiToneMediumLight, messages.emojiToneMedium, messages.emojiToneMediumDark, messages.emojiToneDark][tone];
64
+ const toneSample = tone => tone === 0 ? TONE_SAMPLE : TONE_SAMPLE_LIGHT.replace("\u{1F3FB}", String.fromCodePoint(0x1f3fb + tone - 1));
65
+
66
+ /** Where `index` moves for an arrow key in rows of `COLUMNS`, where every section starts a new row. */
67
+ export const nextEmojiIndex = (sizes, index, key) => {
68
+ const total = sizes.reduce((sum, size) => sum + size, 0);
69
+ if (total === 0) return -1;
70
+ if (index < 0) return 0;
71
+ if (key === "ArrowLeft") return Math.max(0, index - 1);
72
+ if (key === "ArrowRight") return Math.min(total - 1, index + 1);
73
+ const starts = [];
74
+ sizes.reduce((start, size) => (starts.push(start), start + size), 0);
75
+ const section = starts.findLastIndex((start, position) => start <= index && sizes[position] > 0);
76
+ const offset = index - starts[section];
77
+ const column = offset % COLUMNS;
78
+ const size = sizes[section];
79
+ if (key === "ArrowUp") {
80
+ if (offset >= COLUMNS) return index - COLUMNS;
81
+ for (let previous = section - 1; previous >= 0; previous--) {
82
+ if (!sizes[previous]) continue;
83
+ const lastRow = Math.floor((sizes[previous] - 1) / COLUMNS) * COLUMNS;
84
+ return starts[previous] + Math.min(lastRow + column, sizes[previous] - 1);
85
+ }
86
+ return index;
87
+ }
88
+ if (offset + COLUMNS < size) return index + COLUMNS;
89
+ // A shorter last row: the last emoji of the section is below.
90
+ if (Math.floor(offset / COLUMNS) < Math.floor((size - 1) / COLUMNS)) return starts[section] + size - 1;
91
+ for (let next = section + 1; next < sizes.length; next++) {
92
+ if (sizes[next]) return starts[next] + Math.min(column, sizes[next] - 1);
93
+ }
94
+ return index;
95
+ };
96
+
97
+ /**
98
+ * A search field, the groups, and a grid of emoji. Search finds English and German names, keywords and GitHub
99
+ * shortcodes. The focus stays in the search field: arrow keys move through the grid, Enter picks.
100
+ */
101
+ function EmojiPickerPanel(props) {
102
+ const messages = useUiMessages();
103
+ const text = useEmojiMessages();
104
+ const locale = useLocale();
105
+ const id = `k2b-emoji-${createUniqueId().replace(/[^A-Za-z0-9_-]/g, "-")}`;
106
+ const [index, setIndex] = createSignal();
107
+ const [failed, setFailed] = createSignal(false);
108
+ const [query, setQuery] = createSignal("");
109
+ const [active, setActive] = createSignal(0);
110
+ const isActive = createSelector(active);
111
+ const [tonesOpen, setTonesOpen] = createSignal(false);
112
+ const [currentGroup, setCurrentGroup] = createSignal();
113
+ /** Up or down moved into the grid; from then on, left and right move there too, until the search changes. */
114
+ const [gridEntered, setGridEntered] = createSignal(false);
115
+ let searchRef;
116
+ let scrollRef;
117
+ let toneButtonRef;
118
+ let tonesRef;
119
+ let groupsRef;
120
+ /** Keyboard moves scroll the active emoji into view; the pointer does not. */
121
+ let revealActive = false;
122
+ const load = () => {
123
+ setFailed(false);
124
+ loadEmojiIndex().then(setIndex, () => setFailed(true));
125
+ };
126
+ onMount(load);
127
+ const tone = () => props.skinTone ?? 0;
128
+ const searching = () => query().trim().length > 0;
129
+ // The groups render once and stay while a search shows its results above them, so clearing a search is instant.
130
+ const groups = createMemo(() => {
131
+ const data = index();
132
+ if (!data) return [];
133
+ const recent = {
134
+ key: "recent",
135
+ items: (props.recent ?? []).slice(0, RECENT_EMOJI_LIMIT).map(emoji => ({
136
+ emoji,
137
+ entry: findEmoji(data, emoji)
138
+ }))
139
+ };
140
+ const all = EMOJI_GROUPS.map(group => ({
141
+ key: group.key,
142
+ items: data.entries.filter(entry => entry.group === group.id).map(entry => ({
143
+ emoji: emojiInTone(entry, tone()),
144
+ entry
145
+ }))
146
+ }));
147
+ return recent.items.length ? [recent, ...all] : all;
148
+ });
149
+ const groupStarts = createMemo(() => {
150
+ const starts = [];
151
+ groups().reduce((start, section) => (starts.push(start), start + section.items.length), 0);
152
+ return starts;
153
+ });
154
+ const results = createMemo(() => {
155
+ const data = index();
156
+ if (!data || !searching()) return [];
157
+ return searchEmoji(data, query()).map(entry => ({
158
+ emoji: emojiInTone(entry, tone()),
159
+ entry
160
+ }));
161
+ });
162
+ const resultBlocks = createMemo(() => Array.from({
163
+ length: Math.ceil(results().length / RESULT_BLOCK)
164
+ }, (_, block) => results().slice(block * RESULT_BLOCK, (block + 1) * RESULT_BLOCK)));
165
+ /** The sizes of the visible sections, for the arrow keys. */
166
+ const sizes = () => searching() ? [results().length] : groups().map(section => section.items.length);
167
+ const items = createMemo(() => searching() ? results() : groups().flatMap(section => section.items));
168
+ const activeItem = () => items()[active()];
169
+ const optionId = (position, list = searching() ? "r" : "g") => `${id}-${list}${position}`;
170
+ const nameOf = item => item.entry ? emojiName(item.entry, locale()) : item.emoji;
171
+ /** Where an element sits in the list, from its top. */
172
+ const offsetOf = element => element.getBoundingClientRect().top - scrollRef.getBoundingClientRect().top + scrollRef.scrollTop;
173
+
174
+ // A new search starts at its best match; the groups at their top.
175
+ createEffect(on([query, index], () => {
176
+ setActive(0);
177
+ if (scrollRef) scrollRef.scrollTop = 0;
178
+ }));
179
+ createEffect(() => {
180
+ const position = active();
181
+ if (!revealActive) return;
182
+ revealActive = false;
183
+ const option = document.getElementById(optionId(position));
184
+ if (!option || !scrollRef) return;
185
+ // Sticky group titles cover the top of the list; an emoji in a first row scrolls with its title.
186
+ const covered = option.closest("[role='group']")?.querySelector("[data-emoji-heading]")?.offsetHeight ?? 0;
187
+ const top = offsetOf(option) - covered;
188
+ const bottom = offsetOf(option) + option.offsetHeight;
189
+ if (top < scrollRef.scrollTop) scrollRef.scrollTop = top;else if (bottom > scrollRef.scrollTop + scrollRef.clientHeight) scrollRef.scrollTop = bottom - scrollRef.clientHeight;
190
+ });
191
+
192
+ /** The group whose title sits at the top of the list, for the group bar. */
193
+ const syncGroup = () => {
194
+ if (!scrollRef || searching()) return;
195
+ const top = scrollRef.scrollTop + 1;
196
+ let current;
197
+ for (const group of scrollRef.querySelectorAll("[data-emoji-group]")) {
198
+ if (offsetOf(group) <= top) current = group.dataset.emojiGroup;
199
+ }
200
+ setCurrentGroup(current ?? groups()[0]?.key);
201
+ };
202
+ createEffect(on([groups, searching], () => queueMicrotask(syncGroup)));
203
+ onMount(() => {
204
+ if (props.autofocus) queueMicrotask(() => searchRef?.focus({
205
+ preventScroll: true
206
+ }));
207
+ });
208
+ const pick = item => {
209
+ if (item) props.onPick(item.emoji);
210
+ };
211
+ const showGroup = key => {
212
+ setQuery("");
213
+ queueMicrotask(() => {
214
+ const group = scrollRef?.querySelector(`[data-emoji-group="${key}"]`);
215
+ if (!group || !scrollRef) return;
216
+ scrollRef.scrollTop = offsetOf(group);
217
+ const position = groups().findIndex(section => section.key === key);
218
+ if (position >= 0) setActive(groupStarts()[position]);
219
+ syncGroup();
220
+ });
221
+ };
222
+ const onSearchKeyDown = event => {
223
+ // WebKit ends the composition before the keydown of the Enter that confirms it; that keydown keeps keyCode 229.
224
+ if (event.isComposing || event.keyCode === 229) return;
225
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
226
+ // Left and right edit a search until up or down enters the grid.
227
+ if ((event.key === "ArrowLeft" || event.key === "ArrowRight") && query() && !gridEntered()) return;
228
+ event.preventDefault();
229
+ revealActive = true;
230
+ setGridEntered(true);
231
+ setActive(nextEmojiIndex(sizes(), active(), event.key));
232
+ return;
233
+ }
234
+ if (event.key === "Enter") {
235
+ event.preventDefault();
236
+ pick(activeItem());
237
+ return;
238
+ }
239
+ if (event.key === "Escape") {
240
+ if (query()) {
241
+ event.preventDefault();
242
+ event.stopPropagation();
243
+ setQuery("");
244
+ return;
245
+ }
246
+ if (props.onClose) {
247
+ event.preventDefault();
248
+ event.stopPropagation();
249
+ props.onClose();
250
+ }
251
+ }
252
+ };
253
+ const openTones = () => {
254
+ setTonesOpen(true);
255
+ queueMicrotask(() => tonesRef?.querySelector("[aria-checked='true']")?.focus());
256
+ };
257
+ const closeTones = focusButton => {
258
+ setTonesOpen(false);
259
+ if (focusButton) queueMicrotask(() => toneButtonRef?.focus());
260
+ };
261
+ /** As in any radio group, the arrow keys choose; Enter, Space, a click or Escape close the choice. */
262
+ const onTonesKeyDown = event => {
263
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
264
+ event.preventDefault();
265
+ const step = event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 1;
266
+ const next = EMOJI_SKIN_TONES[(tone() + step + EMOJI_SKIN_TONES.length) % EMOJI_SKIN_TONES.length];
267
+ props.onSkinToneChange?.(next);
268
+ queueMicrotask(() => tonesRef?.querySelector(`[data-tone="${next}"]`)?.focus());
269
+ } else if (event.key === "Escape") {
270
+ event.preventDefault();
271
+ event.stopPropagation();
272
+ closeTones(true);
273
+ }
274
+ };
275
+
276
+ /** The group bar is one tab stop; arrow keys move along it. */
277
+ const onGroupsKeyDown = event => {
278
+ const buttons = [...(groupsRef?.querySelectorAll("button") ?? [])];
279
+ const current = buttons.indexOf(document.activeElement);
280
+ const target = event.key === "ArrowLeft" ? current - 1 : event.key === "ArrowRight" ? current + 1 : event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : undefined;
281
+ if (target === undefined) return;
282
+ event.preventDefault();
283
+ buttons[(target + buttons.length) % buttons.length]?.focus();
284
+ };
285
+ const groupKeys = createMemo(() => [...(props.recent?.length ? ["recent"] : []), ...EMOJI_GROUPS.map(group => group.key)]);
286
+ const groupIcon = key => key === "recent" ? "ti ti-clock" : EMOJI_GROUPS.find(group => group.key === key)?.icon ?? "ti ti-mood-smile";
287
+ const tabStop = () => currentGroup() ?? groupKeys()[0];
288
+
289
+ /** The grid shares its pointer listeners; the target's position says which emoji it is. */
290
+ const itemAt = event => {
291
+ const option = event.target?.closest("[data-emoji-index]");
292
+ return option ? Number(option.dataset.emojiIndex) : undefined;
293
+ };
294
+
295
+ // A button, so that a tap is a click on every engine; the focus stays in the search field.
296
+ const option = (item, list, position) => _$ssr(_tmpl$, _$ssrAttribute("id", _$escape(optionId(position(), list), true), false), _$ssrAttribute("aria-selected", _$escape(isActive(position()), true), false) + _$ssrAttribute("aria-label", _$escape(nameOf(item), true), false) + _$ssrAttribute("data-emoji-index", _$escape(position(), true), false) + _$ssrAttribute("data-active", isActive(position()) ? "true" : _$escape(undefined, true), false), _$escape(item.emoji));
297
+ return _$ssr(_tmpl$7, `k2b-emoji-picker ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("aria-label", _$escape(props.label ?? text().emojiPicker, true), false), _$ssrAttribute("inert", tonesOpen(), true), _$ssrAttribute("aria-label", _$escape(text().emojiSearch, true), false), `${_$escape(id, true)}-list`, _$ssrAttribute("aria-activedescendant", activeItem() ? _$escape(optionId(active()), true) : _$escape(undefined, true), false) + _$ssrAttribute("placeholder", _$escape(text().emojiSearch, true), false), _$ssrAttribute("value", _$escape(query(), true), false), _$escape(_$createComponent(Show, {
298
+ get when() {
299
+ return props.onSkinToneChange;
300
+ },
301
+ get children() {
302
+ return [_$ssr(_tmpl$2, _$ssrAttribute("aria-label", _$escape(text().emojiSkinTone, true), false), _$ssrAttribute("aria-expanded", _$escape(tonesOpen(), true), false), `${_$escape(text().emojiSkinTone, true)}: ${_$escape(toneLabel(text(), tone()), true)}`, _$ssrAttribute("inert", tonesOpen(), true), _$escape(toneSample(tone()))), _$createComponent(Show, {
303
+ get when() {
304
+ return tonesOpen();
305
+ },
306
+ get children() {
307
+ return _$ssr(_tmpl$3, _$ssrAttribute("aria-label", _$escape(text().emojiSkinTone, true), false), _$escape(_$createComponent(For, {
308
+ each: EMOJI_SKIN_TONES,
309
+ children: value => _$ssr(_tmpl$8, _$ssrAttribute("aria-checked", _$escape(value, true) === _$escape(tone(), true), false) + _$ssrAttribute("aria-label", _$escape(toneLabel(text(), value), true), false) + _$ssrAttribute("title", _$escape(toneLabel(text(), value), true), false) + _$ssrAttribute("tabindex", value === tone() ? 0 : -1, false) + _$ssrAttribute("data-tone", _$escape(value, true), false), _$escape(toneSample(value)))
310
+ })));
311
+ }
312
+ })];
313
+ }
314
+ })), _$ssrAttribute("aria-label", _$escape(text().emojiGroups, true), false), _$escape(_$createComponent(For, {
315
+ get each() {
316
+ return groupKeys();
317
+ },
318
+ children: key => _$ssr(_tmpl$9, _$ssrAttribute("aria-label", _$escape(groupTitle(text(), key), true), false) + _$ssrAttribute("title", _$escape(groupTitle(text(), key), true), false) + _$ssrAttribute("aria-current", !searching() && currentGroup() === key ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("tabindex", key === tabStop() ? 0 : -1, false), _$ssrAttribute("disabled", !index(), true), _$ssrAttribute("class", _$escape(groupIcon(key), true), false))
319
+ })), _$escape(_$createComponent(Show, {
320
+ get when() {
321
+ return index();
322
+ },
323
+ get fallback() {
324
+ return _$ssr(_tmpl$6, _$escape(_$createComponent(Show, {
325
+ get when() {
326
+ return failed();
327
+ },
328
+ get fallback() {
329
+ return [_$ssr(_tmpl$1), _$ssr(_tmpl$10, _$escape(messages().loading))];
330
+ },
331
+ get children() {
332
+ return [_$ssr(_tmpl$0, _$escape(text().emojiLoadFailed)), _$createComponent(Button, {
333
+ size: "sm",
334
+ variant: "subtle",
335
+ onClick: load,
336
+ get children() {
337
+ return messages().retry;
338
+ }
339
+ })];
340
+ }
341
+ })));
342
+ },
343
+ get children() {
344
+ return [_$ssr(_tmpl$5, `${_$escape(id, true)}-list`, _$ssrAttribute("aria-label", _$escape(props.label ?? text().emojiPicker, true), false), _$escape(_$createComponent(Show, {
345
+ get when() {
346
+ return searching();
347
+ },
348
+ get children() {
349
+ return _$ssr(_tmpl$4, _$ssrAttribute("aria-label", _$escape(messages().searchResults, true), false), _$escape(_$createComponent(For, {
350
+ get each() {
351
+ return resultBlocks();
352
+ },
353
+ children: (block, blockIndex) => _$ssr(_tmpl$11, _$ssrStyleProperty("--k2b-emoji-rows:", _$escape(String(Math.ceil(block.length / COLUMNS)), true)), _$escape(_$createComponent(For, {
354
+ each: block,
355
+ children: (item, itemIndex) => option(item, "r", () => blockIndex() * RESULT_BLOCK + itemIndex())
356
+ })))
357
+ })));
358
+ }
359
+ })), _$ssrAttribute("hidden", searching(), true), _$escape(_$createComponent(For, {
360
+ get each() {
361
+ return groups();
362
+ },
363
+ children: (section, sectionIndex) => _$ssr(_tmpl$12, _$ssrAttribute("data-emoji-group", _$escape(section.key, true), false), `${_$escape(id, true)}-${_$escape(section.key, true)}`, _$ssrStyleProperty("--k2b-emoji-rows:", _$escape(String(Math.ceil(section.items.length / COLUMNS)), true)), `${_$escape(id, true)}-${_$escape(section.key, true)}`, _$escape(groupTitle(text(), section.key)), _$escape(_$createComponent(For, {
364
+ get each() {
365
+ return section.items;
366
+ },
367
+ children: (item, itemIndex) => option(item, "g", () => groupStarts()[sectionIndex()] + itemIndex())
368
+ })))
369
+ }))), _$createComponent(Show, {
370
+ get when() {
371
+ return _$memo(() => !!searching())() && results().length === 0;
372
+ },
373
+ get children() {
374
+ return _$ssr(_tmpl$6, _$escape(text().emojiNoResults));
375
+ }
376
+ })];
377
+ }
378
+ })), _$escape(_$createComponent(Show, {
379
+ get when() {
380
+ return activeItem();
381
+ },
382
+ children: item => [_$ssr(_tmpl$13, _$escape(item().emoji)), _$ssr(_tmpl$14, _$escape(nameOf(item()))), _$createComponent(Show, {
383
+ get when() {
384
+ return item().entry?.shortcodes[0];
385
+ },
386
+ children: code => _$ssr(_tmpl$15, _$escape(code()))
387
+ })]
388
+ })));
389
+ }
390
+ const touchOnlyQuery = "(any-pointer: coarse) and (not (any-pointer: fine))";
391
+ const GAP = 6;
392
+ const MARGIN = 8;
393
+
394
+ /**
395
+ * The picker next to a control, such as the composer's emoji button or a message's "Add reaction". It opens above the
396
+ * control where there is room and below it otherwise, closes after a pick, and returns the focus to where it was.
397
+ */
398
+ function EmojiPickerPopover(props) {
399
+ let surface;
400
+ /** Where the focus was when the picker opened, and whether a keyboard opened it. */
401
+ let previous = null;
402
+ let ring = false;
403
+ /** An anchor pressed while the picker was open closes it; the same click must not open it again. */
404
+ let pressedAnchor;
405
+ const [shown, setShown] = createSignal();
406
+ const place = () => {
407
+ const anchor = shown();
408
+ if (!surface || !anchor) return;
409
+ // An anchor that left the page, such as a row a virtual list recycled, takes the picker with it.
410
+ if (!anchor.isConnected) {
411
+ hide();
412
+ props.onClose();
413
+ return;
414
+ }
415
+ // The visible part of the page: a phone's keyboard shrinks only the visual viewport.
416
+ const viewport = window.visualViewport;
417
+ const view = {
418
+ top: (viewport?.offsetTop ?? 0) + MARGIN,
419
+ bottom: (viewport ? viewport.offsetTop + viewport.height : window.innerHeight) - MARGIN,
420
+ left: (viewport?.offsetLeft ?? 0) + MARGIN,
421
+ right: (viewport ? viewport.offsetLeft + viewport.width : window.innerWidth) - MARGIN
422
+ };
423
+ surface.style.setProperty("--k2b-emoji-popover-height", `${Math.round(view.bottom - view.top)}px`);
424
+ const box = anchor.getBoundingClientRect();
425
+ const size = surface.getBoundingClientRect();
426
+ const above = box.top - GAP - size.height;
427
+ const below = box.bottom + GAP;
428
+ const top = above >= view.top || below + size.height > view.bottom ? above : below;
429
+ // Aligned with the anchor's end, like a menu at the end of a composer.
430
+ const left = box.right - size.width;
431
+ surface.style.left = `${Math.round(Math.max(view.left, Math.min(left, view.right - size.width)))}px`;
432
+ surface.style.top = `${Math.round(Math.max(view.top, Math.min(top, view.bottom - size.height)))}px`;
433
+ };
434
+ const restoreFocus = () => {
435
+ // A pick that moved the focus on purpose, such as into a composer, keeps it there.
436
+ const focused = document.activeElement;
437
+ if (focused && focused !== document.body && !surface?.contains(focused)) return;
438
+ if (previous?.isConnected) returnFocus(previous, ring);
439
+ };
440
+ const hide = () => {
441
+ restoreFocus();
442
+ if (surface?.matches(":popover-open")) surface.hidePopover();
443
+ setShown(undefined);
444
+ };
445
+ const pressed = event => {
446
+ const anchor = shown();
447
+ pressedAnchor = anchor && event.target instanceof Node && anchor.contains(event.target) ? anchor : undefined;
448
+ };
449
+
450
+ /** A key press on the anchor dismisses nothing on its way, unlike a pointer: its click closes the open picker. */
451
+ const clicked = event => {
452
+ const anchor = shown();
453
+ if (!anchor || !(event.target instanceof Node) || !anchor.contains(event.target)) return;
454
+ pressedAnchor = anchor;
455
+ hide();
456
+ props.onClose();
457
+ };
458
+ createEffect(() => {
459
+ const anchor = props.anchor ?? undefined;
460
+ untrack(() => {
461
+ if (!anchor) {
462
+ if (shown()) hide();
463
+ return;
464
+ }
465
+ if (anchor === pressedAnchor) {
466
+ // The press that closed the picker also asked to open it: it stays closed.
467
+ pressedAnchor = undefined;
468
+ props.onClose();
469
+ return;
470
+ }
471
+ if (shown() === anchor || !surface) return;
472
+ // Moving to another anchor keeps the picker open and the focus to return to.
473
+ if (!shown()) {
474
+ previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
475
+ ring = ringOnReturn(previous ?? anchor);
476
+ }
477
+ setShown(anchor);
478
+ if (!surface.matches(":popover-open")) surface.showPopover();
479
+ place();
480
+ });
481
+ });
482
+
483
+ /** The page scrolled under the anchor; the picker's own list scrolling moves nothing. */
484
+ const scrolled = event => {
485
+ if (!(event.target instanceof Node && surface?.contains(event.target))) place();
486
+ };
487
+ onMount(() => {
488
+ window.addEventListener("resize", place);
489
+ window.addEventListener("scroll", scrolled, true);
490
+ window.visualViewport?.addEventListener("resize", place);
491
+ window.visualViewport?.addEventListener("scroll", place);
492
+ document.addEventListener("pointerdown", pressed, true);
493
+ document.addEventListener("click", clicked, true);
494
+ // The picker renders into the open surface after it is shown; its size places it.
495
+ const observer = typeof ResizeObserver === "function" && surface ? new ResizeObserver(place) : undefined;
496
+ if (surface) observer?.observe(surface);
497
+ onCleanup(() => {
498
+ observer?.disconnect();
499
+ window.removeEventListener("resize", place);
500
+ window.removeEventListener("scroll", scrolled, true);
501
+ window.visualViewport?.removeEventListener("resize", place);
502
+ window.visualViewport?.removeEventListener("scroll", place);
503
+ document.removeEventListener("pointerdown", pressed, true);
504
+ document.removeEventListener("click", clicked, true);
505
+ if (surface?.matches(":popover-open")) hide();
506
+ });
507
+ });
508
+ const touchOnly = () => typeof matchMedia === "function" && matchMedia(touchOnlyQuery).matches;
509
+ return _$ssr(_tmpl$16, _$escape(_$createComponent(Show, {
510
+ get when() {
511
+ return shown();
512
+ },
513
+ get children() {
514
+ return _$createComponent(EmojiPickerPanel, {
515
+ onPick: emoji => {
516
+ props.onPick(emoji);
517
+ hide();
518
+ props.onClose();
519
+ },
520
+ get recent() {
521
+ return props.recent;
522
+ },
523
+ get skinTone() {
524
+ return props.skinTone;
525
+ },
526
+ get onSkinToneChange() {
527
+ return props.onSkinToneChange;
528
+ },
529
+ get label() {
530
+ return props.label;
531
+ },
532
+ get ["class"]() {
533
+ return props.class;
534
+ },
535
+ get autofocus() {
536
+ return !touchOnly();
537
+ },
538
+ onClose: () => {
539
+ hide();
540
+ props.onClose();
541
+ }
542
+ });
543
+ }
544
+ })));
545
+ }
546
+
547
+ /** The emoji picker. `EmojiPicker.Popover` opens it next to a control. */
548
+ export const EmojiPicker = Object.assign(EmojiPickerPanel, {
549
+ Popover: EmojiPickerPopover
550
+ });
551
+ //# sourceMappingURL=EmojiPicker.js.map