@openpen/module-api 1.1.0 → 1.2.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.
- package/CHANGELOG.md +43 -0
- package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-DBVFUpxc.js +245 -0
- package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-DBVFUpxc.js.map +1 -0
- package/dist/_chunks/inject-keys-CRXCpN-M.js +196 -0
- package/dist/_chunks/inject-keys-CRXCpN-M.js.map +1 -0
- package/dist/_chunks/module-context-registry-Cn1IWYu9.js +16 -0
- package/dist/_chunks/module-context-registry-Cn1IWYu9.js.map +1 -0
- package/dist/_chunks/passthrough-DKU7JYuQ.js +6 -0
- package/dist/_chunks/passthrough-DKU7JYuQ.js.map +1 -0
- package/dist/cursors.d.ts +83 -0
- package/dist/define-module.d.ts +17 -0
- package/dist/host/color-utils.d.ts +11 -0
- package/{src/host/event-bus.ts → dist/host/event-bus.d.ts} +7 -5
- package/dist/host/host-commands.d.ts +1 -0
- package/dist/host/index.d.ts +24 -0
- package/dist/host/index.js +49 -0
- package/dist/host/index.js.map +1 -0
- package/{src/host/module-context-registry.ts → dist/host/module-context-registry.d.ts} +5 -19
- package/dist/host/passthrough.d.ts +2 -0
- package/dist/host/popup-anchor.d.ts +3 -0
- package/dist/host/registry.d.ts +39 -0
- package/dist/host/registry.js +37 -0
- package/dist/host/registry.js.map +1 -0
- package/dist/host/slot-entries.d.ts +2 -0
- package/dist/host/stroke-store.d.ts +5 -0
- package/dist/host/stroke-style.d.ts +3 -0
- package/dist/host/types.d.ts +164 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +349 -0
- package/dist/index.js.map +1 -0
- package/{src/inject-keys.ts → dist/inject-keys.d.ts} +37 -66
- package/{src/locale.ts → dist/locale.d.ts} +3 -22
- package/dist/slots.d.ts +57 -0
- package/dist/types/contributions.d.ts +288 -0
- package/dist/types/control-bar-layout.d.ts +227 -0
- package/dist/types/module.d.ts +208 -0
- package/{src/types/notification.ts → dist/types/notification.d.ts} +12 -14
- package/dist/types/tool.d.ts +77 -0
- package/dist/uikit/components/AppBanner.vue.d.ts +34 -0
- package/dist/uikit/components/AppButton.vue.d.ts +30 -0
- package/dist/uikit/components/AppButtonDropdown.vue.d.ts +52 -0
- package/dist/uikit/components/AppDialog.vue.d.ts +53 -0
- package/dist/uikit/components/AppPopover.vue.d.ts +43 -0
- package/dist/uikit/components/AppSegmented.vue.d.ts +19 -0
- package/dist/uikit/components/AppSelect.vue.d.ts +26 -0
- package/dist/uikit/components/AppSlider.vue.d.ts +43 -0
- package/dist/uikit/components/AppTabs.vue.d.ts +35 -0
- package/dist/uikit/components/AppToggle.vue.d.ts +13 -0
- package/dist/uikit/components/AppTooltip.vue.d.ts +27 -0
- package/dist/uikit/components/DialogHost.vue.d.ts +3 -0
- package/{src/uikit/composables/dialog-internal-keys.ts → dist/uikit/composables/dialog-internal-keys.d.ts} +3 -5
- package/dist/uikit/composables/useDialog.d.ts +399 -0
- package/dist/uikit/composables/useDialogPluginComponent.d.ts +9 -0
- package/dist/uikit/index.d.ts +42 -0
- package/dist/uikit/index.js +884 -0
- package/dist/uikit/index.js.map +1 -0
- package/{src/uikit/internal.ts → dist/uikit/internal.d.ts} +1 -1
- package/dist/uikit/internal.js +157 -0
- package/dist/uikit/internal.js.map +1 -0
- package/dist/uikit/module-api.css +851 -0
- package/dist/uikit/primitives.d.ts +42 -0
- package/{src/use-module-context.ts → dist/use-module-context.d.ts} +2 -15
- package/{src/validation.ts → dist/validation.d.ts} +2 -6
- package/package.json +26 -12
- package/packages/module-api/CHANGELOG.md +0 -27
- package/src/cursors.ts +0 -369
- package/src/define-module.ts +0 -55
- package/src/host/color-utils.ts +0 -18
- package/src/host/host-commands.ts +0 -9
- package/src/host/index.ts +0 -53
- package/src/host/passthrough.ts +0 -10
- package/src/host/popup-anchor.ts +0 -14
- package/src/host/slot-entries.ts +0 -9
- package/src/host/stroke-store.ts +0 -20
- package/src/host/stroke-style.ts +0 -15
- package/src/index.ts +0 -131
- package/src/slots.ts +0 -135
- package/src/types/contributions.ts +0 -333
- package/src/types/control-bar-layout.ts +0 -174
- package/src/types/module.ts +0 -225
- package/src/types/tool.ts +0 -82
- package/src/uikit/components/.gitkeep +0 -0
- package/src/uikit/components/AppBanner.vue +0 -131
- package/src/uikit/components/AppButton.vue +0 -135
- package/src/uikit/components/AppDialog.vue +0 -393
- package/src/uikit/components/AppPopover.vue +0 -268
- package/src/uikit/components/AppSegmented.vue +0 -128
- package/src/uikit/components/AppSelect.vue +0 -235
- package/src/uikit/components/AppSlider.vue +0 -204
- package/src/uikit/components/AppTabs.vue +0 -163
- package/src/uikit/components/AppToggle.vue +0 -96
- package/src/uikit/components/AppTooltip.vue +0 -118
- package/src/uikit/components/DialogHost.vue +0 -272
- package/src/uikit/composables/useDialog.ts +0 -141
- package/src/uikit/composables/useDialogPluginComponent.ts +0 -27
- package/src/uikit/index.ts +0 -79
- package/src/uikit/primitives.ts +0 -150
- /package/{src → dist}/uikit/tokens.css +0 -0
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* AppTabs — High-level wrapper (Layer 1).
|
|
4
|
-
*
|
|
5
|
-
* Wraps Reka UI Tabs (TabsRoot / TabsList / TabsTrigger / TabsContent).
|
|
6
|
-
* Intended for settings UI tabbed navigation and any plugin tab panel.
|
|
7
|
-
*
|
|
8
|
-
* Plugin authors get a controlled tab switcher with OpenPen styling without
|
|
9
|
-
* knowing about Reka UI internals.
|
|
10
|
-
*
|
|
11
|
-
* Styles use --openpen-* design tokens.
|
|
12
|
-
* No Reka UI types are leaked in the public API.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* <AppTabs
|
|
16
|
-
* v-model="activeTab"
|
|
17
|
-
* :tabs="[{ id: 'general', label: 'General' }, { id: 'advanced', label: 'Advanced' }]"
|
|
18
|
-
* >
|
|
19
|
-
* <template #default="{ activeTabId }">
|
|
20
|
-
* <div v-if="activeTabId === 'general'">...</div>
|
|
21
|
-
* <div v-if="activeTabId === 'advanced'">...</div>
|
|
22
|
-
* </template>
|
|
23
|
-
* </AppTabs>
|
|
24
|
-
*/
|
|
25
|
-
import {
|
|
26
|
-
TabsRoot,
|
|
27
|
-
TabsList,
|
|
28
|
-
TabsTrigger,
|
|
29
|
-
TabsContent,
|
|
30
|
-
} from '../primitives'
|
|
31
|
-
|
|
32
|
-
// ── OpenPen-owned types (MUST NOT reference reka-ui types) ────────────────────
|
|
33
|
-
|
|
34
|
-
export interface AppTabItem {
|
|
35
|
-
/** Unique identifier for this tab. Used as the v-model value. */
|
|
36
|
-
id: string
|
|
37
|
-
/** Human-readable label rendered in the tab trigger button. */
|
|
38
|
-
label: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface AppTabsProps {
|
|
42
|
-
/** Currently active tab id (controlled mode). */
|
|
43
|
-
modelValue: string
|
|
44
|
-
/** Ordered list of tab descriptors. */
|
|
45
|
-
tabs: AppTabItem[]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface AppTabsSlots {
|
|
49
|
-
/**
|
|
50
|
-
* Default slot for tab content area.
|
|
51
|
-
* Receives the currently active tab id via scoped slot.
|
|
52
|
-
*/
|
|
53
|
-
default: (scope: { activeTabId: string }) => unknown
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const props = defineProps<AppTabsProps>()
|
|
57
|
-
|
|
58
|
-
const emit = defineEmits<{
|
|
59
|
-
'update:modelValue': [value: string]
|
|
60
|
-
}>()
|
|
61
|
-
|
|
62
|
-
defineSlots<AppTabsSlots>()
|
|
63
|
-
|
|
64
|
-
function onTabChange(val: string | number | undefined) {
|
|
65
|
-
if (typeof val === 'string') {
|
|
66
|
-
emit('update:modelValue', val)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
</script>
|
|
70
|
-
|
|
71
|
-
<template>
|
|
72
|
-
<TabsRoot
|
|
73
|
-
:model-value="props.modelValue"
|
|
74
|
-
class="openpen-tabs"
|
|
75
|
-
@update:model-value="onTabChange"
|
|
76
|
-
>
|
|
77
|
-
<TabsList class="openpen-tabs-list">
|
|
78
|
-
<TabsTrigger
|
|
79
|
-
v-for="tab in props.tabs"
|
|
80
|
-
:key="tab.id"
|
|
81
|
-
:value="tab.id"
|
|
82
|
-
class="openpen-tabs-trigger"
|
|
83
|
-
>
|
|
84
|
-
{{ tab.label }}
|
|
85
|
-
</TabsTrigger>
|
|
86
|
-
</TabsList>
|
|
87
|
-
|
|
88
|
-
<!-- Single content outlet: plugin author renders tab content via scoped slot. -->
|
|
89
|
-
<TabsContent
|
|
90
|
-
v-for="tab in props.tabs"
|
|
91
|
-
:key="tab.id"
|
|
92
|
-
:value="tab.id"
|
|
93
|
-
class="openpen-tabs-content"
|
|
94
|
-
>
|
|
95
|
-
<slot :active-tab-id="tab.id" />
|
|
96
|
-
</TabsContent>
|
|
97
|
-
</TabsRoot>
|
|
98
|
-
</template>
|
|
99
|
-
|
|
100
|
-
<style scoped>
|
|
101
|
-
/* ── Tab root ───────────────────────────────────────────────────────────────── */
|
|
102
|
-
.openpen-tabs {
|
|
103
|
-
display: flex;
|
|
104
|
-
flex-direction: column;
|
|
105
|
-
gap: var(--openpen-space-sm);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* ── Tab list (trigger bar) ─────────────────────────────────────────────────── */
|
|
109
|
-
.openpen-tabs-list {
|
|
110
|
-
display: flex;
|
|
111
|
-
flex-direction: row;
|
|
112
|
-
gap: 2px;
|
|
113
|
-
padding: 3px;
|
|
114
|
-
background: var(--openpen-color-control-group);
|
|
115
|
-
border: 1px solid var(--openpen-color-border);
|
|
116
|
-
border-radius: var(--openpen-radius-md);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* ── Individual trigger ─────────────────────────────────────────────────────── */
|
|
120
|
-
.openpen-tabs-trigger {
|
|
121
|
-
flex: 1;
|
|
122
|
-
padding: var(--openpen-space-xs) var(--openpen-space-sm);
|
|
123
|
-
border: none;
|
|
124
|
-
border-radius: calc(var(--openpen-radius-md) - 3px);
|
|
125
|
-
background: transparent;
|
|
126
|
-
color: var(--openpen-color-text-dim);
|
|
127
|
-
font-size: 13px;
|
|
128
|
-
font-weight: 500;
|
|
129
|
-
cursor: pointer;
|
|
130
|
-
transition:
|
|
131
|
-
background var(--openpen-duration-fast) var(--openpen-easing-standard),
|
|
132
|
-
color var(--openpen-duration-fast) var(--openpen-easing-standard);
|
|
133
|
-
white-space: nowrap;
|
|
134
|
-
/* Remove default button outline; focus-visible ring below handles a11y. */
|
|
135
|
-
outline: none;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.openpen-tabs-trigger:hover {
|
|
139
|
-
background: var(--openpen-color-control-hover);
|
|
140
|
-
color: var(--openpen-color-text-primary);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.openpen-tabs-trigger[data-state='active'] {
|
|
144
|
-
background: var(--openpen-color-accent-bg);
|
|
145
|
-
color: var(--openpen-color-accent);
|
|
146
|
-
box-shadow: 0 0 0 1px var(--openpen-color-accent-glow);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.openpen-tabs-trigger:focus-visible {
|
|
150
|
-
box-shadow: 0 0 0 2px var(--openpen-color-accent);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/* ── Tab content ────────────────────────────────────────────────────────────── */
|
|
154
|
-
.openpen-tabs-content {
|
|
155
|
-
/* Content area has no chrome by default; plugin authors add their own. */
|
|
156
|
-
outline: none;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.openpen-tabs-content:focus-visible {
|
|
160
|
-
box-shadow: 0 0 0 2px var(--openpen-color-accent);
|
|
161
|
-
border-radius: var(--openpen-radius-sm);
|
|
162
|
-
}
|
|
163
|
-
</style>
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* AppToggle — High-level wrapper (Layer 1).
|
|
4
|
-
*
|
|
5
|
-
* Wraps Reka UI Switch (SwitchRoot / SwitchThumb).
|
|
6
|
-
*
|
|
7
|
-
* Visual: 40×24px, off=toggle-off-bg, on=accent,
|
|
8
|
-
* 18×18 white thumb, bounce transition.
|
|
9
|
-
*
|
|
10
|
-
* Styles use --openpen-* design tokens.
|
|
11
|
-
* No Reka UI types are leaked in the public API.
|
|
12
|
-
*
|
|
13
|
-
* DOM notes:
|
|
14
|
-
* - SwitchRoot renders a <button role="switch"> with class "app-toggle"
|
|
15
|
-
* - "on" class is added when modelValue is true
|
|
16
|
-
* - aria-pressed is added explicitly because Reka UI's SwitchRoot uses
|
|
17
|
-
* aria-checked; both attributes co-exist for maximum a11y surface.
|
|
18
|
-
*/
|
|
19
|
-
import {
|
|
20
|
-
SwitchRoot,
|
|
21
|
-
SwitchThumb,
|
|
22
|
-
} from '../primitives'
|
|
23
|
-
|
|
24
|
-
// ── Public API (MUST NOT reference reka-ui types) ─────────────────────────────
|
|
25
|
-
|
|
26
|
-
withDefaults(defineProps<{
|
|
27
|
-
modelValue: boolean
|
|
28
|
-
ariaLabel?: string
|
|
29
|
-
}>(), { ariaLabel: '' })
|
|
30
|
-
|
|
31
|
-
const emit = defineEmits<{
|
|
32
|
-
'update:modelValue': [value: boolean]
|
|
33
|
-
}>()
|
|
34
|
-
|
|
35
|
-
function onCheckedChange(val: boolean) {
|
|
36
|
-
emit('update:modelValue', val)
|
|
37
|
-
}
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<template>
|
|
41
|
-
<SwitchRoot
|
|
42
|
-
class="app-toggle"
|
|
43
|
-
:class="{ on: modelValue }"
|
|
44
|
-
:model-value="modelValue"
|
|
45
|
-
:aria-label="ariaLabel"
|
|
46
|
-
:aria-pressed="modelValue"
|
|
47
|
-
@update:model-value="onCheckedChange"
|
|
48
|
-
>
|
|
49
|
-
<SwitchThumb class="app-toggle-thumb" />
|
|
50
|
-
</SwitchRoot>
|
|
51
|
-
</template>
|
|
52
|
-
|
|
53
|
-
<style scoped>
|
|
54
|
-
/* Root — the toggle track */
|
|
55
|
-
.app-toggle {
|
|
56
|
-
width: 40px;
|
|
57
|
-
height: 24px;
|
|
58
|
-
border-radius: 12px;
|
|
59
|
-
border: none;
|
|
60
|
-
cursor: pointer;
|
|
61
|
-
position: relative;
|
|
62
|
-
flex-shrink: 0;
|
|
63
|
-
background: var(--openpen-color-toggle-off);
|
|
64
|
-
transition: background var(--openpen-duration-base) var(--openpen-easing-standard);
|
|
65
|
-
/* Remove default button styles */
|
|
66
|
-
padding: 0;
|
|
67
|
-
appearance: none;
|
|
68
|
-
-webkit-appearance: none;
|
|
69
|
-
display: inline-flex;
|
|
70
|
-
align-items: center;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.app-toggle.on {
|
|
74
|
-
background: var(--openpen-color-accent);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/* Thumb — the white circular knob */
|
|
78
|
-
.app-toggle-thumb {
|
|
79
|
-
position: absolute;
|
|
80
|
-
top: 3px;
|
|
81
|
-
left: 3px;
|
|
82
|
-
width: 18px;
|
|
83
|
-
height: 18px;
|
|
84
|
-
border-radius: 50%;
|
|
85
|
-
background: #fff;
|
|
86
|
-
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
|
87
|
-
transition: left var(--openpen-duration-bounce) var(--openpen-easing-bounce);
|
|
88
|
-
display: block;
|
|
89
|
-
pointer-events: none;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.app-toggle.on .app-toggle-thumb,
|
|
93
|
-
.app-toggle[data-state='checked'] .app-toggle-thumb {
|
|
94
|
-
left: 19px;
|
|
95
|
-
}
|
|
96
|
-
</style>
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* AppTooltip — High-level wrapper (Layer 1).
|
|
4
|
-
*
|
|
5
|
-
* Wraps Reka UI Tooltip (TooltipProvider / TooltipRoot / TooltipTrigger /
|
|
6
|
-
* TooltipPortal / TooltipContent / TooltipArrow).
|
|
7
|
-
*
|
|
8
|
-
* Plugin authors get hover-triggered tooltips with OpenPen styling without
|
|
9
|
-
* knowing about Reka UI internals or inject keys.
|
|
10
|
-
*
|
|
11
|
-
* Styles use --openpen-* design tokens.
|
|
12
|
-
* No Reka UI types are leaked in the public API.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* <AppTooltip content="Save file" side="top">
|
|
16
|
-
* <button>Save</button>
|
|
17
|
-
* </AppTooltip>
|
|
18
|
-
*/
|
|
19
|
-
import {
|
|
20
|
-
TooltipProvider,
|
|
21
|
-
TooltipRoot,
|
|
22
|
-
TooltipTrigger,
|
|
23
|
-
TooltipPortal,
|
|
24
|
-
TooltipContent,
|
|
25
|
-
TooltipArrow,
|
|
26
|
-
} from '../primitives'
|
|
27
|
-
|
|
28
|
-
// ── OpenPen-owned types (MUST NOT reference reka-ui types) ────────────────────
|
|
29
|
-
|
|
30
|
-
/** Preferred side for tooltip content relative to the trigger. */
|
|
31
|
-
export type TooltipSide = 'top' | 'bottom' | 'left' | 'right'
|
|
32
|
-
|
|
33
|
-
export interface AppTooltipProps {
|
|
34
|
-
/** Tooltip text content. */
|
|
35
|
-
content: string
|
|
36
|
-
/** Preferred placement side relative to the trigger. Default 'top'. */
|
|
37
|
-
side?: TooltipSide
|
|
38
|
-
/** Delay before tooltip appears on hover, in ms. Default 200. */
|
|
39
|
-
delay?: number
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface AppTooltipSlots {
|
|
43
|
-
/** Trigger element. Any element that receives hover events. */
|
|
44
|
-
default: () => unknown
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const props = withDefaults(defineProps<AppTooltipProps>(), {
|
|
48
|
-
side: 'top',
|
|
49
|
-
delay: 200,
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
defineSlots<AppTooltipSlots>()
|
|
53
|
-
</script>
|
|
54
|
-
|
|
55
|
-
<template>
|
|
56
|
-
<TooltipProvider :delay-duration="props.delay">
|
|
57
|
-
<TooltipRoot>
|
|
58
|
-
<TooltipTrigger as-child>
|
|
59
|
-
<slot />
|
|
60
|
-
</TooltipTrigger>
|
|
61
|
-
<TooltipPortal>
|
|
62
|
-
<TooltipContent
|
|
63
|
-
:side="props.side"
|
|
64
|
-
:side-offset="6"
|
|
65
|
-
:avoid-collisions="true"
|
|
66
|
-
:collision-padding="8"
|
|
67
|
-
class="openpen-tooltip-content"
|
|
68
|
-
>
|
|
69
|
-
{{ props.content }}
|
|
70
|
-
<TooltipArrow class="openpen-tooltip-arrow" :width="10" :height="5" />
|
|
71
|
-
</TooltipContent>
|
|
72
|
-
</TooltipPortal>
|
|
73
|
-
</TooltipRoot>
|
|
74
|
-
</TooltipProvider>
|
|
75
|
-
</template>
|
|
76
|
-
|
|
77
|
-
<!-- Styles for teleported TooltipContent MUST be unscoped — Vue scoped CSS
|
|
78
|
-
does not follow Teleport targets (same rationale as AppPopover). -->
|
|
79
|
-
<style>
|
|
80
|
-
.openpen-tooltip-content {
|
|
81
|
-
background: var(--openpen-color-tooltip-bg);
|
|
82
|
-
border: 1px solid var(--openpen-color-border-hi);
|
|
83
|
-
border-radius: var(--openpen-radius-sm);
|
|
84
|
-
padding: 5px 9px;
|
|
85
|
-
color: var(--openpen-color-tooltip-text);
|
|
86
|
-
font-size: 12px;
|
|
87
|
-
line-height: 1.4;
|
|
88
|
-
/* Prevent text wrap for short tooltip labels */
|
|
89
|
-
white-space: nowrap;
|
|
90
|
-
box-shadow: var(--openpen-shadow-sm);
|
|
91
|
-
z-index: 200;
|
|
92
|
-
pointer-events: none;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.openpen-tooltip-arrow {
|
|
96
|
-
fill: var(--openpen-color-tooltip-bg);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/* ── Open / close animations ────────────────────────────────────────────────
|
|
100
|
-
Reka UI Presence keeps the element in the DOM until the CSS animation ends,
|
|
101
|
-
so data-state="delayed-open" / "instant-open" / "closed" all animate. */
|
|
102
|
-
.openpen-tooltip-content[data-state='delayed-open'],
|
|
103
|
-
.openpen-tooltip-content[data-state='instant-open'] {
|
|
104
|
-
animation: openpen-tooltip-in var(--openpen-duration-fast) var(--openpen-easing-standard);
|
|
105
|
-
}
|
|
106
|
-
.openpen-tooltip-content[data-state='closed'] {
|
|
107
|
-
animation: openpen-tooltip-out var(--openpen-duration-fast) ease forwards;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
@keyframes openpen-tooltip-in {
|
|
111
|
-
from { opacity: 0; transform: scale(0.95); }
|
|
112
|
-
to { opacity: 1; transform: scale(1); }
|
|
113
|
-
}
|
|
114
|
-
@keyframes openpen-tooltip-out {
|
|
115
|
-
from { opacity: 1; transform: scale(1); }
|
|
116
|
-
to { opacity: 0; transform: scale(0.95); }
|
|
117
|
-
}
|
|
118
|
-
</style>
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* DialogHost — private host-internal component.
|
|
4
|
-
*
|
|
5
|
-
* HOST-INTERNAL: Do not export from any public barrel. Plugin authors never
|
|
6
|
-
* interact with this component directly. The host mounts exactly one instance
|
|
7
|
-
* at bootstrap; it renders the currently active imperative dialog request from
|
|
8
|
-
* the useDialog() store.
|
|
9
|
-
*
|
|
10
|
-
* Rendering strategy:
|
|
11
|
-
* - Reads the active request from _activeRequest (module-scoped reactive ref).
|
|
12
|
-
* - Renders an <AppDialog> wired to that request's options.
|
|
13
|
-
* - On ok / cancel / dismiss: resolves the request's Promise and advances queue.
|
|
14
|
-
*
|
|
15
|
-
* Settings window constraint: AppDialog internally injects MODAL_MANAGER_KEY
|
|
16
|
-
* and CONTROL_BAR_ANIMATING_KEY. In the settings window those keys are not
|
|
17
|
-
* provided (ControlBar only exists in the overlay/main window), so the dialog
|
|
18
|
-
* falls back gracefully (no mutual exclusion, no animating-guard) — it still
|
|
19
|
-
* renders and resolves correctly, just without those host integrations.
|
|
20
|
-
*/
|
|
21
|
-
import { computed, inject, nextTick, provide, ref, watch } from 'vue'
|
|
22
|
-
import AppDialog from './AppDialog.vue'
|
|
23
|
-
// _activeRequest MUST come from the package barrel — see AppPopover.vue header
|
|
24
|
-
// comment. The runtime build externalises '@openpen/module-api/uikit' so host
|
|
25
|
-
// + uikit share the same module instance. A relative '../composables/useDialog'
|
|
26
|
-
// import bundles a duplicate ref into the host bundle, causing useDialog()
|
|
27
|
-
// callers to write to a different ref than DialogHost watches (dialog never
|
|
28
|
-
// opens in production).
|
|
29
|
-
import { _activeRequest, _resolveActive } from '@openpen/module-api/uikit'
|
|
30
|
-
import { HOST_DIALOG_OPEN_COUNT_KEY } from '@openpen/module-api'
|
|
31
|
-
import { DIALOG_PLUGIN_HANDLE_KEY } from '../composables/dialog-internal-keys'
|
|
32
|
-
import type { DialogPluginComponentHandle } from '../composables/useDialogPluginComponent'
|
|
33
|
-
|
|
34
|
-
const MODAL_ID = '__openpen_imperative_dialog__'
|
|
35
|
-
|
|
36
|
-
// Increment/decrement the host-level dialog-open counter so ControlBar can
|
|
37
|
-
// pause the auto-collapse timer while any imperative dialog is visible.
|
|
38
|
-
// Falls back to a local ref when not provided (settings window has no host bar).
|
|
39
|
-
const dialogOpenCount = inject(HOST_DIALOG_OPEN_COUNT_KEY, ref(0))
|
|
40
|
-
|
|
41
|
-
// Whether the dialog is open: true when there is an active request.
|
|
42
|
-
const isOpen = ref(false)
|
|
43
|
-
|
|
44
|
-
// Prompt value — bound to the input in prompt dialogs.
|
|
45
|
-
const promptValue = ref('')
|
|
46
|
-
|
|
47
|
-
// Track the prompt input element so we can focus it on open.
|
|
48
|
-
const promptInputRef = ref<HTMLInputElement | null>(null)
|
|
49
|
-
|
|
50
|
-
// Watch the active request: open when a request arrives, close when cleared.
|
|
51
|
-
watch(_activeRequest, (req) => {
|
|
52
|
-
if (req) {
|
|
53
|
-
if (req.kind === 'prompt') {
|
|
54
|
-
promptValue.value = req.opts.defaultValue ?? ''
|
|
55
|
-
}
|
|
56
|
-
if (!isOpen.value) {
|
|
57
|
-
dialogOpenCount.value++
|
|
58
|
-
}
|
|
59
|
-
isOpen.value = true
|
|
60
|
-
if (req.kind === 'prompt') {
|
|
61
|
-
nextTick(() => {
|
|
62
|
-
promptInputRef.value?.focus()
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
if (isOpen.value) {
|
|
67
|
-
dialogOpenCount.value = Math.max(0, dialogOpenCount.value - 1)
|
|
68
|
-
}
|
|
69
|
-
isOpen.value = false
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// Derived from the active request for convenience.
|
|
74
|
-
const req = computed(() => _activeRequest.value)
|
|
75
|
-
const opts = computed(() => req.value?.opts)
|
|
76
|
-
|
|
77
|
-
function handleOk() {
|
|
78
|
-
const r = _activeRequest.value
|
|
79
|
-
if (!r) return
|
|
80
|
-
if (r.kind === 'confirm') {
|
|
81
|
-
r.resolve(true)
|
|
82
|
-
} else if (r.kind === 'alert') {
|
|
83
|
-
r.resolve()
|
|
84
|
-
} else if (r.kind === 'prompt') {
|
|
85
|
-
r.resolve(promptValue.value)
|
|
86
|
-
}
|
|
87
|
-
// custom: custom component handles its own resolution via ok/cancel/dismiss
|
|
88
|
-
_resolveActive()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function handleCancel() {
|
|
92
|
-
const r = _activeRequest.value
|
|
93
|
-
if (!r) return
|
|
94
|
-
if (r.kind === 'confirm') {
|
|
95
|
-
r.resolve(false)
|
|
96
|
-
} else if (r.kind === 'prompt') {
|
|
97
|
-
r.resolve(null)
|
|
98
|
-
}
|
|
99
|
-
_resolveActive()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function handleUpdateOpen(v: boolean) {
|
|
103
|
-
if (!v) {
|
|
104
|
-
// Dialog closed via ESC / close button (not persistent).
|
|
105
|
-
const r = _activeRequest.value
|
|
106
|
-
if (!r) return
|
|
107
|
-
if (r.kind === 'confirm') {
|
|
108
|
-
r.resolve(false)
|
|
109
|
-
} else if (r.kind === 'alert') {
|
|
110
|
-
r.resolve()
|
|
111
|
-
} else if (r.kind === 'prompt') {
|
|
112
|
-
r.resolve(null)
|
|
113
|
-
} else if (r.kind === 'custom') {
|
|
114
|
-
r.resolve(null)
|
|
115
|
-
}
|
|
116
|
-
_resolveActive()
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ── Custom component plugin handle ───────────────────────────────────────────
|
|
121
|
-
//
|
|
122
|
-
// Provide the handle so custom components can call ok/cancel/dismiss via
|
|
123
|
-
// useDialogPluginComponent(). We provide at this component level so only the
|
|
124
|
-
// custom component subtree gets it.
|
|
125
|
-
|
|
126
|
-
function customOk(payload?: unknown) {
|
|
127
|
-
const r = _activeRequest.value
|
|
128
|
-
if (!r || r.kind !== 'custom') return
|
|
129
|
-
r.resolve(payload ?? null)
|
|
130
|
-
_resolveActive()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function customCancel() {
|
|
134
|
-
const r = _activeRequest.value
|
|
135
|
-
if (!r || r.kind !== 'custom') return
|
|
136
|
-
r.resolve(null)
|
|
137
|
-
_resolveActive()
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const pluginHandle: DialogPluginComponentHandle<unknown> = {
|
|
141
|
-
ok: customOk,
|
|
142
|
-
cancel: customCancel,
|
|
143
|
-
dismiss: customCancel,
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
provide(DIALOG_PLUGIN_HANDLE_KEY, pluginHandle)
|
|
147
|
-
</script>
|
|
148
|
-
|
|
149
|
-
<template>
|
|
150
|
-
<!-- DialogHost renders nothing when there is no active request. -->
|
|
151
|
-
<AppDialog
|
|
152
|
-
v-if="req"
|
|
153
|
-
:modal-id="MODAL_ID"
|
|
154
|
-
:title="opts!.title"
|
|
155
|
-
:open="isOpen"
|
|
156
|
-
:persistent="opts!.persistent"
|
|
157
|
-
:danger="opts!.danger"
|
|
158
|
-
@update:open="handleUpdateOpen"
|
|
159
|
-
>
|
|
160
|
-
<!-- Body slot: varies by kind -->
|
|
161
|
-
<template v-if="req.kind === 'confirm' || req.kind === 'alert'">
|
|
162
|
-
<p class="openpen-dialog-host-message">{{ (opts as { message: string }).message }}</p>
|
|
163
|
-
</template>
|
|
164
|
-
|
|
165
|
-
<template v-else-if="req.kind === 'prompt'">
|
|
166
|
-
<p class="openpen-dialog-host-message">{{ (opts as { message: string }).message }}</p>
|
|
167
|
-
<input
|
|
168
|
-
ref="promptInputRef"
|
|
169
|
-
v-model="promptValue"
|
|
170
|
-
class="openpen-dialog-host-input"
|
|
171
|
-
:placeholder="(opts as { placeholder?: string }).placeholder"
|
|
172
|
-
type="text"
|
|
173
|
-
@keydown.enter="handleOk"
|
|
174
|
-
@keydown.esc="handleCancel"
|
|
175
|
-
/>
|
|
176
|
-
</template>
|
|
177
|
-
|
|
178
|
-
<template v-else-if="req.kind === 'custom'">
|
|
179
|
-
<!-- Render the custom component; it gets ok/cancel/dismiss via injection. -->
|
|
180
|
-
<component
|
|
181
|
-
:is="(opts as { component: unknown }).component"
|
|
182
|
-
v-bind="(opts as { componentProps?: Record<string, unknown> }).componentProps ?? {}"
|
|
183
|
-
/>
|
|
184
|
-
</template>
|
|
185
|
-
|
|
186
|
-
<!-- Footer slot: only for confirm/alert/prompt; custom owns its own buttons. -->
|
|
187
|
-
<template v-if="req.kind !== 'custom'" #footer>
|
|
188
|
-
<button
|
|
189
|
-
v-if="req.kind === 'confirm' || req.kind === 'prompt'"
|
|
190
|
-
class="openpen-btn openpen-btn-secondary"
|
|
191
|
-
@click="handleCancel"
|
|
192
|
-
>
|
|
193
|
-
{{ (opts as { cancelLabel?: string }).cancelLabel ?? 'Cancel' }}
|
|
194
|
-
</button>
|
|
195
|
-
<button
|
|
196
|
-
class="openpen-btn openpen-btn-primary"
|
|
197
|
-
:class="{ 'openpen-btn-danger': opts!.danger }"
|
|
198
|
-
@click="handleOk"
|
|
199
|
-
>
|
|
200
|
-
{{ (opts as { okLabel?: string }).okLabel ?? 'OK' }}
|
|
201
|
-
</button>
|
|
202
|
-
</template>
|
|
203
|
-
</AppDialog>
|
|
204
|
-
</template>
|
|
205
|
-
|
|
206
|
-
<style>
|
|
207
|
-
/* ── DialogHost utility styles ───────────────────────────────────────────── */
|
|
208
|
-
.openpen-dialog-host-message {
|
|
209
|
-
margin: 0 0 var(--openpen-space-md, 12px) 0;
|
|
210
|
-
color: var(--openpen-color-text-primary, #fff);
|
|
211
|
-
line-height: 1.5;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.openpen-dialog-host-input {
|
|
215
|
-
display: block;
|
|
216
|
-
width: 100%;
|
|
217
|
-
box-sizing: border-box;
|
|
218
|
-
padding: 6px 10px;
|
|
219
|
-
border: 1px solid var(--openpen-color-border-hi, rgba(255,255,255,0.2));
|
|
220
|
-
border-radius: var(--openpen-radius-sm, 6px);
|
|
221
|
-
background: var(--openpen-color-control, rgba(255,255,255,0.08));
|
|
222
|
-
color: var(--openpen-color-text-primary, #fff);
|
|
223
|
-
font-size: 13px;
|
|
224
|
-
outline: none;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.openpen-dialog-host-input:focus {
|
|
228
|
-
border-color: var(--accent, #7c7cff);
|
|
229
|
-
box-shadow: 0 0 0 2px var(--accent-bg, rgba(124,124,255,0.18));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/* ── DialogHost button styles ─────────────────────────────────────────────── */
|
|
233
|
-
.openpen-btn {
|
|
234
|
-
display: inline-flex;
|
|
235
|
-
align-items: center;
|
|
236
|
-
justify-content: center;
|
|
237
|
-
padding: 6px 16px;
|
|
238
|
-
border-radius: var(--openpen-radius-sm, 6px);
|
|
239
|
-
font-size: 13px;
|
|
240
|
-
font-weight: 500;
|
|
241
|
-
cursor: pointer;
|
|
242
|
-
border: none;
|
|
243
|
-
transition: background var(--openpen-duration-fast, 120ms), color var(--openpen-duration-fast, 120ms);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.openpen-btn-secondary {
|
|
247
|
-
background: var(--openpen-color-control, rgba(255,255,255,0.08));
|
|
248
|
-
color: var(--openpen-color-text-primary, #fff);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.openpen-btn-secondary:hover {
|
|
252
|
-
background: var(--openpen-color-control-hover, rgba(255,255,255,0.14));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.openpen-btn-primary {
|
|
256
|
-
background: var(--accent, #7c7cff);
|
|
257
|
-
color: #fff;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.openpen-btn-primary:hover {
|
|
261
|
-
opacity: 0.9;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.openpen-btn-danger {
|
|
265
|
-
background: var(--openpen-color-danger, #e05252);
|
|
266
|
-
color: #fff;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.openpen-btn-danger:hover {
|
|
270
|
-
opacity: 0.9;
|
|
271
|
-
}
|
|
272
|
-
</style>
|