@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-DBVFUpxc.js +245 -0
  3. package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-DBVFUpxc.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 +49 -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 +5 -0
  26. package/dist/host/stroke-style.d.ts +3 -0
  27. package/dist/host/types.d.ts +164 -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 +13 -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 +884 -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 +26 -12
  65. package/packages/module-api/CHANGELOG.md +0 -27
  66. package/src/cursors.ts +0 -369
  67. package/src/define-module.ts +0 -55
  68. package/src/host/color-utils.ts +0 -18
  69. package/src/host/host-commands.ts +0 -9
  70. package/src/host/index.ts +0 -53
  71. package/src/host/passthrough.ts +0 -10
  72. package/src/host/popup-anchor.ts +0 -14
  73. package/src/host/slot-entries.ts +0 -9
  74. package/src/host/stroke-store.ts +0 -20
  75. package/src/host/stroke-style.ts +0 -15
  76. package/src/index.ts +0 -131
  77. package/src/slots.ts +0 -135
  78. package/src/types/contributions.ts +0 -333
  79. package/src/types/control-bar-layout.ts +0 -174
  80. package/src/types/module.ts +0 -225
  81. package/src/types/tool.ts +0 -82
  82. package/src/uikit/components/.gitkeep +0 -0
  83. package/src/uikit/components/AppBanner.vue +0 -131
  84. package/src/uikit/components/AppButton.vue +0 -135
  85. package/src/uikit/components/AppDialog.vue +0 -393
  86. package/src/uikit/components/AppPopover.vue +0 -268
  87. package/src/uikit/components/AppSegmented.vue +0 -128
  88. package/src/uikit/components/AppSelect.vue +0 -235
  89. package/src/uikit/components/AppSlider.vue +0 -204
  90. package/src/uikit/components/AppTabs.vue +0 -163
  91. package/src/uikit/components/AppToggle.vue +0 -96
  92. package/src/uikit/components/AppTooltip.vue +0 -118
  93. package/src/uikit/components/DialogHost.vue +0 -272
  94. package/src/uikit/composables/useDialog.ts +0 -141
  95. package/src/uikit/composables/useDialogPluginComponent.ts +0 -27
  96. package/src/uikit/index.ts +0 -79
  97. package/src/uikit/primitives.ts +0 -150
  98. /package/{src → dist}/uikit/tokens.css +0 -0
@@ -1,128 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * AppSegmented — High-level wrapper (Layer 1).
4
- *
5
- * Wraps Reka UI RadioGroup (RadioGroupRoot / RadioGroupItem).
6
- *
7
- * Visual: flex gap 4px, 1.5px border var(--border-hi),
8
- * active = var(--accent-bg) background + var(--accent) color.
9
- *
10
- * Styles use --openpen-* design tokens.
11
- * No Reka UI types are leaked in the public API.
12
- */
13
- import {
14
- RadioGroupRoot,
15
- RadioGroupItem,
16
- } from '../primitives'
17
-
18
- // ── Public API (MUST NOT reference reka-ui types) ─────────────────────────────
19
-
20
- interface Option {
21
- value: string
22
- label: string
23
- icon?: string
24
- testid?: string
25
- }
26
-
27
- defineProps<{
28
- modelValue: string
29
- options: Option[]
30
- /** When true, all options are non-interactive and visually dimmed. */
31
- disabled?: boolean
32
- }>()
33
-
34
- const emit = defineEmits<{
35
- 'update:modelValue': [value: string]
36
- }>()
37
-
38
- // AcceptableValue from Reka UI can be null; we narrow to string so the public API has no Reka UI type leakage.
39
- function onValueChange(val: unknown) {
40
- if (typeof val === 'string') {
41
- emit('update:modelValue', val)
42
- }
43
- }
44
- </script>
45
-
46
- <template>
47
- <RadioGroupRoot
48
- class="app-seg"
49
- :class="{ 'app-seg--disabled': disabled }"
50
- :model-value="modelValue"
51
- :disabled="disabled"
52
- orientation="horizontal"
53
- @update:model-value="onValueChange"
54
- >
55
- <RadioGroupItem
56
- v-for="opt in options"
57
- :key="opt.value"
58
- :value="opt.value"
59
- as-child
60
- >
61
- <button
62
- class="app-seg-btn"
63
- :class="{ active: modelValue === opt.value }"
64
- :data-testid="opt.testid"
65
- >
66
- <span
67
- v-if="opt.icon"
68
- class="app-seg-icon"
69
- aria-hidden="true"
70
- v-html="opt.icon"
71
- />
72
- <span>{{ opt.label }}</span>
73
- </button>
74
- </RadioGroupItem>
75
- </RadioGroupRoot>
76
- </template>
77
-
78
- <style scoped>
79
- .app-seg {
80
- display: flex;
81
- gap: var(--openpen-space-xs);
82
- }
83
-
84
- .app-seg-btn {
85
- flex: 1;
86
- display: flex;
87
- align-items: center;
88
- justify-content: center;
89
- gap: 6px;
90
- padding: 6px var(--openpen-space-lg);
91
- border: 1.5px solid var(--openpen-color-border-hi);
92
- border-radius: var(--openpen-radius-sm);
93
- background: var(--openpen-color-input-bg);
94
- color: var(--openpen-color-text-dim);
95
- font-size: 12px;
96
- font-weight: 600;
97
- cursor: pointer;
98
- white-space: nowrap;
99
- transition:
100
- background var(--openpen-duration-fast) var(--openpen-easing-standard),
101
- color var(--openpen-duration-fast) var(--openpen-easing-standard),
102
- border-color var(--openpen-duration-fast) var(--openpen-easing-standard);
103
- }
104
-
105
- .app-seg-icon {
106
- display: inline-flex;
107
- align-items: center;
108
- justify-content: center;
109
- line-height: 0;
110
- }
111
-
112
- .app-seg-btn:hover {
113
- color: var(--openpen-color-text-primary);
114
- background: var(--openpen-color-control-hover);
115
- }
116
-
117
- .app-seg-btn.active {
118
- background: var(--openpen-color-accent-bg);
119
- color: var(--openpen-color-accent);
120
- border-color: color-mix(in srgb, var(--openpen-color-accent) 40%, transparent);
121
- }
122
-
123
- .app-seg--disabled {
124
- opacity: 0.45;
125
- pointer-events: none;
126
- cursor: not-allowed;
127
- }
128
- </style>
@@ -1,235 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * AppSelect — High-level wrapper (Layer 1).
4
- *
5
- * Plugin authors use this component without needing to know about Reka UI's
6
- * Select primitives. The API surface is OpenPen-owned and stable.
7
- *
8
- * Public API is stable: switching the underlying headless library in the
9
- * future does not break the plugin author's code.
10
- *
11
- * Implementation notes:
12
- * - Controlled via modelValue / update:modelValue (standard Vue v-model pattern).
13
- * - options[] is an OpenPen-owned type; no Reka UI types leak to the public API.
14
- * - Popup styling mirrors surface-popup tokens (same as AppPopover content).
15
- * - Styles are unscoped because SelectContent is teleported via SelectPortal.
16
- */
17
- import {
18
- SelectRoot,
19
- SelectTrigger,
20
- SelectPortal,
21
- SelectContent,
22
- SelectViewport,
23
- SelectItem,
24
- SelectItemText,
25
- SelectItemIndicator,
26
- SelectValue,
27
- SelectIcon,
28
- } from '../primitives'
29
-
30
- // ── OpenPen-owned types (MUST NOT reference reka-ui types) ───────────────────
31
-
32
- /** A single option in the dropdown list. */
33
- export interface SelectOption {
34
- /** The value emitted on selection. */
35
- value: string
36
- /** The label shown to the user. */
37
- label: string
38
- }
39
-
40
- export interface AppSelectProps {
41
- /** Currently selected value (bind with v-model). */
42
- modelValue: string
43
- /** List of selectable options. */
44
- options: SelectOption[]
45
- /** Placeholder shown when no option is selected. */
46
- placeholder: string
47
- /** Whether the select is disabled. */
48
- disabled?: boolean
49
- }
50
-
51
- const props = withDefaults(defineProps<AppSelectProps>(), {
52
- disabled: false,
53
- })
54
-
55
- const emit = defineEmits<{
56
- 'update:modelValue': [value: string]
57
- }>()
58
-
59
- function onValueChange(v: string) {
60
- emit('update:modelValue', v)
61
- }
62
- </script>
63
-
64
- <template>
65
- <SelectRoot
66
- :model-value="modelValue"
67
- :disabled="disabled"
68
- @update:model-value="onValueChange"
69
- >
70
- <SelectTrigger class="openpen-select-trigger">
71
- <SelectValue :placeholder="placeholder" />
72
- <SelectIcon class="openpen-select-icon" aria-hidden="true">
73
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
74
- <path d="M2 4L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
75
- </svg>
76
- </SelectIcon>
77
- </SelectTrigger>
78
- <SelectPortal>
79
- <SelectContent class="openpen-select-content" position="popper" :side-offset="4">
80
- <SelectViewport class="openpen-select-viewport">
81
- <SelectItem
82
- v-for="option in options"
83
- :key="option.value"
84
- :value="option.value"
85
- class="openpen-select-item"
86
- >
87
- <SelectItemIndicator class="openpen-select-item-indicator">
88
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
89
- <path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
90
- </svg>
91
- </SelectItemIndicator>
92
- <SelectItemText>{{ option.label }}</SelectItemText>
93
- </SelectItem>
94
- </SelectViewport>
95
- </SelectContent>
96
- </SelectPortal>
97
- </SelectRoot>
98
- </template>
99
-
100
- <!-- Styles MUST be unscoped — Vue scoped CSS does not follow Teleport targets,
101
- so .openpen-select-* classes would not receive the scoped data-v-* attribute
102
- when rendered inside the portal. -->
103
- <style>
104
- /* ── Trigger ─────────────────────────────────────────────────────────────── */
105
- .openpen-select-trigger {
106
- display: inline-flex;
107
- align-items: center;
108
- justify-content: space-between;
109
- gap: var(--openpen-space-sm);
110
- min-width: 120px;
111
- height: 32px;
112
- padding: 0 var(--openpen-space-md);
113
- background: var(--openpen-color-input-bg);
114
- border: 1px solid var(--openpen-color-border-hi);
115
- border-radius: var(--openpen-radius-md);
116
- color: var(--openpen-color-text-primary);
117
- font-size: 13px;
118
- cursor: pointer;
119
- transition:
120
- background var(--openpen-duration-fast),
121
- border-color var(--openpen-duration-fast);
122
- outline: none;
123
- white-space: nowrap;
124
- user-select: none;
125
- }
126
-
127
- .openpen-select-trigger:hover {
128
- background: var(--openpen-color-control-hover);
129
- border-color: var(--openpen-color-border-hi);
130
- }
131
-
132
- .openpen-select-trigger:focus-visible {
133
- border-color: var(--openpen-color-accent);
134
- box-shadow: 0 0 0 2px var(--openpen-color-accent-glow);
135
- }
136
-
137
- .openpen-select-trigger[data-disabled] {
138
- opacity: 0.45;
139
- cursor: not-allowed;
140
- }
141
-
142
- /* ── Trigger icon (chevron) ──────────────────────────────────────────────── */
143
- .openpen-select-icon {
144
- color: var(--openpen-color-text-dim);
145
- flex-shrink: 0;
146
- display: flex;
147
- align-items: center;
148
- transition: transform var(--openpen-duration-fast);
149
- }
150
-
151
- .openpen-select-trigger[data-state='open'] .openpen-select-icon {
152
- transform: rotate(180deg);
153
- }
154
-
155
- /* ── Dropdown content ────────────────────────────────────────────────────── */
156
- .openpen-select-content {
157
- background: var(--openpen-color-surface-popup);
158
- border: 1px solid var(--openpen-color-border-hi);
159
- border-radius: var(--openpen-radius-md);
160
- box-shadow: var(--openpen-shadow);
161
- backdrop-filter: var(--openpen-blur);
162
- -webkit-backdrop-filter: var(--openpen-blur);
163
- z-index: 100;
164
- pointer-events: auto;
165
- /* Match trigger width */
166
- min-width: var(--reka-select-trigger-width, 120px);
167
- max-height: 240px;
168
- overflow: hidden;
169
- }
170
-
171
- /* ── Viewport scrollable area ────────────────────────────────────────────── */
172
- .openpen-select-viewport {
173
- padding: var(--openpen-space-xs);
174
- }
175
-
176
- /* ── Individual items ────────────────────────────────────────────────────── */
177
- .openpen-select-item {
178
- display: flex;
179
- align-items: center;
180
- gap: var(--openpen-space-sm);
181
- height: 30px;
182
- padding: 0 var(--openpen-space-md);
183
- padding-left: 28px; /* indent to align text past indicator */
184
- border-radius: var(--openpen-radius-sm);
185
- font-size: 13px;
186
- color: var(--openpen-color-text-primary);
187
- cursor: pointer;
188
- position: relative;
189
- user-select: none;
190
- outline: none;
191
- transition: background var(--openpen-duration-fast);
192
- }
193
-
194
- .openpen-select-item[data-highlighted] {
195
- background: var(--openpen-color-control-hover);
196
- color: var(--openpen-color-text-primary);
197
- }
198
-
199
- .openpen-select-item[data-state='checked'] {
200
- color: var(--openpen-color-accent);
201
- }
202
-
203
- .openpen-select-item[data-disabled] {
204
- opacity: 0.45;
205
- cursor: not-allowed;
206
- }
207
-
208
- /* ── Selected item indicator (checkmark) ─────────────────────────────────── */
209
- .openpen-select-item-indicator {
210
- position: absolute;
211
- left: var(--openpen-space-sm);
212
- display: flex;
213
- align-items: center;
214
- justify-content: center;
215
- color: var(--openpen-color-accent);
216
- }
217
-
218
- /* ── Open / close animations ─────────────────────────────────────────────────
219
- Pattern matches AppPopover keyframes (200ms bounce enter, 150ms ease leave). */
220
- .openpen-select-content[data-state='open'] {
221
- animation: openpen-select-in 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
222
- }
223
- .openpen-select-content[data-state='closed'] {
224
- animation: openpen-select-out 150ms ease forwards;
225
- }
226
-
227
- @keyframes openpen-select-in {
228
- from { opacity: 0; transform: scale(0.95); }
229
- to { opacity: 1; transform: scale(1); }
230
- }
231
- @keyframes openpen-select-out {
232
- from { opacity: 1; transform: scale(1); }
233
- to { opacity: 0; transform: scale(0.95); }
234
- }
235
- </style>
@@ -1,204 +0,0 @@
1
- <script setup lang="ts">
2
- /**
3
- * AppSlider — High-level wrapper (Layer 1).
4
- *
5
- * Wraps Reka UI Slider (SliderRoot / SliderTrack / SliderRange / SliderThumb).
6
- *
7
- * Visual: 4px track, 14×14 white thumb, accent fill,
8
- * hover scale(1.2) at var(--openpen-duration-fast) easing.
9
- *
10
- * Styles use --openpen-* design tokens.
11
- * No Reka UI types are leaked in the public API.
12
- */
13
- import { computed } from 'vue'
14
- import {
15
- SliderRoot,
16
- SliderTrack,
17
- SliderRange,
18
- SliderThumb,
19
- } from '../primitives'
20
-
21
- // ── Public API (MUST NOT reference reka-ui types) ─────────────────────────────
22
-
23
- const props = withDefaults(defineProps<{
24
- modelValue: number
25
- min?: number
26
- max?: number
27
- step?: number
28
- /** Container width (e.g. '100%', '72px'). In vertical mode this sets the height. */
29
- width?: string
30
- orientation?: 'horizontal' | 'vertical'
31
- trackHeight?: string
32
- trackRadius?: string
33
- thumbWidth?: string
34
- thumbHeight?: string
35
- thumbRadius?: string
36
- /**
37
- * Reverse the value-to-position mapping. With orientation='vertical', sets
38
- * top=min and bottom=max (drag down to increase). With orientation='horizontal',
39
- * sets right=min and left=max (drag left to increase). Useful when an icon
40
- * legend implies the opposite direction of the underlying control.
41
- *
42
- * Delegates to reka-ui SliderRoot's native `inverted` prop, which swaps
43
- * startEdge/endEdge on SliderRange — no manual value transformation needed.
44
- */
45
- inverted?: boolean
46
- }>(), {
47
- min: 0,
48
- max: 100,
49
- step: 1,
50
- width: '100%',
51
- orientation: 'horizontal',
52
- trackHeight: '4px',
53
- trackRadius: '2px',
54
- thumbWidth: '14px',
55
- thumbHeight: '14px',
56
- thumbRadius: '50%',
57
- inverted: false,
58
- })
59
-
60
- const emit = defineEmits<{
61
- 'update:modelValue': [value: number]
62
- }>()
63
-
64
- // ── Internal state ────────────────────────────────────────────────────────────
65
-
66
- const isVertical = computed(() => props.orientation === 'vertical')
67
-
68
- /** Fill percentage for the track range background. */
69
- const fillPct = computed(() => {
70
- const range = props.max - props.min
71
- if (range === 0) return '0.00%'
72
- const pct = ((props.modelValue - props.min) / range) * 100
73
- return Math.max(0, Math.min(100, pct)).toFixed(2) + '%'
74
- })
75
-
76
- /** Root element inline style */
77
- const rootStyle = computed(() => {
78
- if (isVertical.value) {
79
- return {
80
- '--thumb-w': props.thumbWidth,
81
- '--thumb-h': props.thumbHeight,
82
- '--thumb-r': props.thumbRadius,
83
- '--fill-pct': fillPct.value,
84
- '--track-height': props.trackHeight,
85
- '--track-radius': props.trackRadius,
86
- height: props.width,
87
- width: '16px',
88
- }
89
- }
90
- return {
91
- '--thumb-w': props.thumbWidth,
92
- '--thumb-h': props.thumbHeight,
93
- '--thumb-r': props.thumbRadius,
94
- '--fill-pct': fillPct.value,
95
- '--track-height': props.trackHeight,
96
- '--track-radius': props.trackRadius,
97
- width: props.width,
98
- }
99
- })
100
-
101
- function onValueChange(val: number[] | undefined) {
102
- if (val && val.length > 0) {
103
- emit('update:modelValue', val[0])
104
- }
105
- }
106
- </script>
107
-
108
- <template>
109
- <SliderRoot
110
- class="app-slider-root"
111
- :class="{ 'app-slider-root--vertical': isVertical, 'app-slider-root--inverted': inverted }"
112
- :style="rootStyle"
113
- :model-value="[modelValue]"
114
- :min="min"
115
- :max="max"
116
- :step="step"
117
- :orientation="orientation"
118
- :inverted="inverted"
119
- @update:model-value="onValueChange"
120
- >
121
- <SliderTrack class="app-slider-track">
122
- <SliderRange class="app-slider-range" />
123
- </SliderTrack>
124
- <SliderThumb class="app-slider-thumb" />
125
- </SliderRoot>
126
- </template>
127
-
128
- <style scoped>
129
- /* ── Root container ────────────────────────────────────────────────────────── */
130
- .app-slider-root {
131
- position: relative;
132
- display: flex;
133
- align-items: center;
134
- user-select: none;
135
- touch-action: none;
136
- flex-shrink: 0;
137
- cursor: pointer;
138
- }
139
-
140
- .app-slider-root[data-orientation='horizontal'] {
141
- height: var(--track-height, 4px);
142
- }
143
-
144
- .app-slider-root[data-orientation='vertical'] {
145
- flex-direction: column;
146
- width: var(--track-height, 4px);
147
- }
148
-
149
- /* ── Track ─────────────────────────────────────────────────────────────────── */
150
- .app-slider-track {
151
- position: relative;
152
- flex-grow: 1;
153
- border-radius: var(--track-radius, 2px);
154
- background: var(--openpen-color-border-hi);
155
- }
156
-
157
- .app-slider-root[data-orientation='horizontal'] .app-slider-track {
158
- height: var(--track-height, 4px);
159
- }
160
-
161
- .app-slider-root[data-orientation='vertical'] .app-slider-track {
162
- width: var(--track-height, 4px);
163
- }
164
-
165
- /* ── Range (filled portion) ─────────────────────────────────────────────────── */
166
- .app-slider-range {
167
- position: absolute;
168
- border-radius: var(--track-radius, 2px);
169
- background: var(--openpen-color-accent);
170
- }
171
-
172
- .app-slider-root[data-orientation='horizontal'] .app-slider-range {
173
- height: 100%;
174
- top: 0;
175
- left: 0;
176
- }
177
-
178
- .app-slider-root[data-orientation='vertical'] .app-slider-range {
179
- width: 100%;
180
- bottom: 0;
181
- left: 0;
182
- }
183
-
184
- /* ── Thumb ─────────────────────────────────────────────────────────────────── */
185
- .app-slider-thumb {
186
- display: block;
187
- width: var(--thumb-w, 14px);
188
- height: var(--thumb-h, 14px);
189
- border-radius: var(--thumb-r, 50%);
190
- background: #fff;
191
- box-shadow: 0 1px 6px rgba(0, 0, 0, 0.50);
192
- cursor: pointer;
193
- outline: none;
194
- transition: transform var(--openpen-duration-fast) var(--openpen-easing-standard);
195
- }
196
-
197
- .app-slider-thumb:hover {
198
- transform: scale(1.2);
199
- }
200
-
201
- .app-slider-thumb:focus-visible {
202
- box-shadow: 0 0 0 3px var(--openpen-color-accent-glow);
203
- }
204
- </style>