@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
@@ -1,174 +0,0 @@
1
- import { z } from 'zod'
2
- import type { Component } from 'vue'
3
- import type { LocaleMap } from '../locale'
4
-
5
- // ── Schema ────────────────────────────────────────────────────────────────
6
-
7
- export const GroupInsetSchema = z.object({
8
- enabled: z.boolean(),
9
- /** Optional CSS color override for the inset background; defaults to --openpen-color-control-group. */
10
- color: z.string().optional(),
11
- })
12
-
13
- export const LayoutGroupSchema = z.object({
14
- id: z.string().regex(/^[a-z][a-z0-9-]*$/, 'Group id must be kebab-case'),
15
- items: z.array(z.string()),
16
- separator: z.enum(['auto', 'always', 'never']).optional(),
17
- /**
18
- * Optional inset rendering for this group. When `enabled: true`, the group
19
- * MUST render with a visible bg + border + padding container (the design's
20
- * "grouped tools" look) AND its inner items MUST shrink so the group's
21
- * outer height matches an unwrapped 36px button — i.e. enabling inset
22
- * MUST NOT increase control-bar height.
23
- */
24
- inset: GroupInsetSchema.optional(),
25
- })
26
-
27
- export const ControlBarLayoutSchema = z.object({
28
- version: z.literal(1),
29
- groups: z
30
- .array(LayoutGroupSchema)
31
- .refine((groups) => groups.some((g) => g.id === 'default'), {
32
- message: "Layout must contain a 'default' group",
33
- })
34
- .refine(
35
- (groups) => {
36
- const ids = groups.map((g) => g.id)
37
- return new Set(ids).size === ids.length
38
- },
39
- { message: 'Group ids must be unique' },
40
- )
41
- .refine(
42
- (groups) => {
43
- const allItems = groups.flatMap((g) => g.items)
44
- return new Set(allItems).size === allItems.length
45
- },
46
- { message: 'Item ids must not appear in multiple groups' },
47
- ),
48
- })
49
-
50
- // ── TypeScript types ──────────────────────────────────────────────────────
51
-
52
- export interface GroupInset {
53
- enabled: boolean
54
- /** Optional CSS color override for the inset background; defaults to --openpen-color-control-group. */
55
- color?: string
56
- }
57
-
58
- export interface LayoutGroup {
59
- id: string
60
- items: string[]
61
- separator?: 'auto' | 'always' | 'never'
62
- /** When `enabled: true`, render the group with the visible "grouped tools" container. */
63
- inset?: GroupInset
64
- }
65
-
66
- export interface ControlBarLayout {
67
- version: 1
68
- groups: LayoutGroup[]
69
- }
70
-
71
- // ── Contribution type ─────────────────────────────────────────────────────
72
-
73
- export interface ControlBarContribution {
74
- /** Item id — MUST be globally unique across all modules. */
75
- id: string
76
- /** Vue component rendered for this item. */
77
- component: Component
78
- /**
79
- * Preferred group id on first install.
80
- * MUST NOT be 'default' (reserved).
81
- * When omitted, item falls into the 'default' group.
82
- */
83
- defaultGroup?: string
84
- /**
85
- * Hints used when host auto-creates the defaultGroup.
86
- * Ignored if the group already exists in the layout.
87
- */
88
- groupHint?: {
89
- separator?: 'auto' | 'always' | 'never'
90
- label?: string | LocaleMap
91
- }
92
- }
93
-
94
- // ── Default layout ────────────────────────────────────────────────────────
95
-
96
- /** Baseline layout used on first launch: a single empty `'default'` group. */
97
- export const DEFAULT_CONTROL_BAR_LAYOUT: ControlBarLayout = {
98
- version: 1,
99
- groups: [{ id: 'default', items: [], separator: 'auto' }],
100
- }
101
-
102
- // ── Reconcile helpers ─────────────────────────────────────────────────────
103
-
104
- /**
105
- * Apply L3a non-destructive repairs to a parsed layout:
106
- * - Ensure 'default' group exists
107
- * - Replace invalid separator values with 'auto'
108
- * Returns the (possibly mutated copy) and whether any repair was made.
109
- *
110
- * @internal Host-only utility; not part of the plugin author API.
111
- */
112
- export function repairLayoutL3a(layout: ControlBarLayout): {
113
- layout: ControlBarLayout
114
- repaired: boolean
115
- } {
116
- let repaired = false
117
- const groups = layout.groups.map((g) => {
118
- if (g.separator !== undefined && !['auto', 'always', 'never'].includes(g.separator)) {
119
- repaired = true
120
- return { ...g, separator: 'auto' as const }
121
- }
122
- return g
123
- })
124
- if (!groups.some((g) => g.id === 'default')) {
125
- groups.push({ id: 'default', items: [], separator: 'auto' })
126
- repaired = true
127
- }
128
- return { layout: { ...layout, groups }, repaired }
129
- }
130
-
131
- /**
132
- * Apply L3b potentially-destructive repairs:
133
- * - De-duplicate item ids across groups (keep last occurrence)
134
- * - Trim ghost ids when count ≥ 20 (noop — ghost trimming is a UX action)
135
- * Returns the repaired layout and whether any repair was made.
136
- *
137
- * @internal Host-only utility; not part of the plugin author API.
138
- */
139
- export function repairLayoutL3b(
140
- layout: ControlBarLayout,
141
- knownItemIds: Set<string>,
142
- ): { layout: ControlBarLayout; repaired: boolean } {
143
- let repaired = false
144
- const seen = new Set<string>()
145
- // Process groups in reverse so the last occurrence wins.
146
- const reversedGroups = [...layout.groups].reverse()
147
- const deduped = reversedGroups.map((g) => {
148
- const items = g.items.filter((id) => {
149
- if (seen.has(id)) {
150
- repaired = true
151
- return false
152
- }
153
- seen.add(id)
154
- return true
155
- })
156
- return { ...g, items }
157
- })
158
- const groups = deduped.reverse()
159
-
160
- // Count ghosts across layout
161
- const totalItems = groups.flatMap((g) => g.items)
162
- const ghostCount = totalItems.filter((id) => !knownItemIds.has(id)).length
163
- if (ghostCount >= 20) {
164
- repaired = true
165
- // Remove ghosts from all groups
166
- const cleaned = groups.map((g) => ({
167
- ...g,
168
- items: g.items.filter((id) => knownItemIds.has(id)),
169
- }))
170
- return { layout: { ...layout, groups: cleaned }, repaired }
171
- }
172
-
173
- return { layout: { ...layout, groups }, repaired }
174
- }
@@ -1,225 +0,0 @@
1
- import type { z } from 'zod'
2
- import type { ModuleContributions } from './contributions'
3
- import type { NotifyPayload, NotifyHandle } from './notification'
4
- import type { LocaleMap } from '../locale'
5
-
6
- /**
7
- * Public contract that every OpenPen module — first-party (`src/core/modules/*`)
8
- * and third-party plugin alike — must satisfy.
9
- *
10
- * Modules express their behaviour entirely via `contributes` and
11
- * (optionally) `setup`. They never touch host-app internals directly:
12
- * cross-module communication goes through declared contribution slots
13
- * and the domain event bus.
14
- */
15
- export interface OpenPenModule {
16
- /** Globally unique kebab-case id. Subject to {@link import('../validation').MODULE_ID_RE}. */
17
- id: string
18
-
19
- /**
20
- * i18n-independent display metadata. Used as a fallback layer in the
21
- * Settings UI when the module's `contributes.locales` entries are not
22
- * registered (e.g. the module is disabled and its contributions are not
23
- * wired into the host). The runtime i18n contribution is always preferred
24
- * as the primary source; this field is only consulted on a cache miss.
25
- *
26
- * Values are `LocaleMap` (`Record<string, string>`) keyed by BCP-47 tag.
27
- * Resolution follows the same order as `resolveLabel`: exact tag → language
28
- * prefix → `'en'` fallback → first declared value.
29
- */
30
- metadata?: {
31
- name: LocaleMap
32
- description?: LocaleMap
33
- }
34
-
35
- /**
36
- * SemVer string. **Required for plugins** — the host validator
37
- * rejects any non-built-in module whose `version` is missing or
38
- * empty with a `'missing-version'` ValidationError, so a plugin
39
- * with no version never finishes loading.
40
- *
41
- * Built-in modules (those bundled with the host under
42
- * `src/core/modules/`) MAY omit it and inherit the host version;
43
- * the type stays optional only to accommodate that case.
44
- */
45
- version?: string
46
-
47
- /**
48
- * Minimum OpenPen app version this module requires (semver).
49
- *
50
- * Validated at load time by the host: a plugin whose `minAppVersion`
51
- * exceeds the running host version is skipped with a console warning.
52
- * Set this to `"1.0.0"` (or omit it) for broad v1.x compatibility.
53
- */
54
- minAppVersion?: string
55
-
56
- /**
57
- * One-shot initialisation, called after manifest validation and
58
- * before contributions are wired into the host. Use this for things
59
- * that need to happen exactly once, like seeding default settings or
60
- * subscribing to long-running event streams.
61
- */
62
- setup?: (ctx: ModuleSetupContext) => void | Promise<void>
63
-
64
- /** At least one field on this object is required (enforced by `defineModule`). */
65
- contributes?: ModuleContributions
66
-
67
- /**
68
- * Zod schema for this module's user-facing settings. The host renders
69
- * the settings form from this schema and persists the parsed value
70
- * under `config.json.modules[id]`. The same schema validates writes
71
- * made via `ctx.updateSettings()`; writes that fail validation reject
72
- * with the underlying `ZodError`.
73
- *
74
- * Modules that intend to call `ctx.updateSettings()` MUST declare
75
- * this field; calling `updateSettings` without a schema throws at
76
- * runtime so the missing contract surfaces during development.
77
- */
78
- settingsSchema?: z.ZodType
79
-
80
- /**
81
- * Settings schema version. Increment when the schema's shape changes
82
- * in a way that needs migration (renames, removals, or restructuring).
83
- * Pure additive changes (new keys with `.default()` values) do not
84
- * require a bump — Zod's defaults fill in on parse.
85
- *
86
- * Defaults to `1` if omitted. The host persists the version under
87
- * `config.json.moduleMeta[id].schemaVersion` (separate from the data
88
- * blob) so plugins never see this sentinel in their settings object.
89
- */
90
- settingsVersion?: number
91
-
92
- /**
93
- * Optional migration hook run before `settingsSchema` parses persisted
94
- * data. Called when the stored `schemaVersion` is less than the
95
- * module's `settingsVersion`. Receives the stored version + raw data
96
- * blob; returns the migrated blob, which is then validated by
97
- * `settingsSchema` and persisted at the new version.
98
- *
99
- * Use this for renames, removals, or restructuring. For pure additive
100
- * changes (new keys), Zod's `.default()` covers it — no migrate hook
101
- * needed.
102
- *
103
- * @example
104
- * migrate(stored, data) {
105
- * if (stored < 2 && 'oldName' in data) {
106
- * data.newName = data.oldName
107
- * delete data.oldName
108
- * }
109
- * return data
110
- * }
111
- */
112
- migrate?: (
113
- storedVersion: number,
114
- data: Record<string, unknown>
115
- ) => Record<string, unknown>
116
- }
117
-
118
- /**
119
- * Runtime context handed to `setup()`. Intentionally narrow — modules
120
- * that need more capabilities should declare them via contribution
121
- * slots so the dependency is visible at validation time.
122
- */
123
- export interface ModuleSetupContext {
124
- /** This module's id, for logging / error messages. */
125
- moduleId: string
126
- /** Currently active locale, e.g. 'zh-TW'. Read-only. */
127
- locale: string
128
- /**
129
- * Returns this module's current settings — a live in-memory snapshot
130
- * already validated by `settingsSchema`. Synchronous and zero-I/O:
131
- * the host hydrates the cache before `setup()` is called and updates
132
- * it in-place after every successful `updateSettings()`.
133
- *
134
- * Generic `T` should match the inferred type of your `settingsSchema`
135
- * (use `z.infer<typeof MySchema>` at the call site).
136
- *
137
- * @example
138
- * const settings = ctx.getSettings<z.infer<typeof MySchema>>()
139
- */
140
- getSettings: <T = unknown>() => T
141
-
142
- /**
143
- * Shallow-merges `patch` into this module's settings, validates the
144
- * result against `settingsSchema` (Zod), and persists to `config.json`.
145
- * Resolves once the write is durable; rejects with the underlying
146
- * `ZodError` if the merged object fails validation (no I/O happens
147
- * on validation failure).
148
- *
149
- * The in-memory snapshot returned by `getSettings()` is updated
150
- * before the Promise resolves; subscribers registered via
151
- * `onSettingsChange()` then fire with the new snapshot.
152
- *
153
- * Note on nested objects: the merge is shallow. Updating a nested
154
- * object replaces it entirely — spread the existing nested value
155
- * yourself if you only want to change one inner field:
156
- *
157
- * ```ts
158
- * const cur = ctx.getSettings<MySettings>()
159
- * await ctx.updateSettings({ colors: { ...cur.colors, primary: '#0f0' } })
160
- * ```
161
- *
162
- * Throws synchronously if the module did not declare `settingsSchema`.
163
- */
164
- updateSettings: <T = unknown>(patch: Partial<T>) => Promise<void>
165
-
166
- /**
167
- * Subscribe to settings changes for this module. The callback fires
168
- * after every successful `updateSettings()` — including writes that
169
- * originated from the host's settings panel — with the fresh,
170
- * already-validated snapshot.
171
- *
172
- * Returns an unsubscribe function. The host also calls it
173
- * automatically when the module is rolled back, so manual cleanup
174
- * is only needed if you want to stop listening sooner than
175
- * module dispose.
176
- *
177
- * @example
178
- * const stop = ctx.onSettingsChange<MySettings>((s) => {
179
- * redrawWithColor(s.primary)
180
- * })
181
- */
182
- onSettingsChange: <T = unknown>(cb: (settings: T) => void) => () => void
183
-
184
- /**
185
- * Invoke one of THIS module's main-side handlers (declared via
186
- * `contributes.mainHandlers`). The moduleId is captured by the host
187
- * so plugins cannot reach into other modules' handlers — use this
188
- * helper rather than the lower-level `openPenApi.moduleCall`.
189
- *
190
- * Returns whatever the handler returns (typed by the caller).
191
- */
192
- callMain: <T = unknown>(action: string, payload?: unknown) => Promise<T>
193
- /**
194
- * Register a cleanup callback. Called in reverse registration order
195
- * when the module is unloaded (hot-reload, user-disable, or app quit).
196
- * Use this to cancel timers, remove listeners, or release resources
197
- * that `setup()` acquired.
198
- */
199
- onDispose: (fn: () => void | Promise<void>) => void
200
- /**
201
- * Shows a short-lived toast notification in the overlay window.
202
- * Visibility and position are controlled by the user's settings.
203
- * Returns a NotifyHandle for early dismissal.
204
- */
205
- notify: (payload: NotifyPayload) => NotifyHandle
206
-
207
- /**
208
- * Resolves an i18n key for this module to the current-locale string.
209
- *
210
- * The key is automatically namespaced as `${moduleId}.${key}`, matching
211
- * the dictionary structure registered via `contributes.locales` (the host
212
- * merges each locale dict into the global vue-i18n instance under the
213
- * module's id namespace).
214
- *
215
- * Aligned with VS Code's `vscode.l10n.t()` convention:
216
- * ```typescript
217
- * ctx.t('notif.start')
218
- * ctx.t('notif.greeting', { name: 'Alice' }) // dict entry: 'Hello {name}'
219
- * ```
220
- *
221
- * When a key has no matching translation the namespaced key string is
222
- * returned (vue-i18n's default missing-key behaviour).
223
- */
224
- t: (key: string, params?: Record<string, unknown>) => string
225
- }
package/src/types/tool.ts DELETED
@@ -1,82 +0,0 @@
1
- /**
2
- * Public Tool / Stroke / Point primitives shared by host code and
3
- * every module / plugin. Declared inside @openpen/module-api so
4
- * third parties don't need to import host internals to type their
5
- * `canvas.tools` contributions.
6
- */
7
-
8
- export interface Point {
9
- x: number
10
- y: number
11
- }
12
-
13
- /** Solid color or linear gradient between two stops. */
14
- export type StrokeColor =
15
- | string
16
- | { type: 'linear'; from: string; to: string }
17
-
18
- export interface StrokeStyle {
19
- color: StrokeColor
20
- lineWidth: number
21
- lineCap: CanvasLineCap
22
- lineJoin: CanvasLineJoin
23
- }
24
-
25
- export interface Stroke {
26
- id: string
27
- /** Id of the tool that produced this stroke (matches a ToolContribution.id). */
28
- tool: string
29
- points: Point[]
30
- style: StrokeStyle
31
- /**
32
- * Tool-specific extra state. Tools MAY store arbitrary keys here during
33
- * `onPointerDown` / `onPointerMove` / `onPointerUp`; those keys survive
34
- * into `renderStroke(stroke, canvasCtx)` calls so the renderer can read
35
- * them back.
36
- *
37
- * The host treats extra keys as `unknown` — type-cast at the read site is
38
- * the plugin author's responsibility (TypeScript cannot infer the shape).
39
- *
40
- * @example
41
- * // Store during onPointerUp:
42
- * const stroke: Stroke = { id, tool: 'my-tool', points, style, opacity: 0.5 }
43
- *
44
- * // Read back in renderStroke:
45
- * const opacity = (stroke.opacity as number) ?? 1
46
- * canvasCtx.globalAlpha = opacity
47
- */
48
- [key: string]: unknown
49
- }
50
-
51
- export interface PointerModifiers {
52
- shiftKey?: boolean
53
- }
54
-
55
- export interface Tool {
56
- onPointerDown(
57
- canvasCtx: CanvasRenderingContext2D,
58
- point: Point,
59
- style: StrokeStyle
60
- ): void
61
- /**
62
- * Returns `true` when the engine should perform a full redraw
63
- * (e.g. stroke-eraser removed an existing stroke). Otherwise `void`.
64
- */
65
- onPointerMove(
66
- canvasCtx: CanvasRenderingContext2D,
67
- point: Point,
68
- modifiers?: PointerModifiers
69
- ): void | boolean
70
- /**
71
- * Returns the finished `Stroke` to push into the store, or `null`
72
- * to discard (e.g. an eraser that doesn't accumulate state).
73
- */
74
- onPointerUp(
75
- canvasCtx: CanvasRenderingContext2D,
76
- point: Point,
77
- modifiers?: PointerModifiers
78
- ): Stroke | null
79
- /** When true, canvas-engine clears + redraws all strokes before each move. */
80
- needsPreviewRedraw?: boolean
81
- renderPreview?(canvasCtx: CanvasRenderingContext2D): void
82
- }
File without changes
@@ -1,131 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * AppBanner — High-level wrapper (Layer 1).
4
- *
5
- * Presents info / warning / success / error status messages using the
6
- * --openpen-color-state-* design tokens. The icon is auto-rendered based on
7
- * the variant; message content goes in the default slot; optional inline action
8
- * buttons (e.g. restart / dismiss) go in the `#actions` slot.
9
- *
10
- * Inline mode (compact single-line) is enabled via the `inline` prop and is
11
- * suited for form validation feedback and status lines inside dialogs.
12
- */
13
-
14
- export type BannerVariant = 'info' | 'warning' | 'success' | 'error'
15
-
16
- withDefaults(defineProps<{
17
- variant: BannerVariant
18
- /** Compact single-line layout: smaller padding, vertically centered. */
19
- inline?: boolean
20
- }>(), { inline: false })
21
- </script>
22
-
23
- <template>
24
- <div
25
- :class="['app-banner', `app-banner-${variant}`, { 'app-banner-inline': inline }]"
26
- :role="variant === 'error' ? 'alert' : 'status'"
27
- >
28
- <span class="app-banner-icon" aria-hidden="true">
29
- <!-- info -->
30
- <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">
31
- <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"/>
32
- </svg>
33
- <!-- warning -->
34
- <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">
35
- <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"/>
36
- </svg>
37
- <!-- success -->
38
- <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">
39
- <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
40
- </svg>
41
- <!-- error -->
42
- <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">
43
- <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"/>
44
- </svg>
45
- </span>
46
-
47
- <span class="app-banner-body"><slot /></span>
48
-
49
- <span v-if="$slots.actions" class="app-banner-actions">
50
- <slot name="actions" />
51
- </span>
52
- </div>
53
- </template>
54
-
55
- <style scoped>
56
- .app-banner {
57
- display: flex;
58
- align-items: flex-start;
59
- gap: 10px;
60
- padding: 10px 14px;
61
- border-radius: var(--openpen-radius-md, 10px);
62
- border: 1px solid;
63
- font-size: 13px;
64
- line-height: 1.55;
65
- }
66
-
67
- .app-banner-inline {
68
- padding: 7px 12px;
69
- align-items: center;
70
- font-size: 12px;
71
- }
72
-
73
- .app-banner-icon {
74
- flex-shrink: 0;
75
- display: flex;
76
- align-items: center;
77
- margin-top: 1px;
78
- }
79
-
80
- .app-banner-inline .app-banner-icon { margin-top: 0; }
81
-
82
- .app-banner-body {
83
- flex: 1;
84
- min-width: 0;
85
- }
86
-
87
- .app-banner-actions {
88
- display: flex;
89
- align-items: center;
90
- gap: 6px;
91
- flex-shrink: 0;
92
- margin-left: auto;
93
- }
94
-
95
- /* Variants */
96
- .app-banner-info {
97
- background: var(--openpen-color-state-info-bg);
98
- border-color: var(--openpen-color-state-info-border);
99
- color: var(--openpen-color-state-info-text);
100
- }
101
- .app-banner-info .app-banner-icon {
102
- color: var(--openpen-color-state-info-icon);
103
- }
104
-
105
- .app-banner-warning {
106
- background: var(--openpen-color-state-warning-bg);
107
- border-color: var(--openpen-color-state-warning-border);
108
- color: var(--openpen-color-state-warning-text);
109
- }
110
- .app-banner-warning .app-banner-icon {
111
- color: var(--openpen-color-state-warning-icon);
112
- }
113
-
114
- .app-banner-success {
115
- background: var(--openpen-color-state-success-bg);
116
- border-color: var(--openpen-color-state-success-border);
117
- color: var(--openpen-color-state-success-text);
118
- }
119
- .app-banner-success .app-banner-icon {
120
- color: var(--openpen-color-state-success-icon);
121
- }
122
-
123
- .app-banner-error {
124
- background: var(--openpen-color-state-error-bg);
125
- border-color: var(--openpen-color-state-error-border);
126
- color: var(--openpen-color-state-error-text);
127
- }
128
- .app-banner-error .app-banner-icon {
129
- color: var(--openpen-color-state-error-icon);
130
- }
131
- </style>