@openpen/module-api 1.1.1 → 1.3.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 (97) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js +246 -0
  3. package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js.map +1 -0
  4. package/dist/_chunks/inject-keys-CRXCpN-M.js +196 -0
  5. package/dist/_chunks/inject-keys-CRXCpN-M.js.map +1 -0
  6. package/dist/_chunks/module-context-registry-Cn1IWYu9.js +16 -0
  7. package/dist/_chunks/module-context-registry-Cn1IWYu9.js.map +1 -0
  8. package/dist/_chunks/passthrough-DKU7JYuQ.js +6 -0
  9. package/dist/_chunks/passthrough-DKU7JYuQ.js.map +1 -0
  10. package/dist/cursors.d.ts +83 -0
  11. package/dist/define-module.d.ts +17 -0
  12. package/dist/host/color-utils.d.ts +11 -0
  13. package/{src/host/event-bus.ts → dist/host/event-bus.d.ts} +7 -5
  14. package/dist/host/host-commands.d.ts +1 -0
  15. package/dist/host/index.d.ts +24 -0
  16. package/dist/host/index.js +51 -0
  17. package/dist/host/index.js.map +1 -0
  18. package/{src/host/module-context-registry.ts → dist/host/module-context-registry.d.ts} +5 -19
  19. package/dist/host/passthrough.d.ts +2 -0
  20. package/dist/host/popup-anchor.d.ts +3 -0
  21. package/dist/host/registry.d.ts +39 -0
  22. package/dist/host/registry.js +37 -0
  23. package/dist/host/registry.js.map +1 -0
  24. package/dist/host/slot-entries.d.ts +2 -0
  25. package/dist/host/stroke-store.d.ts +6 -0
  26. package/dist/host/stroke-style.d.ts +3 -0
  27. package/dist/host/types.d.ts +176 -0
  28. package/dist/index.d.ts +28 -0
  29. package/dist/index.js +349 -0
  30. package/dist/index.js.map +1 -0
  31. package/{src/inject-keys.ts → dist/inject-keys.d.ts} +37 -66
  32. package/{src/locale.ts → dist/locale.d.ts} +3 -22
  33. package/dist/slots.d.ts +57 -0
  34. package/dist/types/contributions.d.ts +288 -0
  35. package/dist/types/control-bar-layout.d.ts +227 -0
  36. package/dist/types/module.d.ts +208 -0
  37. package/{src/types/notification.ts → dist/types/notification.d.ts} +12 -14
  38. package/dist/types/tool.d.ts +77 -0
  39. package/dist/uikit/components/AppBanner.vue.d.ts +34 -0
  40. package/dist/uikit/components/AppButton.vue.d.ts +30 -0
  41. package/dist/uikit/components/AppButtonDropdown.vue.d.ts +52 -0
  42. package/dist/uikit/components/AppDialog.vue.d.ts +53 -0
  43. package/dist/uikit/components/AppPopover.vue.d.ts +43 -0
  44. package/dist/uikit/components/AppSegmented.vue.d.ts +19 -0
  45. package/dist/uikit/components/AppSelect.vue.d.ts +26 -0
  46. package/dist/uikit/components/AppSlider.vue.d.ts +43 -0
  47. package/dist/uikit/components/AppTabs.vue.d.ts +35 -0
  48. package/dist/uikit/components/AppToggle.vue.d.ts +15 -0
  49. package/dist/uikit/components/AppTooltip.vue.d.ts +27 -0
  50. package/dist/uikit/components/DialogHost.vue.d.ts +3 -0
  51. package/{src/uikit/composables/dialog-internal-keys.ts → dist/uikit/composables/dialog-internal-keys.d.ts} +3 -5
  52. package/dist/uikit/composables/useDialog.d.ts +399 -0
  53. package/dist/uikit/composables/useDialogPluginComponent.d.ts +9 -0
  54. package/dist/uikit/index.d.ts +42 -0
  55. package/dist/uikit/index.js +898 -0
  56. package/dist/uikit/index.js.map +1 -0
  57. package/{src/uikit/internal.ts → dist/uikit/internal.d.ts} +1 -1
  58. package/dist/uikit/internal.js +157 -0
  59. package/dist/uikit/internal.js.map +1 -0
  60. package/dist/uikit/module-api.css +851 -0
  61. package/dist/uikit/primitives.d.ts +42 -0
  62. package/{src/use-module-context.ts → dist/use-module-context.d.ts} +2 -15
  63. package/{src/validation.ts → dist/validation.d.ts} +2 -6
  64. package/package.json +28 -14
  65. package/src/cursors.ts +0 -369
  66. package/src/define-module.ts +0 -55
  67. package/src/host/color-utils.ts +0 -18
  68. package/src/host/host-commands.ts +0 -9
  69. package/src/host/index.ts +0 -53
  70. package/src/host/passthrough.ts +0 -10
  71. package/src/host/popup-anchor.ts +0 -14
  72. package/src/host/slot-entries.ts +0 -9
  73. package/src/host/stroke-store.ts +0 -20
  74. package/src/host/stroke-style.ts +0 -15
  75. package/src/index.ts +0 -131
  76. package/src/slots.ts +0 -135
  77. package/src/types/contributions.ts +0 -333
  78. package/src/types/control-bar-layout.ts +0 -174
  79. package/src/types/module.ts +0 -225
  80. package/src/types/tool.ts +0 -82
  81. package/src/uikit/components/.gitkeep +0 -0
  82. package/src/uikit/components/AppBanner.vue +0 -131
  83. package/src/uikit/components/AppButton.vue +0 -135
  84. package/src/uikit/components/AppDialog.vue +0 -393
  85. package/src/uikit/components/AppPopover.vue +0 -268
  86. package/src/uikit/components/AppSegmented.vue +0 -128
  87. package/src/uikit/components/AppSelect.vue +0 -235
  88. package/src/uikit/components/AppSlider.vue +0 -204
  89. package/src/uikit/components/AppTabs.vue +0 -163
  90. package/src/uikit/components/AppToggle.vue +0 -96
  91. package/src/uikit/components/AppTooltip.vue +0 -118
  92. package/src/uikit/components/DialogHost.vue +0 -272
  93. package/src/uikit/composables/useDialog.ts +0 -141
  94. package/src/uikit/composables/useDialogPluginComponent.ts +0 -27
  95. package/src/uikit/index.ts +0 -79
  96. package/src/uikit/primitives.ts +0 -150
  97. /package/{src → dist}/uikit/tokens.css +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/uikit/components/AppPopover.vue","../../src/uikit/components/AppSlider.vue","../../src/uikit/components/AppToggle.vue","../../src/uikit/components/AppSegmented.vue","../../src/uikit/composables/useDialogPluginComponent.ts","../../src/uikit/components/AppSelect.vue","../../src/uikit/components/AppTooltip.vue","../../src/uikit/components/AppTabs.vue","../../src/uikit/components/AppBanner.vue","../../src/uikit/components/AppButton.vue","../../src/uikit/components/AppButtonDropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * AppPopover — High-level wrapper (Layer 1).\n *\n * Plugin authors use this component without needing to know about inject\n * keys, Reka UI, modal manager mutual exclusion, or the animating guard.\n *\n * Public API is stable: switching the underlying headless library in the\n * future does not break the plugin author's code.\n *\n * Implementation notes:\n * - placement 'auto' picks the initial Reka UI side based on snap edge:\n * vbar-left → 'right', vbar-right → 'left', horizontal → 'bottom'.\n * Reka UI's built-in flip middleware then handles viewport-edge collision.\n * - usePassthroughGuard: called with the content element ref. The contentEl\n * ref is passed to usePassthroughGuard, which attaches mousemove/leave\n * handlers that toggle mouse passthrough based on pointer position.\n */\nimport { computed, inject, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport {\n PopoverRoot,\n PopoverTrigger,\n PopoverPortal,\n PopoverContent,\n PopoverArrow,\n} from '../primitives'\n// Inject keys MUST come from the package barrel — the runtime build\n// externalises '@openpen/module-api' so host + uikit share the same Symbol\n// instances. Importing from the relative '../../inject-keys' path bundles a\n// duplicate Symbol into module-api-uikit.js, causing host's provide() and\n// uikit's inject() to miss each other (popover never opens, etc).\nimport {\n WRAPPER_EL_KEY,\n MODAL_MANAGER_KEY,\n CONTROL_BAR_ANIMATING_KEY,\n POPOVER_PLACEMENT_HINT_KEY,\n} from '@openpen/module-api'\nimport { usePassthroughGuard } from '../../host/passthrough'\n\n// ── OpenPen-owned type (MUST NOT reference reka-ui types) ─────────────────────\n\n/** Preferred placement for the popover content relative to the trigger. */\nexport type Placement = 'auto' | 'top' | 'bottom' | 'left' | 'right'\n\nexport interface AppPopoverProps {\n /** Globally unique popover id; used for modal-stack mutual exclusion. */\n popoverId: string\n /**\n * Popover placement direction.\n * - 'auto' (default, recommended): the host's snap-edge hint picks the side that\n * keeps the popover on-screen across all control-bar modes.\n * - explicit values ('top' | 'bottom' | 'left' | 'right'): bypass the auto hint.\n * In dev mode a console warning is emitted because explicit values can position\n * popovers off-screen or away from the trigger button when the control bar is\n * in a vertical mode. Only use when you have verified the placement stays\n * on-screen across all snap layouts.\n */\n placement?: Placement\n /** Gap between trigger and content in px. Default 8. */\n gap?: number\n}\n\nexport interface AppPopoverSlots {\n /** Trigger slot; scope exposes active state + toggle / open / close actions. */\n trigger: (scope: {\n active: boolean\n toggle: () => void\n open: () => void\n close: () => void\n }) => unknown\n /** Popover content slot. */\n content: () => unknown\n}\n\nconst props = withDefaults(defineProps<AppPopoverProps>(), {\n placement: 'auto',\n gap: 8,\n})\n\ndefineSlots<AppPopoverSlots>()\n\n// ── 0. Dev-mode guard: warn plugin authors about risky explicit placement ─────\nonMounted(() => {\n if (!import.meta.env.DEV) return\n if (props.placement !== 'auto' && props.placement !== undefined) {\n console.warn(\n `[AppPopover] Explicit placement=\"${props.placement}\" bypasses ` +\n `the host's snap-edge auto detection. In vertical control-bar modes ` +\n `this can position popups off-screen or away from the trigger button. ` +\n `Prefer placement=\"auto\" (default) and let the host pick the side; ` +\n `use an explicit value only when you have verified it stays on-screen ` +\n `across snap-left / snap-right / snap-free / horizontal layouts.`\n )\n }\n})\n\n// ── 1. Inject host context (plugin author never sees these) ───────────────────\n\nconst wrapperElRef = inject(WRAPPER_EL_KEY)\nconst wrapperEl = computed<HTMLElement | null>(() => wrapperElRef?.value ?? null)\nconst modalManager = inject(MODAL_MANAGER_KEY)\nconst animating = inject(CONTROL_BAR_ANIMATING_KEY)\nconst popoverPlacementHint = inject(POPOVER_PLACEMENT_HINT_KEY)\n\n// ── 2. Modal manager mutual exclusion ────────────────────────────────────────\n\nconst isOpen = computed(() => modalManager?.isOpen(props.popoverId) ?? false)\n\nfunction open() {\n modalManager?.open(props.popoverId)\n}\n\nfunction close() {\n modalManager?.close(props.popoverId)\n}\n\nfunction toggle() {\n if (isOpen.value) {\n close()\n } else {\n open()\n }\n}\n\n// Sync Reka UI's controlled open state back to modal manager.\n// Reka UI may close via Escape / outside-click; we must keep manager in sync.\nfunction onUpdateOpen(v: boolean) {\n if (v) {\n open()\n } else {\n close()\n }\n}\n\n// ── 3. ControlBar animation guard — auto-close during transitions ────────────\n\nwatch(\n () => animating?.value,\n (isAnimating) => {\n if (isAnimating && isOpen.value) {\n close()\n }\n }\n)\n\n// ── 4. placement → Reka UI side mapping ──────────────────────────────────────\n//\n// 'auto' uses the POPOVER_PLACEMENT_HINT_KEY provided by ControlBar. The hint\n// encodes a two-tier UX rule:\n// - Snap edge set → open away from the edge (left→right, right→left, etc.)\n// - Snap off → open toward the side with more screen space on the relevant axis\n// (horizontal bar: top/bottom; vertical bar: left/right)\n// Reka UI's built-in flip middleware handles secondary overflow. The window is\n// workArea-sized, so the viewport equals the workArea and floating-ui's collision\n// detection is correct without any boundary-proxy workaround.\n\ntype RekaSide = 'top' | 'bottom' | 'left' | 'right'\n\nconst rekaSide = computed<RekaSide>(() => {\n if (props.placement !== 'auto') return props.placement as RekaSide\n // Fall back to 'bottom' only if no hint is provided (e.g. standalone use outside ControlBar).\n return popoverPlacementHint?.value ?? 'bottom'\n})\n\n// ── 5. Passthrough guard integration ─────────────────────────────────────────\n//\n// usePassthroughGuard is called with the content element ref. Reka UI's\n// PopoverContent is a Vue component, so the template ref resolves to a\n// component instance (with `$el`) rather than a raw HTMLElement. We normalize\n// via a watcher so the guard receives a plain element ref.\n// openpen-interactive class enables the ControlBar passthrough guard's CSS selector path.\nconst contentInstance = useTemplateRef<HTMLElement | { $el?: HTMLElement } | null>('content')\nconst contentEl = ref<HTMLElement | null>(null)\nwatch(contentInstance, (instance) => {\n if (!instance) {\n contentEl.value = null\n } else if (instance instanceof HTMLElement) {\n contentEl.value = instance\n } else if (typeof instance === 'object' && '$el' in instance && instance.$el instanceof HTMLElement) {\n contentEl.value = instance.$el\n } else {\n contentEl.value = null\n }\n})\nusePassthroughGuard(contentEl)\n</script>\n\n<template>\n <PopoverRoot :open=\"isOpen\" @update:open=\"onUpdateOpen\">\n <PopoverTrigger as-child>\n <slot\n name=\"trigger\"\n :active=\"isOpen\"\n :toggle=\"toggle\"\n :open=\"open\"\n :close=\"close\"\n />\n </PopoverTrigger>\n <PopoverPortal :to=\"wrapperEl ?? undefined\">\n <PopoverContent\n ref=\"content\"\n :side=\"rekaSide\"\n :avoid-collisions=\"true\"\n :side-offset=\"props.gap\"\n class=\"openpen-popover-content openpen-interactive\"\n data-testid=\"controlbar-popover\"\n >\n <slot name=\"content\" />\n <!-- Arrow geometry: 14×8 sharp triangle. Fill shares\n --openpen-color-popover-frame with the popup border so the arrow\n reads as a continuous extension of the frame. See tokens.css for\n per-theme values. -->\n <PopoverArrow class=\"openpen-popover-arrow\" :width=\"14\" :height=\"8\" />\n </PopoverContent>\n </PopoverPortal>\n </PopoverRoot>\n</template>\n\n<!-- Styles for teleported PopoverContent MUST be unscoped — Vue scoped CSS\n does not follow Teleport targets, so .openpen-popover-content would not\n receive the scoped data-v-* attribute when rendered inside the portal. -->\n<style>\n.openpen-popover-content {\n background: var(--openpen-color-surface-popup);\n border: 1px solid var(--openpen-color-popover-frame);\n border-radius: var(--openpen-radius-lg);\n padding: 14px 16px;\n color: var(--openpen-color-text-primary);\n font-size: 13px;\n box-shadow: var(--openpen-shadow);\n backdrop-filter: var(--openpen-blur);\n -webkit-backdrop-filter: var(--openpen-blur);\n z-index: 100;\n /* Prevent pointer events from leaking through transparent gaps. */\n pointer-events: auto;\n}\n\n.openpen-popover-arrow {\n fill: var(--openpen-color-popover-frame);\n}\n\n/* ── Open / close animations ────────────────────────────────────────────────\n Reka UI's Presence keeps the element in the DOM until the running CSS\n animation ends, so animations targeting [data-state=\"closed\"] DO play\n on close. transform-origin tracks data-side so the popover scales out\n from the trigger instead of from its own center.\n\n Timings: 200ms easeOutBack enter, 150ms ease leave. */\n.openpen-popover-content[data-state='open'] {\n animation: openpen-popover-in 200ms cubic-bezier(0.34, 1.56, 0.64, 1);\n}\n.openpen-popover-content[data-state='closed'] {\n animation: openpen-popover-out 150ms ease forwards;\n}\n\n.openpen-popover-content[data-side='bottom'] { transform-origin: top center; }\n.openpen-popover-content[data-side='top'] { transform-origin: bottom center; }\n.openpen-popover-content[data-side='left'] { transform-origin: right center; }\n.openpen-popover-content[data-side='right'] { transform-origin: left center; }\n\n@keyframes openpen-popover-in {\n from { opacity: 0; transform: scale(0.92); }\n to { opacity: 1; transform: scale(1); }\n}\n@keyframes openpen-popover-out {\n from { opacity: 1; transform: scale(1); }\n to { opacity: 0; transform: scale(0.92); }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppSlider — High-level wrapper (Layer 1).\n *\n * Wraps Reka UI Slider (SliderRoot / SliderTrack / SliderRange / SliderThumb).\n *\n * Visual: 4px track, 14×14 white thumb, accent fill,\n * hover scale(1.2) at var(--openpen-duration-fast) easing.\n *\n * Styles use --openpen-* design tokens.\n * No Reka UI types are leaked in the public API.\n */\nimport { computed } from 'vue'\nimport {\n SliderRoot,\n SliderTrack,\n SliderRange,\n SliderThumb,\n} from '../primitives'\n\n// ── Public API (MUST NOT reference reka-ui types) ─────────────────────────────\n\nconst props = withDefaults(defineProps<{\n modelValue: number\n min?: number\n max?: number\n step?: number\n /** Container width (e.g. '100%', '72px'). In vertical mode this sets the height. */\n width?: string\n orientation?: 'horizontal' | 'vertical'\n trackHeight?: string\n trackRadius?: string\n thumbWidth?: string\n thumbHeight?: string\n thumbRadius?: string\n /**\n * Reverse the value-to-position mapping. With orientation='vertical', sets\n * top=min and bottom=max (drag down to increase). With orientation='horizontal',\n * sets right=min and left=max (drag left to increase). Useful when an icon\n * legend implies the opposite direction of the underlying control.\n *\n * Delegates to reka-ui SliderRoot's native `inverted` prop, which swaps\n * startEdge/endEdge on SliderRange — no manual value transformation needed.\n */\n inverted?: boolean\n}>(), {\n min: 0,\n max: 100,\n step: 1,\n width: '100%',\n orientation: 'horizontal',\n trackHeight: '4px',\n trackRadius: '2px',\n thumbWidth: '14px',\n thumbHeight: '14px',\n thumbRadius: '50%',\n inverted: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n}>()\n\n// ── Internal state ────────────────────────────────────────────────────────────\n\nconst isVertical = computed(() => props.orientation === 'vertical')\n\n/** Root element inline style */\nconst rootStyle = computed(() => {\n if (isVertical.value) {\n return {\n '--thumb-w': props.thumbWidth,\n '--thumb-h': props.thumbHeight,\n '--thumb-r': props.thumbRadius,\n '--track-height': props.trackHeight,\n '--track-radius': props.trackRadius,\n height: props.width,\n width: '16px',\n }\n }\n return {\n '--thumb-w': props.thumbWidth,\n '--thumb-h': props.thumbHeight,\n '--thumb-r': props.thumbRadius,\n '--track-height': props.trackHeight,\n '--track-radius': props.trackRadius,\n width: props.width,\n }\n})\n\nfunction onValueChange(val: number[] | undefined) {\n if (val && val.length > 0) {\n emit('update:modelValue', val[0])\n }\n}\n</script>\n\n<template>\n <SliderRoot\n class=\"app-slider-root\"\n data-testid=\"app-slider-root\"\n :class=\"{ 'app-slider-root--vertical': isVertical, 'app-slider-root--inverted': inverted }\"\n :style=\"rootStyle\"\n :model-value=\"[modelValue]\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :orientation=\"orientation\"\n :inverted=\"inverted\"\n @update:model-value=\"onValueChange\"\n >\n <SliderTrack class=\"app-slider-track\" data-testid=\"app-slider-track\">\n <SliderRange class=\"app-slider-range\" data-testid=\"app-slider-range\" />\n </SliderTrack>\n <SliderThumb class=\"app-slider-thumb\" data-testid=\"app-slider-thumb\" />\n </SliderRoot>\n</template>\n\n<style scoped>\n/* ── Root container ────────────────────────────────────────────────────────── */\n.app-slider-root {\n position: relative;\n display: flex;\n align-items: center;\n user-select: none;\n touch-action: none;\n flex-shrink: 0;\n cursor: pointer;\n}\n\n.app-slider-root[data-orientation='horizontal'] {\n height: var(--track-height, 4px);\n}\n\n.app-slider-root[data-orientation='vertical'] {\n flex-direction: column;\n width: var(--track-height, 4px);\n}\n\n/* ── Track ─────────────────────────────────────────────────────────────────── */\n.app-slider-track {\n position: relative;\n flex-grow: 1;\n border-radius: var(--track-radius, 2px);\n background: var(--openpen-color-border-hi);\n}\n\n.app-slider-root[data-orientation='horizontal'] .app-slider-track {\n height: var(--track-height, 4px);\n}\n\n.app-slider-root[data-orientation='vertical'] .app-slider-track {\n width: var(--track-height, 4px);\n}\n\n/* ── Range (filled portion) ─────────────────────────────────────────────────── */\n.app-slider-range {\n position: absolute;\n border-radius: var(--track-radius, 2px);\n background: var(--openpen-color-accent);\n}\n\n.app-slider-root[data-orientation='horizontal'] .app-slider-range {\n height: 100%;\n top: 0;\n left: 0;\n}\n\n.app-slider-root[data-orientation='vertical'] .app-slider-range {\n width: 100%;\n bottom: 0;\n left: 0;\n}\n\n/* ── Thumb ─────────────────────────────────────────────────────────────────── */\n.app-slider-thumb {\n display: block;\n width: var(--thumb-w, 14px);\n height: var(--thumb-h, 14px);\n border-radius: var(--thumb-r, 50%);\n background: #fff;\n box-shadow: 0 1px 6px rgba(0, 0, 0, 0.50);\n cursor: pointer;\n outline: none;\n transition: transform var(--openpen-duration-fast) var(--openpen-easing-standard);\n}\n\n.app-slider-thumb:hover {\n transform: scale(1.2);\n}\n\n.app-slider-thumb:focus-visible {\n box-shadow: 0 0 0 3px var(--openpen-color-accent-glow);\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppToggle — High-level wrapper (Layer 1).\n *\n * Wraps Reka UI Switch (SwitchRoot / SwitchThumb).\n *\n * Visual: 40×24px, off=toggle-off-bg, on=accent,\n * 18×18 white thumb, bounce transition.\n *\n * Styles use --openpen-* design tokens.\n * No Reka UI types are leaked in the public API.\n *\n * DOM notes:\n * - SwitchRoot renders a <button role=\"switch\"> with class \"app-toggle\"\n * - \"on\" class is added when modelValue is true\n * - aria-pressed is added explicitly because Reka UI's SwitchRoot uses\n * aria-checked; both attributes co-exist for maximum a11y surface.\n */\nimport {\n SwitchRoot,\n SwitchThumb,\n} from '../primitives'\n\n// ── Public API (MUST NOT reference reka-ui types) ─────────────────────────────\n\nwithDefaults(defineProps<{\n modelValue: boolean\n ariaLabel?: string\n testid?: string\n}>(), { ariaLabel: '', testid: undefined })\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nfunction onCheckedChange(val: boolean) {\n emit('update:modelValue', val)\n}\n</script>\n\n<template>\n <SwitchRoot\n class=\"app-toggle\"\n :class=\"{ on: modelValue }\"\n :model-value=\"modelValue\"\n :aria-label=\"ariaLabel\"\n :aria-pressed=\"modelValue\"\n :data-testid=\"testid\"\n @update:model-value=\"onCheckedChange\"\n >\n <SwitchThumb class=\"app-toggle-thumb\" />\n </SwitchRoot>\n</template>\n\n<style scoped>\n/* Root — the toggle track */\n.app-toggle {\n width: 40px;\n height: 24px;\n border-radius: 12px;\n border: none;\n cursor: pointer;\n position: relative;\n flex-shrink: 0;\n background: var(--openpen-color-toggle-off);\n transition: background var(--openpen-duration-base) var(--openpen-easing-standard);\n /* Remove default button styles */\n padding: 0;\n appearance: none;\n -webkit-appearance: none;\n display: inline-flex;\n align-items: center;\n}\n\n.app-toggle.on {\n background: var(--openpen-color-accent);\n}\n\n/* Thumb — the white circular knob */\n.app-toggle-thumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n transition: left var(--openpen-duration-bounce) var(--openpen-easing-bounce);\n display: block;\n pointer-events: none;\n}\n\n.app-toggle.on .app-toggle-thumb,\n.app-toggle[data-state='checked'] .app-toggle-thumb {\n left: 19px;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppSegmented — High-level wrapper (Layer 1).\n *\n * Wraps Reka UI RadioGroup (RadioGroupRoot / RadioGroupItem).\n *\n * Visual: flex gap 4px, 1.5px border var(--border-hi),\n * active = var(--accent-bg) background + var(--accent) color.\n *\n * Styles use --openpen-* design tokens.\n * No Reka UI types are leaked in the public API.\n */\nimport {\n RadioGroupRoot,\n RadioGroupItem,\n} from '../primitives'\n\n// ── Public API (MUST NOT reference reka-ui types) ─────────────────────────────\n\ninterface Option {\n value: string\n label: string\n icon?: string\n testid?: string\n}\n\ndefineProps<{\n modelValue: string\n options: Option[]\n /** When true, all options are non-interactive and visually dimmed. */\n disabled?: boolean\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\n// AcceptableValue from Reka UI can be null; we narrow to string so the public API has no Reka UI type leakage.\nfunction onValueChange(val: unknown) {\n if (typeof val === 'string') {\n emit('update:modelValue', val)\n }\n}\n</script>\n\n<template>\n <RadioGroupRoot\n class=\"app-seg\"\n data-testid=\"app-seg\"\n :class=\"{ 'app-seg--disabled': disabled }\"\n :model-value=\"modelValue\"\n :disabled=\"disabled\"\n orientation=\"horizontal\"\n @update:model-value=\"onValueChange\"\n >\n <RadioGroupItem\n v-for=\"opt in options\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n as-child\n >\n <button\n class=\"app-seg-btn\"\n :class=\"{ active: modelValue === opt.value }\"\n :data-testid=\"opt.testid\"\n >\n <span\n v-if=\"opt.icon\"\n class=\"app-seg-icon\"\n aria-hidden=\"true\"\n v-html=\"opt.icon\"\n />\n <span>{{ opt.label }}</span>\n </button>\n </RadioGroupItem>\n </RadioGroupRoot>\n</template>\n\n<style scoped>\n.app-seg {\n display: flex;\n gap: var(--openpen-space-xs);\n}\n\n.app-seg-btn {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px var(--openpen-space-lg);\n border: 1.5px solid var(--openpen-color-border-hi);\n border-radius: var(--openpen-radius-sm);\n background: var(--openpen-color-input-bg);\n color: var(--openpen-color-text-dim);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition:\n background var(--openpen-duration-fast) var(--openpen-easing-standard),\n color var(--openpen-duration-fast) var(--openpen-easing-standard),\n border-color var(--openpen-duration-fast) var(--openpen-easing-standard);\n}\n\n.app-seg-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 0;\n}\n\n.app-seg-btn:hover {\n color: var(--openpen-color-text-primary);\n background: var(--openpen-color-control-hover);\n}\n\n.app-seg-btn.active {\n background: var(--openpen-color-accent-bg);\n color: var(--openpen-color-accent);\n border-color: color-mix(in srgb, var(--openpen-color-accent) 40%, transparent);\n}\n\n.app-seg--disabled {\n opacity: 0.45;\n pointer-events: none;\n cursor: not-allowed;\n}\n</style>\n","/**\n * useDialogPluginComponent — composable for components rendered via useDialog().custom().\n *\n * The custom component calls this to get ok/cancel/dismiss callbacks, which\n * resolve the Promise returned by useDialog().custom().\n */\nimport { inject } from 'vue'\nimport { DIALOG_PLUGIN_HANDLE_KEY } from './dialog-internal-keys'\n\nexport interface DialogPluginComponentHandle<T = unknown> {\n /** Resolve with a payload value and close the dialog. */\n ok(payload?: T): void\n /** Resolve with null and close the dialog. */\n cancel(): void\n /** Alias for cancel() — resolves with null. */\n dismiss(): void\n}\n\nexport function useDialogPluginComponent<T = unknown>(): DialogPluginComponentHandle<T> {\n const handle = inject(DIALOG_PLUGIN_HANDLE_KEY)\n if (!handle) {\n throw new Error(\n 'useDialogPluginComponent() must be called inside a component rendered via useDialog().custom()'\n )\n }\n return handle as DialogPluginComponentHandle<T>\n}\n","<script setup lang=\"ts\">\n/**\n * AppSelect — High-level wrapper (Layer 1).\n *\n * Plugin authors use this component without needing to know about Reka UI's\n * Select primitives. The API surface is OpenPen-owned and stable.\n *\n * Public API is stable: switching the underlying headless library in the\n * future does not break the plugin author's code.\n *\n * Implementation notes:\n * - Controlled via modelValue / update:modelValue (standard Vue v-model pattern).\n * - options[] is an OpenPen-owned type; no Reka UI types leak to the public API.\n * - Popup styling mirrors surface-popup tokens (same as AppPopover content).\n * - Styles are unscoped because SelectContent is teleported via SelectPortal.\n */\nimport {\n SelectRoot,\n SelectTrigger,\n SelectPortal,\n SelectContent,\n SelectViewport,\n SelectItem,\n SelectItemText,\n SelectItemIndicator,\n SelectValue,\n SelectIcon,\n} from '../primitives'\n\n// ── OpenPen-owned types (MUST NOT reference reka-ui types) ───────────────────\n\n/** A single option in the dropdown list. */\nexport interface SelectOption {\n /** The value emitted on selection. */\n value: string\n /** The label shown to the user. */\n label: string\n}\n\nexport interface AppSelectProps {\n /** Currently selected value (bind with v-model). */\n modelValue: string\n /** List of selectable options. */\n options: SelectOption[]\n /** Placeholder shown when no option is selected. */\n placeholder: string\n /** Whether the select is disabled. */\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<AppSelectProps>(), {\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nfunction onValueChange(v: string) {\n emit('update:modelValue', v)\n}\n</script>\n\n<template>\n <SelectRoot\n :model-value=\"modelValue\"\n :disabled=\"disabled\"\n @update:model-value=\"onValueChange\"\n >\n <SelectTrigger class=\"openpen-select-trigger\">\n <SelectValue :placeholder=\"placeholder\" />\n <SelectIcon class=\"openpen-select-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M2 4L6 8L10 4\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </SelectIcon>\n </SelectTrigger>\n <SelectPortal>\n <SelectContent class=\"openpen-select-content\" position=\"popper\" :side-offset=\"4\">\n <SelectViewport class=\"openpen-select-viewport\">\n <SelectItem\n v-for=\"option in options\"\n :key=\"option.value\"\n :value=\"option.value\"\n class=\"openpen-select-item\"\n >\n <SelectItemIndicator class=\"openpen-select-item-indicator\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M2 6L5 9L10 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </SelectItemIndicator>\n <SelectItemText>{{ option.label }}</SelectItemText>\n </SelectItem>\n </SelectViewport>\n </SelectContent>\n </SelectPortal>\n </SelectRoot>\n</template>\n\n<!-- Styles MUST be unscoped — Vue scoped CSS does not follow Teleport targets,\n so .openpen-select-* classes would not receive the scoped data-v-* attribute\n when rendered inside the portal. -->\n<style>\n/* ── Trigger ─────────────────────────────────────────────────────────────── */\n.openpen-select-trigger {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--openpen-space-sm);\n min-width: 120px;\n height: 32px;\n padding: 0 var(--openpen-space-md);\n background: var(--openpen-color-input-bg);\n border: 1px solid var(--openpen-color-border-hi);\n border-radius: var(--openpen-radius-md);\n color: var(--openpen-color-text-primary);\n font-size: 13px;\n cursor: pointer;\n transition:\n background var(--openpen-duration-fast),\n border-color var(--openpen-duration-fast);\n outline: none;\n white-space: nowrap;\n user-select: none;\n}\n\n.openpen-select-trigger:hover {\n background: var(--openpen-color-control-hover);\n border-color: var(--openpen-color-border-hi);\n}\n\n.openpen-select-trigger:focus-visible {\n border-color: var(--openpen-color-accent);\n box-shadow: 0 0 0 2px var(--openpen-color-accent-glow);\n}\n\n.openpen-select-trigger[data-disabled] {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n/* ── Trigger icon (chevron) ──────────────────────────────────────────────── */\n.openpen-select-icon {\n color: var(--openpen-color-text-dim);\n flex-shrink: 0;\n display: flex;\n align-items: center;\n transition: transform var(--openpen-duration-fast);\n}\n\n.openpen-select-trigger[data-state='open'] .openpen-select-icon {\n transform: rotate(180deg);\n}\n\n/* ── Dropdown content ────────────────────────────────────────────────────── */\n.openpen-select-content {\n background: var(--openpen-color-surface-popup);\n border: 1px solid var(--openpen-color-border-hi);\n border-radius: var(--openpen-radius-md);\n box-shadow: var(--openpen-shadow);\n backdrop-filter: var(--openpen-blur);\n -webkit-backdrop-filter: var(--openpen-blur);\n z-index: 100;\n pointer-events: auto;\n /* Match trigger width */\n min-width: var(--reka-select-trigger-width, 120px);\n max-height: 240px;\n overflow: hidden;\n}\n\n/* ── Viewport scrollable area ────────────────────────────────────────────── */\n.openpen-select-viewport {\n padding: var(--openpen-space-xs);\n}\n\n/* ── Individual items ────────────────────────────────────────────────────── */\n.openpen-select-item {\n display: flex;\n align-items: center;\n gap: var(--openpen-space-sm);\n height: 30px;\n padding: 0 var(--openpen-space-md);\n padding-left: 28px; /* indent to align text past indicator */\n border-radius: var(--openpen-radius-sm);\n font-size: 13px;\n color: var(--openpen-color-text-primary);\n cursor: pointer;\n position: relative;\n user-select: none;\n outline: none;\n transition: background var(--openpen-duration-fast);\n}\n\n.openpen-select-item[data-highlighted] {\n background: var(--openpen-color-control-hover);\n color: var(--openpen-color-text-primary);\n}\n\n.openpen-select-item[data-state='checked'] {\n color: var(--openpen-color-accent);\n}\n\n.openpen-select-item[data-disabled] {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n/* ── Selected item indicator (checkmark) ─────────────────────────────────── */\n.openpen-select-item-indicator {\n position: absolute;\n left: var(--openpen-space-sm);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--openpen-color-accent);\n}\n\n/* ── Open / close animations ─────────────────────────────────────────────────\n Pattern matches AppPopover keyframes (200ms bounce enter, 150ms ease leave). */\n.openpen-select-content[data-state='open'] {\n animation: openpen-select-in 200ms cubic-bezier(0.34, 1.56, 0.64, 1);\n}\n.openpen-select-content[data-state='closed'] {\n animation: openpen-select-out 150ms ease forwards;\n}\n\n@keyframes openpen-select-in {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n}\n@keyframes openpen-select-out {\n from { opacity: 1; transform: scale(1); }\n to { opacity: 0; transform: scale(0.95); }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppTooltip — High-level wrapper (Layer 1).\n *\n * Wraps Reka UI Tooltip (TooltipProvider / TooltipRoot / TooltipTrigger /\n * TooltipPortal / TooltipContent / TooltipArrow).\n *\n * Plugin authors get hover-triggered tooltips with OpenPen styling without\n * knowing about Reka UI internals or inject keys.\n *\n * Styles use --openpen-* design tokens.\n * No Reka UI types are leaked in the public API.\n *\n * Usage:\n * <AppTooltip content=\"Save file\" side=\"top\">\n * <button>Save</button>\n * </AppTooltip>\n */\nimport {\n TooltipProvider,\n TooltipRoot,\n TooltipTrigger,\n TooltipPortal,\n TooltipContent,\n TooltipArrow,\n} from '../primitives'\n\n// ── OpenPen-owned types (MUST NOT reference reka-ui types) ────────────────────\n\n/** Preferred side for tooltip content relative to the trigger. */\nexport type TooltipSide = 'top' | 'bottom' | 'left' | 'right'\n\nexport interface AppTooltipProps {\n /** Tooltip text content. */\n content: string\n /** Preferred placement side relative to the trigger. Default 'top'. */\n side?: TooltipSide\n /** Delay before tooltip appears on hover, in ms. Default 200. */\n delay?: number\n}\n\nexport interface AppTooltipSlots {\n /** Trigger element. Any element that receives hover events. */\n default: () => unknown\n}\n\nconst props = withDefaults(defineProps<AppTooltipProps>(), {\n side: 'top',\n delay: 200,\n})\n\ndefineSlots<AppTooltipSlots>()\n</script>\n\n<template>\n <TooltipProvider :delay-duration=\"props.delay\">\n <TooltipRoot>\n <TooltipTrigger as-child>\n <slot />\n </TooltipTrigger>\n <TooltipPortal>\n <TooltipContent\n :side=\"props.side\"\n :side-offset=\"6\"\n :avoid-collisions=\"true\"\n :collision-padding=\"8\"\n class=\"openpen-tooltip-content\"\n >\n {{ props.content }}\n <TooltipArrow class=\"openpen-tooltip-arrow\" :width=\"10\" :height=\"5\" />\n </TooltipContent>\n </TooltipPortal>\n </TooltipRoot>\n </TooltipProvider>\n</template>\n\n<!-- Styles for teleported TooltipContent MUST be unscoped — Vue scoped CSS\n does not follow Teleport targets (same rationale as AppPopover). -->\n<style>\n.openpen-tooltip-content {\n background: var(--openpen-color-tooltip-bg);\n border: 1px solid var(--openpen-color-border-hi);\n border-radius: var(--openpen-radius-sm);\n padding: 5px 9px;\n color: var(--openpen-color-tooltip-text);\n font-size: 12px;\n line-height: 1.4;\n /* Prevent text wrap for short tooltip labels */\n white-space: nowrap;\n box-shadow: var(--openpen-shadow-sm);\n z-index: 200;\n pointer-events: none;\n}\n\n.openpen-tooltip-arrow {\n fill: var(--openpen-color-tooltip-bg);\n}\n\n/* ── Open / close animations ────────────────────────────────────────────────\n Reka UI Presence keeps the element in the DOM until the CSS animation ends,\n so data-state=\"delayed-open\" / \"instant-open\" / \"closed\" all animate. */\n.openpen-tooltip-content[data-state='delayed-open'],\n.openpen-tooltip-content[data-state='instant-open'] {\n animation: openpen-tooltip-in var(--openpen-duration-fast) var(--openpen-easing-standard);\n}\n.openpen-tooltip-content[data-state='closed'] {\n animation: openpen-tooltip-out var(--openpen-duration-fast) ease forwards;\n}\n\n@keyframes openpen-tooltip-in {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n}\n@keyframes openpen-tooltip-out {\n from { opacity: 1; transform: scale(1); }\n to { opacity: 0; transform: scale(0.95); }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppTabs — High-level wrapper (Layer 1).\n *\n * Wraps Reka UI Tabs (TabsRoot / TabsList / TabsTrigger / TabsContent).\n * Intended for settings UI tabbed navigation and any plugin tab panel.\n *\n * Plugin authors get a controlled tab switcher with OpenPen styling without\n * knowing about Reka UI internals.\n *\n * Styles use --openpen-* design tokens.\n * No Reka UI types are leaked in the public API.\n *\n * Usage:\n * <AppTabs\n * v-model=\"activeTab\"\n * :tabs=\"[{ id: 'general', label: 'General' }, { id: 'advanced', label: 'Advanced' }]\"\n * >\n * <template #default=\"{ activeTabId }\">\n * <div v-if=\"activeTabId === 'general'\">...</div>\n * <div v-if=\"activeTabId === 'advanced'\">...</div>\n * </template>\n * </AppTabs>\n */\nimport {\n TabsRoot,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from '../primitives'\n\n// ── OpenPen-owned types (MUST NOT reference reka-ui types) ────────────────────\n\nexport interface AppTabItem {\n /** Unique identifier for this tab. Used as the v-model value. */\n id: string\n /** Human-readable label rendered in the tab trigger button. */\n label: string\n}\n\nexport interface AppTabsProps {\n /** Currently active tab id (controlled mode). */\n modelValue: string\n /** Ordered list of tab descriptors. */\n tabs: AppTabItem[]\n}\n\nexport interface AppTabsSlots {\n /**\n * Default slot for tab content area.\n * Receives the currently active tab id via scoped slot.\n */\n default: (scope: { activeTabId: string }) => unknown\n}\n\nconst props = defineProps<AppTabsProps>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\ndefineSlots<AppTabsSlots>()\n\nfunction onTabChange(val: string | number | undefined) {\n if (typeof val === 'string') {\n emit('update:modelValue', val)\n }\n}\n</script>\n\n<template>\n <TabsRoot\n :model-value=\"props.modelValue\"\n class=\"openpen-tabs\"\n @update:model-value=\"onTabChange\"\n >\n <TabsList class=\"openpen-tabs-list\">\n <TabsTrigger\n v-for=\"tab in props.tabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n class=\"openpen-tabs-trigger\"\n >\n {{ tab.label }}\n </TabsTrigger>\n </TabsList>\n\n <!-- Single content outlet: plugin author renders tab content via scoped slot. -->\n <TabsContent\n v-for=\"tab in props.tabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n class=\"openpen-tabs-content\"\n >\n <slot :active-tab-id=\"tab.id\" />\n </TabsContent>\n </TabsRoot>\n</template>\n\n<style scoped>\n/* ── Tab root ───────────────────────────────────────────────────────────────── */\n.openpen-tabs {\n display: flex;\n flex-direction: column;\n gap: var(--openpen-space-sm);\n}\n\n/* ── Tab list (trigger bar) ─────────────────────────────────────────────────── */\n.openpen-tabs-list {\n display: flex;\n flex-direction: row;\n gap: 2px;\n padding: 3px;\n background: var(--openpen-color-control-group);\n border: 1px solid var(--openpen-color-border);\n border-radius: var(--openpen-radius-md);\n}\n\n/* ── Individual trigger ─────────────────────────────────────────────────────── */\n.openpen-tabs-trigger {\n flex: 1;\n padding: var(--openpen-space-xs) var(--openpen-space-sm);\n border: none;\n border-radius: calc(var(--openpen-radius-md) - 3px);\n background: transparent;\n color: var(--openpen-color-text-dim);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition:\n background var(--openpen-duration-fast) var(--openpen-easing-standard),\n color var(--openpen-duration-fast) var(--openpen-easing-standard);\n white-space: nowrap;\n /* Remove default button outline; focus-visible ring below handles a11y. */\n outline: none;\n}\n\n.openpen-tabs-trigger:hover {\n background: var(--openpen-color-control-hover);\n color: var(--openpen-color-text-primary);\n}\n\n.openpen-tabs-trigger[data-state='active'] {\n background: var(--openpen-color-accent-bg);\n color: var(--openpen-color-accent);\n box-shadow: 0 0 0 1px var(--openpen-color-accent-glow);\n}\n\n.openpen-tabs-trigger:focus-visible {\n box-shadow: 0 0 0 2px var(--openpen-color-accent);\n}\n\n/* ── Tab content ────────────────────────────────────────────────────────────── */\n.openpen-tabs-content {\n /* Content area has no chrome by default; plugin authors add their own. */\n outline: none;\n}\n\n.openpen-tabs-content:focus-visible {\n box-shadow: 0 0 0 2px var(--openpen-color-accent);\n border-radius: var(--openpen-radius-sm);\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppBanner — High-level wrapper (Layer 1).\n *\n * Presents info / warning / success / error status messages using the\n * --openpen-color-state-* design tokens. The icon is auto-rendered based on\n * the variant; message content goes in the default slot; optional inline action\n * buttons (e.g. restart / dismiss) go in the `#actions` slot.\n *\n * Inline mode (compact single-line) is enabled via the `inline` prop and is\n * suited for form validation feedback and status lines inside dialogs.\n */\n\nexport type BannerVariant = 'info' | 'warning' | 'success' | 'error'\n\nwithDefaults(defineProps<{\n variant: BannerVariant\n /** Compact single-line layout: smaller padding, vertically centered. */\n inline?: boolean\n}>(), { inline: false })\n</script>\n\n<template>\n <div\n :class=\"['app-banner', `app-banner-${variant}`, { 'app-banner-inline': inline }]\"\n :role=\"variant === 'error' ? 'alert' : 'status'\"\n >\n <span class=\"app-banner-icon\" aria-hidden=\"true\">\n <!-- info -->\n <svg v-if=\"variant === 'info'\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>\n </svg>\n <!-- warning -->\n <svg v-else-if=\"variant === 'warning'\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n <!-- success -->\n <svg v-else-if=\"variant === 'success'\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n <!-- error -->\n <svg v-else width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n </span>\n\n <span class=\"app-banner-body\"><slot /></span>\n\n <span v-if=\"$slots.actions\" class=\"app-banner-actions\">\n <slot name=\"actions\" />\n </span>\n </div>\n</template>\n\n<style scoped>\n.app-banner {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 14px;\n border-radius: var(--openpen-radius-md, 10px);\n border: 1px solid;\n font-size: 13px;\n line-height: 1.55;\n}\n\n.app-banner-inline {\n padding: 7px 12px;\n align-items: center;\n font-size: 12px;\n}\n\n.app-banner-icon {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n margin-top: 1px;\n}\n\n.app-banner-inline .app-banner-icon { margin-top: 0; }\n\n.app-banner-body {\n flex: 1;\n min-width: 0;\n}\n\n.app-banner-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n margin-left: auto;\n}\n\n/* Variants */\n.app-banner-info {\n background: var(--openpen-color-state-info-bg);\n border-color: var(--openpen-color-state-info-border);\n color: var(--openpen-color-state-info-text);\n}\n.app-banner-info .app-banner-icon {\n color: var(--openpen-color-state-info-icon);\n}\n\n.app-banner-warning {\n background: var(--openpen-color-state-warning-bg);\n border-color: var(--openpen-color-state-warning-border);\n color: var(--openpen-color-state-warning-text);\n}\n.app-banner-warning .app-banner-icon {\n color: var(--openpen-color-state-warning-icon);\n}\n\n.app-banner-success {\n background: var(--openpen-color-state-success-bg);\n border-color: var(--openpen-color-state-success-border);\n color: var(--openpen-color-state-success-text);\n}\n.app-banner-success .app-banner-icon {\n color: var(--openpen-color-state-success-icon);\n}\n\n.app-banner-error {\n background: var(--openpen-color-state-error-bg);\n border-color: var(--openpen-color-state-error-border);\n color: var(--openpen-color-state-error-text);\n}\n.app-banner-error .app-banner-icon {\n color: var(--openpen-color-state-error-icon);\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppButton — control-bar button wrapper (Layer 1).\n *\n * Provides the standard 36×36 rounded button appearance that matches the\n * host's control-bar buttons, so plugins do not need to replicate the\n * visual design manually.\n *\n * Disabled state uses aria-disabled + a CSS class rather than the HTML\n * `disabled` attribute, because `disabled` removes pointer events and would\n * swallow the tooltip hover that this component is expected to keep alive.\n *\n * In a vertical control bar the button shrinks to 34×34 and the tooltip\n * points away from the snapped edge (snap-left → right side; snap-right →\n * left side; vbar-free → right side). In a horizontal bar the tooltip sits\n * above the button by default, but flips below when the bar is near the\n * workArea top edge (the host raises TOOLTIP_FLIP_DOWN_KEY in that case).\n *\n * All orientation signals come from the host via injection\n * (IS_VERTICAL_KEY / SNAP_EDGE_KEY / TOOLTIP_FLIP_DOWN_KEY) so plugin\n * buttons render identically to the host's own control-bar buttons.\n */\n\nimport { computed, inject } from 'vue'\nimport { SNAP_EDGE_KEY, IS_VERTICAL_KEY, TOOLTIP_FLIP_DOWN_KEY } from '../../inject-keys'\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nconst props = withDefaults(defineProps<{\n variant?: 'default' | 'danger'\n active?: boolean\n disabled?: boolean\n tooltip?: string\n ariaLabel?: string\n}>(), {\n variant: 'default',\n active: false,\n disabled: false,\n tooltip: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<{\n click: []\n}>()\n\nfunction handleClick() {\n if (!props.disabled) {\n emit('click')\n }\n}\n\n// ── Orientation context (host-injected) ───────────────────────────────────────\n\nconst isVertical = inject(IS_VERTICAL_KEY, undefined)\nconst snapEdge = inject(SNAP_EDGE_KEY, undefined)\nconst tooltipFlipDown = inject(TOOLTIP_FLIP_DOWN_KEY, undefined)\n\nconst tooltipSide = computed<'top' | 'top-flip' | 'right' | 'left'>(() => {\n if (isVertical?.value) {\n return snapEdge?.value === 'right' ? 'left' : 'right'\n }\n return tooltipFlipDown?.value ? 'top-flip' : 'top'\n})\n</script>\n\n<template>\n <button\n class=\"app-btn\"\n :class=\"[\n `app-btn--tooltip-${tooltipSide}`,\n {\n 'app-btn--vertical': isVertical,\n active,\n danger: variant === 'danger',\n 'app-btn-disabled': disabled,\n },\n ]\"\n :data-tip=\"tooltip\"\n :aria-label=\"ariaLabel\"\n :aria-disabled=\"disabled || undefined\"\n @click=\"handleClick\"\n >\n <slot />\n </button>\n</template>\n\n<style scoped>\n.app-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border: none;\n border-radius: var(--openpen-radius-md);\n background: transparent;\n color: var(--openpen-color-text-dim);\n cursor: pointer;\n transition: background var(--openpen-duration-fast), color var(--openpen-duration-fast), transform var(--openpen-duration-fast);\n position: relative;\n flex-shrink: 0;\n padding: 0;\n}\n\n/* Vertical bars shrink the button to 34×34 to match the host's\n `.control-bar.is-vertical .cb-btn` rule. */\n.app-btn.app-btn--vertical {\n width: 34px;\n height: 34px;\n}\n\n.app-btn:hover {\n background: var(--openpen-color-control-hover);\n color: var(--openpen-color-text-primary);\n}\n\n.app-btn:active {\n transform: scale(0.88);\n}\n\n.app-btn.active {\n background: var(--openpen-color-accent-bg);\n color: var(--openpen-color-accent);\n box-shadow: 0 0 0 1px var(--openpen-color-accent-glow);\n}\n\n.app-btn.danger {\n color: var(--openpen-color-danger);\n}\n\n.app-btn.danger:hover {\n background: var(--openpen-color-danger-hover-bg);\n color: var(--openpen-color-danger-hover);\n}\n\n/* Disabled: keeps pointer events alive so the tooltip ::after hover still fires. */\n.app-btn.app-btn-disabled {\n cursor: not-allowed;\n}\n\n/* Dim only the button content — tooltip pseudo-element stays at full opacity. */\n.app-btn.app-btn-disabled > * {\n opacity: 0.30;\n}\n\n/* Tooltip — horizontal mode default (above the button). */\n.app-btn[data-tip]::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n background: var(--openpen-color-tooltip-bg);\n border: 1px solid var(--openpen-color-tooltip-border);\n color: var(--openpen-color-tooltip-text);\n font-size: 11.5px;\n font-weight: 500;\n white-space: nowrap;\n padding: 5px 10px;\n border-radius: var(--openpen-radius-sm);\n pointer-events: none;\n opacity: 0;\n transition: opacity var(--openpen-duration-fast);\n box-shadow: var(--openpen-shadow-sm);\n z-index: 41;\n}\n\n/* Horizontal mode near workArea top: tooltip flips below the button so\n it does not extend off-screen. The host sets TOOLTIP_FLIP_DOWN_KEY when\n the ball is within ~60 px of the top edge. */\n.app-btn.app-btn--tooltip-top-flip[data-tip]::after {\n bottom: auto;\n top: calc(100% + 8px);\n}\n\n/* Vertical bar, snap-left or vbar-free: tooltip points right (inward). */\n.app-btn.app-btn--tooltip-right[data-tip]::after {\n top: 50%;\n bottom: auto;\n left: calc(100% + 8px);\n right: auto;\n transform: translateY(-50%);\n}\n\n/* Vertical bar, snap-right: tooltip points left (inward). */\n.app-btn.app-btn--tooltip-left[data-tip]::after {\n top: 50%;\n bottom: auto;\n left: auto;\n right: calc(100% + 8px);\n transform: translateY(-50%);\n}\n\n.app-btn[data-tip]:hover::after {\n opacity: 1;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * AppButtonDropdown — split-mode dropdown button wrapper (Layer 1).\n *\n * Pairs an AppButton (main action) with a narrow caret button that toggles\n * an AppPopover. Modelled after Quasar's QBtnDropdown split-mode and shadcn\n * Button + DropdownMenu composition: main click and caret click are\n * independent events so the host can activate a tool on the main click\n * and open the dropdown on the caret click (or activate AND open from\n * the caret when the tool is currently inactive).\n *\n * Caret icon rotation tracks the host's snap edge and vertical layout via\n * the same injection keys AppButton uses — plugin authors get the right\n * rotation behaviour without configuring anything.\n *\n * Visual identity:\n * - Main button: identical to AppButton (36×36 horizontal, 34×34 vertical)\n * - Caret: 16 wide × 36 tall in horizontal mode (14 × 30 in vertical),\n * hover background + accent on active, chevron rotates toward the\n * popover side\n * - Wrap: flex row in horizontal mode, flex column in vertical\n *\n * The structural classes (`app-btn-dropdown-wrap`, `app-btn-dropdown-caret`,\n * `app-btn-dropdown-caret-icon`) are deliberately stable so host CSS can\n * apply contextual overrides (e.g. `.cb-group--inset .app-btn-dropdown-caret`\n * shrinks the caret inside inset groups).\n */\nimport { computed, inject } from 'vue'\nimport { SNAP_EDGE_KEY, IS_VERTICAL_KEY } from '../../inject-keys'\nimport AppButton from './AppButton.vue'\nimport AppPopover from './AppPopover.vue'\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nconst props = withDefaults(defineProps<{\n /** Globally unique popover id (passed through to the inner AppPopover). */\n popoverId: string\n /** Preferred popover placement; defaults to 'auto'. */\n popoverPlacement?: 'auto' | 'top' | 'bottom' | 'left' | 'right'\n /** Main button active state (drives accent highlight). */\n active?: boolean\n /** Disables both main and caret buttons; tooltip remains active. */\n disabled?: boolean\n /** Main button variant (passed through to AppButton). */\n variant?: 'default' | 'danger'\n /** Tooltip shown above the main button. */\n mainTooltip?: string\n /** Main button aria-label. */\n mainAriaLabel?: string\n /** Caret button aria-label. Required for screen-reader users. */\n caretAriaLabel?: string\n /** Optional test ids forwarded to the rendered buttons. */\n mainTestid?: string\n caretTestid?: string\n}>(), {\n popoverPlacement: 'auto',\n active: false,\n disabled: false,\n variant: 'default',\n mainTooltip: undefined,\n mainAriaLabel: undefined,\n caretAriaLabel: undefined,\n mainTestid: undefined,\n caretTestid: undefined,\n})\n\nconst emit = defineEmits<{\n /** Fires when the main button is clicked (unless `disabled`). */\n mainClick: []\n /**\n * Fires when the caret button is clicked (unless `disabled`).\n * AppPopover handles open/close via its own trigger logic; this event\n * lets the host run side-effects (e.g. activate the tool if it isn't\n * already active before opening the dropdown).\n */\n caretClick: []\n}>()\n\n// ── Orientation context (host-injected) ───────────────────────────────────────\n\nconst isVertical = inject(IS_VERTICAL_KEY, undefined)\nconst snapEdge = inject(SNAP_EDGE_KEY, undefined)\n\n/**\n * Caret chevron rotation when the popover is open.\n *\n * - inactive → down (resting state, points at the popover that will appear)\n * - horizontal + active → up (popover is above, chevron points to it)\n * - vertical + snap-left or vbar-free + active → right (popover opens right)\n * - vertical + snap-right + active → left (popover opens left)\n */\nfunction caretRotation(open: boolean): 'down' | 'up' | 'left' | 'right' {\n if (!open) return 'down'\n if (!isVertical?.value) return 'up'\n return snapEdge?.value === 'right' ? 'left' : 'right'\n}\n\nfunction onMainClick(): void {\n if (!props.disabled) emit('mainClick')\n}\n\nfunction onCaretClick(): void {\n if (!props.disabled) emit('caretClick')\n}\n</script>\n\n<template>\n <div\n class=\"app-btn-dropdown-wrap\"\n :class=\"{ 'app-btn-dropdown-wrap--vertical': isVertical }\"\n >\n <AppButton\n :variant=\"variant\"\n :active=\"active\"\n :disabled=\"disabled\"\n :tooltip=\"mainTooltip\"\n :aria-label=\"mainAriaLabel\"\n :data-testid=\"mainTestid\"\n @click=\"onMainClick\"\n >\n <slot name=\"main-content\" />\n </AppButton>\n\n <AppPopover :popover-id=\"popoverId\" :placement=\"popoverPlacement\">\n <template #trigger=\"{ active: open }\">\n <button\n type=\"button\"\n class=\"app-btn-dropdown-caret\"\n :class=\"{\n active: open,\n 'app-btn-dropdown-caret--vertical': isVertical,\n 'app-btn-dropdown-caret--disabled': disabled,\n }\"\n :aria-label=\"caretAriaLabel\"\n :aria-disabled=\"disabled || undefined\"\n :data-testid=\"caretTestid\"\n @click=\"onCaretClick\"\n >\n <svg\n class=\"app-btn-dropdown-caret-icon\"\n :class=\"`app-btn-dropdown-caret-icon--${caretRotation(open)}`\"\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n </template>\n <template #content>\n <slot name=\"popover-content\" />\n </template>\n </AppPopover>\n </div>\n</template>\n\n<style>\n/* Structural classes ship UNSCOPED on purpose so host CSS can apply\n contextual overrides (e.g. `.cb-group--inset .app-btn-dropdown-caret`\n shrinks the caret inside inset groups in the host control bar). The\n classes are deliberately prefixed `app-btn-dropdown-*` to avoid\n collisions in plugin code. */\n.app-btn-dropdown-wrap {\n display: flex;\n align-items: center;\n gap: 0;\n -webkit-app-region: no-drag;\n}\n\n.app-btn-dropdown-wrap--vertical {\n flex-direction: column;\n gap: 2px;\n}\n\n.app-btn-dropdown-caret {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 36px;\n border: none;\n background: transparent;\n color: var(--openpen-color-text-dim);\n cursor: pointer;\n padding: 0;\n border-radius: 8px;\n transition: color var(--openpen-duration-fast), background var(--openpen-duration-fast);\n}\n\n.app-btn-dropdown-caret--vertical {\n width: 30px;\n height: 14px;\n}\n\n.app-btn-dropdown-caret:hover {\n color: var(--openpen-color-text-primary);\n background: var(--openpen-color-control-hover);\n}\n\n.app-btn-dropdown-caret.active {\n color: var(--openpen-color-accent);\n background: var(--openpen-color-accent-bg);\n}\n\n.app-btn-dropdown-caret--disabled {\n cursor: not-allowed;\n}\n\n.app-btn-dropdown-caret--disabled > * {\n opacity: 0.30;\n}\n\n.app-btn-dropdown-caret-icon {\n transition: transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1);\n}\n\n.app-btn-dropdown-caret-icon--down { transform: rotate(0deg); }\n.app-btn-dropdown-caret-icon--up { transform: rotate(180deg); }\n.app-btn-dropdown-caret-icon--right { transform: rotate(-90deg); }\n.app-btn-dropdown-caret-icon--left { transform: rotate(90deg); }\n</style>\n"],"names":["_createBlock","_unref","_createVNode","_renderSlot","_normalizeClass","_createElementBlock","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createTextVNode","_openBlock","$slots","AppPopover","_withCtx"],"mappings":"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"}
@@ -9,4 +9,4 @@
9
9
  * DialogHost — the imperative dialog renderer mounted by App.vue at bootstrap.
10
10
  * DialogHost is NOT part of the plugin-facing uikit API.
11
11
  */
12
- export { default as DialogHost } from './components/DialogHost.vue'
12
+ export { default as DialogHost } from './components/DialogHost.vue';
@@ -0,0 +1,157 @@
1
+ import { defineComponent, inject, ref, watch, nextTick, computed, provide, openBlock, createBlock, createSlots, withCtx, createElementBlock, toDisplayString, Fragment, createElementVNode, withDirectives, withKeys, vModelText, resolveDynamicComponent, normalizeProps, mergeProps, createCommentVNode, normalizeClass } from "vue";
2
+ import { a as _activeRequest, D as DIALOG_PLUGIN_HANDLE_KEY, _ as _sfc_main$1, b as _resolveActive } from "../_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js";
3
+ import "reka-ui";
4
+ import { H as HOST_DIALOG_OPEN_COUNT_KEY } from "../_chunks/inject-keys-CRXCpN-M.js";
5
+ import "dompurify";
6
+ import "zod";
7
+ const _hoisted_1 = {
8
+ key: 0,
9
+ class: "openpen-dialog-host-message"
10
+ };
11
+ const _hoisted_2 = { class: "openpen-dialog-host-message" };
12
+ const _hoisted_3 = ["placeholder"];
13
+ const MODAL_ID = "__openpen_imperative_dialog__";
14
+ const _sfc_main = /* @__PURE__ */ defineComponent({
15
+ __name: "DialogHost",
16
+ setup(__props) {
17
+ const dialogOpenCount = inject(HOST_DIALOG_OPEN_COUNT_KEY, ref(0));
18
+ const isOpen = ref(false);
19
+ const promptValue = ref("");
20
+ const promptInputRef = ref(null);
21
+ watch(_activeRequest, (req2) => {
22
+ if (req2) {
23
+ if (req2.kind === "prompt") {
24
+ promptValue.value = req2.opts.defaultValue ?? "";
25
+ }
26
+ if (!isOpen.value) {
27
+ dialogOpenCount.value++;
28
+ }
29
+ isOpen.value = true;
30
+ if (req2.kind === "prompt") {
31
+ nextTick(() => {
32
+ var _a;
33
+ (_a = promptInputRef.value) == null ? void 0 : _a.focus();
34
+ });
35
+ }
36
+ } else {
37
+ if (isOpen.value) {
38
+ dialogOpenCount.value = Math.max(0, dialogOpenCount.value - 1);
39
+ }
40
+ isOpen.value = false;
41
+ }
42
+ });
43
+ const req = computed(() => _activeRequest.value);
44
+ const opts = computed(() => {
45
+ var _a;
46
+ return (_a = req.value) == null ? void 0 : _a.opts;
47
+ });
48
+ function handleOk() {
49
+ const r = _activeRequest.value;
50
+ if (!r) return;
51
+ if (r.kind === "confirm") {
52
+ r.resolve(true);
53
+ } else if (r.kind === "alert") {
54
+ r.resolve();
55
+ } else if (r.kind === "prompt") {
56
+ r.resolve(promptValue.value);
57
+ }
58
+ _resolveActive();
59
+ }
60
+ function handleCancel() {
61
+ const r = _activeRequest.value;
62
+ if (!r) return;
63
+ if (r.kind === "confirm") {
64
+ r.resolve(false);
65
+ } else if (r.kind === "prompt") {
66
+ r.resolve(null);
67
+ }
68
+ _resolveActive();
69
+ }
70
+ function handleUpdateOpen(v) {
71
+ if (!v) {
72
+ const r = _activeRequest.value;
73
+ if (!r) return;
74
+ if (r.kind === "confirm") {
75
+ r.resolve(false);
76
+ } else if (r.kind === "alert") {
77
+ r.resolve();
78
+ } else if (r.kind === "prompt") {
79
+ r.resolve(null);
80
+ } else if (r.kind === "custom") {
81
+ r.resolve(null);
82
+ }
83
+ _resolveActive();
84
+ }
85
+ }
86
+ function customOk(payload) {
87
+ const r = _activeRequest.value;
88
+ if (!r || r.kind !== "custom") return;
89
+ r.resolve(payload ?? null);
90
+ _resolveActive();
91
+ }
92
+ function customCancel() {
93
+ const r = _activeRequest.value;
94
+ if (!r || r.kind !== "custom") return;
95
+ r.resolve(null);
96
+ _resolveActive();
97
+ }
98
+ const pluginHandle = {
99
+ ok: customOk,
100
+ cancel: customCancel,
101
+ dismiss: customCancel
102
+ };
103
+ provide(DIALOG_PLUGIN_HANDLE_KEY, pluginHandle);
104
+ return (_ctx, _cache) => {
105
+ return req.value ? (openBlock(), createBlock(_sfc_main$1, {
106
+ key: 0,
107
+ "modal-id": MODAL_ID,
108
+ title: opts.value.title,
109
+ open: isOpen.value,
110
+ persistent: opts.value.persistent,
111
+ danger: opts.value.danger,
112
+ "onUpdate:open": handleUpdateOpen
113
+ }, createSlots({
114
+ default: withCtx(() => [
115
+ req.value.kind === "confirm" || req.value.kind === "alert" ? (openBlock(), createElementBlock("p", _hoisted_1, toDisplayString(opts.value.message), 1)) : req.value.kind === "prompt" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
116
+ createElementVNode("p", _hoisted_2, toDisplayString(opts.value.message), 1),
117
+ withDirectives(createElementVNode("input", {
118
+ ref_key: "promptInputRef",
119
+ ref: promptInputRef,
120
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => promptValue.value = $event),
121
+ class: "openpen-dialog-host-input",
122
+ placeholder: opts.value.placeholder,
123
+ type: "text",
124
+ onKeydown: [
125
+ withKeys(handleOk, ["enter"]),
126
+ withKeys(handleCancel, ["esc"])
127
+ ]
128
+ }, null, 40, _hoisted_3), [
129
+ [vModelText, promptValue.value]
130
+ ])
131
+ ], 64)) : req.value.kind === "custom" ? (openBlock(), createBlock(resolveDynamicComponent(opts.value.component), normalizeProps(mergeProps({ key: 2 }, opts.value.componentProps ?? {})), null, 16)) : createCommentVNode("", true)
132
+ ]),
133
+ _: 2
134
+ }, [
135
+ req.value.kind !== "custom" ? {
136
+ name: "footer",
137
+ fn: withCtx(() => [
138
+ req.value.kind === "confirm" || req.value.kind === "prompt" ? (openBlock(), createElementBlock("button", {
139
+ key: 0,
140
+ class: "openpen-btn openpen-btn-secondary",
141
+ onClick: handleCancel
142
+ }, toDisplayString(opts.value.cancelLabel ?? "Cancel"), 1)) : createCommentVNode("", true),
143
+ createElementVNode("button", {
144
+ class: normalizeClass(["openpen-btn openpen-btn-primary", { "openpen-btn-danger": opts.value.danger }]),
145
+ onClick: handleOk
146
+ }, toDisplayString(opts.value.okLabel ?? "OK"), 3)
147
+ ]),
148
+ key: "0"
149
+ } : void 0
150
+ ]), 1032, ["title", "open", "persistent", "danger"])) : createCommentVNode("", true);
151
+ };
152
+ }
153
+ });
154
+ export {
155
+ _sfc_main as DialogHost
156
+ };
157
+ //# sourceMappingURL=internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.js","sources":["../../src/uikit/components/DialogHost.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * DialogHost — private host-internal component.\n *\n * HOST-INTERNAL: Do not export from any public barrel. Plugin authors never\n * interact with this component directly. The host mounts exactly one instance\n * at bootstrap; it renders the currently active imperative dialog request from\n * the useDialog() store.\n *\n * Rendering strategy:\n * - Reads the active request from _activeRequest (module-scoped reactive ref).\n * - Renders an <AppDialog> wired to that request's options.\n * - On ok / cancel / dismiss: resolves the request's Promise and advances queue.\n *\n * Settings window constraint: AppDialog internally injects MODAL_MANAGER_KEY\n * and CONTROL_BAR_ANIMATING_KEY. In the settings window those keys are not\n * provided (ControlBar only exists in the overlay/main window), so the dialog\n * falls back gracefully (no mutual exclusion, no animating-guard) — it still\n * renders and resolves correctly, just without those host integrations.\n */\nimport { computed, inject, nextTick, provide, ref, watch } from 'vue'\nimport AppDialog from './AppDialog.vue'\n// _activeRequest MUST come from the package barrel — see AppPopover.vue header\n// comment. The runtime build externalises '@openpen/module-api/uikit' so host\n// + uikit share the same module instance. A relative '../composables/useDialog'\n// import bundles a duplicate ref into the host bundle, causing useDialog()\n// callers to write to a different ref than DialogHost watches (dialog never\n// opens in production).\nimport { _activeRequest, _resolveActive } from '@openpen/module-api/uikit'\nimport { HOST_DIALOG_OPEN_COUNT_KEY } from '@openpen/module-api'\nimport { DIALOG_PLUGIN_HANDLE_KEY } from '../composables/dialog-internal-keys'\nimport type { DialogPluginComponentHandle } from '../composables/useDialogPluginComponent'\n\nconst MODAL_ID = '__openpen_imperative_dialog__'\n\n// Increment/decrement the host-level dialog-open counter so ControlBar can\n// pause the auto-collapse timer while any imperative dialog is visible.\n// Falls back to a local ref when not provided (settings window has no host bar).\nconst dialogOpenCount = inject(HOST_DIALOG_OPEN_COUNT_KEY, ref(0))\n\n// Whether the dialog is open: true when there is an active request.\nconst isOpen = ref(false)\n\n// Prompt value — bound to the input in prompt dialogs.\nconst promptValue = ref('')\n\n// Track the prompt input element so we can focus it on open.\nconst promptInputRef = ref<HTMLInputElement | null>(null)\n\n// Watch the active request: open when a request arrives, close when cleared.\nwatch(_activeRequest, (req) => {\n if (req) {\n if (req.kind === 'prompt') {\n promptValue.value = req.opts.defaultValue ?? ''\n }\n if (!isOpen.value) {\n dialogOpenCount.value++\n }\n isOpen.value = true\n if (req.kind === 'prompt') {\n nextTick(() => {\n promptInputRef.value?.focus()\n })\n }\n } else {\n if (isOpen.value) {\n dialogOpenCount.value = Math.max(0, dialogOpenCount.value - 1)\n }\n isOpen.value = false\n }\n})\n\n// Derived from the active request for convenience.\nconst req = computed(() => _activeRequest.value)\nconst opts = computed(() => req.value?.opts)\n\nfunction handleOk() {\n const r = _activeRequest.value\n if (!r) return\n if (r.kind === 'confirm') {\n r.resolve(true)\n } else if (r.kind === 'alert') {\n r.resolve()\n } else if (r.kind === 'prompt') {\n r.resolve(promptValue.value)\n }\n // custom: custom component handles its own resolution via ok/cancel/dismiss\n _resolveActive()\n}\n\nfunction handleCancel() {\n const r = _activeRequest.value\n if (!r) return\n if (r.kind === 'confirm') {\n r.resolve(false)\n } else if (r.kind === 'prompt') {\n r.resolve(null)\n }\n _resolveActive()\n}\n\nfunction handleUpdateOpen(v: boolean) {\n if (!v) {\n // Dialog closed via ESC / close button (not persistent).\n const r = _activeRequest.value\n if (!r) return\n if (r.kind === 'confirm') {\n r.resolve(false)\n } else if (r.kind === 'alert') {\n r.resolve()\n } else if (r.kind === 'prompt') {\n r.resolve(null)\n } else if (r.kind === 'custom') {\n r.resolve(null)\n }\n _resolveActive()\n }\n}\n\n// ── Custom component plugin handle ───────────────────────────────────────────\n//\n// Provide the handle so custom components can call ok/cancel/dismiss via\n// useDialogPluginComponent(). We provide at this component level so only the\n// custom component subtree gets it.\n\nfunction customOk(payload?: unknown) {\n const r = _activeRequest.value\n if (!r || r.kind !== 'custom') return\n r.resolve(payload ?? null)\n _resolveActive()\n}\n\nfunction customCancel() {\n const r = _activeRequest.value\n if (!r || r.kind !== 'custom') return\n r.resolve(null)\n _resolveActive()\n}\n\nconst pluginHandle: DialogPluginComponentHandle<unknown> = {\n ok: customOk,\n cancel: customCancel,\n dismiss: customCancel,\n}\n\nprovide(DIALOG_PLUGIN_HANDLE_KEY, pluginHandle)\n</script>\n\n<template>\n <!-- DialogHost renders nothing when there is no active request. -->\n <AppDialog\n v-if=\"req\"\n :modal-id=\"MODAL_ID\"\n :title=\"opts!.title\"\n :open=\"isOpen\"\n :persistent=\"opts!.persistent\"\n :danger=\"opts!.danger\"\n @update:open=\"handleUpdateOpen\"\n >\n <!-- Body slot: varies by kind -->\n <template v-if=\"req.kind === 'confirm' || req.kind === 'alert'\">\n <p class=\"openpen-dialog-host-message\">{{ (opts as { message: string }).message }}</p>\n </template>\n\n <template v-else-if=\"req.kind === 'prompt'\">\n <p class=\"openpen-dialog-host-message\">{{ (opts as { message: string }).message }}</p>\n <input\n ref=\"promptInputRef\"\n v-model=\"promptValue\"\n class=\"openpen-dialog-host-input\"\n :placeholder=\"(opts as { placeholder?: string }).placeholder\"\n type=\"text\"\n @keydown.enter=\"handleOk\"\n @keydown.esc=\"handleCancel\"\n />\n </template>\n\n <template v-else-if=\"req.kind === 'custom'\">\n <!-- Render the custom component; it gets ok/cancel/dismiss via injection. -->\n <component\n :is=\"(opts as { component: unknown }).component\"\n v-bind=\"(opts as { componentProps?: Record<string, unknown> }).componentProps ?? {}\"\n />\n </template>\n\n <!-- Footer slot: only for confirm/alert/prompt; custom owns its own buttons. -->\n <template v-if=\"req.kind !== 'custom'\" #footer>\n <button\n v-if=\"req.kind === 'confirm' || req.kind === 'prompt'\"\n class=\"openpen-btn openpen-btn-secondary\"\n @click=\"handleCancel\"\n >\n {{ (opts as { cancelLabel?: string }).cancelLabel ?? 'Cancel' }}\n </button>\n <button\n class=\"openpen-btn openpen-btn-primary\"\n :class=\"{ 'openpen-btn-danger': opts!.danger }\"\n @click=\"handleOk\"\n >\n {{ (opts as { okLabel?: string }).okLabel ?? 'OK' }}\n </button>\n </template>\n </AppDialog>\n</template>\n\n<style>\n/* ── DialogHost utility styles ───────────────────────────────────────────── */\n.openpen-dialog-host-message {\n margin: 0 0 var(--openpen-space-md, 12px) 0;\n color: var(--openpen-color-text-primary, #fff);\n line-height: 1.5;\n}\n\n.openpen-dialog-host-input {\n display: block;\n width: 100%;\n box-sizing: border-box;\n padding: 6px 10px;\n border: 1px solid var(--openpen-color-border-hi, rgba(255,255,255,0.2));\n border-radius: var(--openpen-radius-sm, 6px);\n background: var(--openpen-color-control, rgba(255,255,255,0.08));\n color: var(--openpen-color-text-primary, #fff);\n font-size: 13px;\n outline: none;\n}\n\n.openpen-dialog-host-input:focus {\n border-color: var(--accent, #7c7cff);\n box-shadow: 0 0 0 2px var(--accent-bg, rgba(124,124,255,0.18));\n}\n\n/* ── DialogHost button styles ─────────────────────────────────────────────── */\n.openpen-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 6px 16px;\n border-radius: var(--openpen-radius-sm, 6px);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n border: none;\n transition: background var(--openpen-duration-fast, 120ms), color var(--openpen-duration-fast, 120ms);\n}\n\n.openpen-btn-secondary {\n background: var(--openpen-color-control, rgba(255,255,255,0.08));\n color: var(--openpen-color-text-primary, #fff);\n}\n\n.openpen-btn-secondary:hover {\n background: var(--openpen-color-control-hover, rgba(255,255,255,0.14));\n}\n\n.openpen-btn-primary {\n background: var(--accent, #7c7cff);\n color: #fff;\n}\n\n.openpen-btn-primary:hover {\n opacity: 0.9;\n}\n\n.openpen-btn-danger {\n background: var(--openpen-color-danger, #e05252);\n color: #fff;\n}\n\n.openpen-btn-danger:hover {\n opacity: 0.9;\n}\n</style>\n"],"names":["req","_createBlock","AppDialog","_openBlock","_createElementBlock","_toDisplayString","_Fragment","_createElementVNode","_resolveDynamicComponent","_normalizeProps","_mergeProps","_normalizeClass"],"mappings":"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"}