@witchcraft/ui 0.1.1 → 0.1.3
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/dist/module.cjs +5 -0
- package/dist/module.d.ts +36 -0
- package/dist/module.json +2 -2
- package/dist/module.mjs +2 -1
- package/dist/runtime/assets/utils.css +1 -1
- package/dist/runtime/components/Aria/Aria.vue +9 -5
- package/dist/runtime/components/Focus.stories.d.ts +11 -0
- package/dist/runtime/components/Focus.stories.js +53 -0
- package/dist/runtime/components/Icon/Icon.vue +30 -10
- package/dist/runtime/components/LibButton/LibButton.stories.d.ts +12 -0
- package/dist/runtime/components/LibButton/LibButton.stories.js +94 -0
- package/dist/runtime/components/LibButton/LibButton.vue +72 -58
- package/dist/runtime/components/LibCheckbox/LibCheckbox.stories.d.ts +14 -0
- package/dist/runtime/components/LibCheckbox/LibCheckbox.stories.js +29 -0
- package/dist/runtime/components/LibCheckbox/LibCheckbox.vue +74 -48
- package/dist/runtime/components/LibColorInput/LibColorInput.stories.d.ts +7 -0
- package/dist/runtime/components/LibColorInput/LibColorInput.stories.js +58 -0
- package/dist/runtime/components/LibColorInput/LibColorInput.vue +107 -63
- package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.d.ts +9 -0
- package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.js +68 -0
- package/dist/runtime/components/LibColorPicker/LibColorPicker.vue +352 -271
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.d.ts +7 -0
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.js +36 -0
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +56 -32
- package/dist/runtime/components/LibDatePicker/LibDatePicker.stories.d.ts +11 -0
- package/dist/runtime/components/LibDatePicker/LibDatePicker.stories.js +98 -0
- package/dist/runtime/components/LibDatePicker/LibDatePicker.vue +38 -17
- package/dist/runtime/components/LibDatePicker/LibRangeDatePicker.vue +82 -53
- package/dist/runtime/components/LibDatePicker/LibSingleDatePicker.vue +67 -50
- package/dist/runtime/components/LibDatePicker/LibTimeZonePicker.vue +8 -7
- package/dist/runtime/components/LibDebug/LibDebug.stories.d.ts +9 -0
- package/dist/runtime/components/LibDebug/LibDebug.stories.js +46 -0
- package/dist/runtime/components/LibDebug/LibDebug.vue +70 -42
- package/dist/runtime/components/LibDevOnly/LibDevOnly.vue +31 -18
- package/dist/runtime/components/LibFileInput/LibFileInput.stories.d.ts +10 -0
- package/dist/runtime/components/LibFileInput/LibFileInput.stories.js +63 -0
- package/dist/runtime/components/LibFileInput/LibFileInput.vue +156 -113
- package/dist/runtime/components/LibInputDeprecated/LibInputDeprecated.stories.d.ts +33 -0
- package/dist/runtime/components/LibInputDeprecated/LibInputDeprecated.stories.js +384 -0
- package/dist/runtime/components/LibInputDeprecated/LibInputDeprecated.vue +241 -215
- package/dist/runtime/components/LibLabel/LibLabel.stories.d.ts +6 -0
- package/dist/runtime/components/LibLabel/LibLabel.stories.js +25 -0
- package/dist/runtime/components/LibLabel/LibLabel.vue +46 -30
- package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.d.ts +23 -0
- package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.js +61 -0
- package/dist/runtime/components/LibMultiValues/LibMultiValues.vue +58 -44
- package/dist/runtime/components/LibNotifications/LibNotification.stories.d.ts +15 -0
- package/dist/runtime/components/LibNotifications/LibNotification.stories.js +126 -0
- package/dist/runtime/components/LibNotifications/LibNotification.vue +48 -32
- package/dist/runtime/components/LibNotifications/LibNotifications.stories.d.ts +6 -0
- package/dist/runtime/components/LibNotifications/LibNotifications.stories.js +109 -0
- package/dist/runtime/components/LibNotifications/LibNotifications.vue +83 -63
- package/dist/runtime/components/LibPagination/LibPagination.stories.d.ts +6 -0
- package/dist/runtime/components/LibPagination/LibPagination.stories.js +40 -0
- package/dist/runtime/components/LibPagination/LibPagination.vue +111 -67
- package/dist/runtime/components/LibPalette/LibPalette.stories.d.ts +6 -0
- package/dist/runtime/components/LibPalette/LibPalette.stories.js +20 -0
- package/dist/runtime/components/LibPalette/LibPalette.vue +23 -20
- package/dist/runtime/components/LibPopup/LibPopup.stories.d.ts +14 -0
- package/dist/runtime/components/LibPopup/LibPopup.stories.js +147 -0
- package/dist/runtime/components/LibPopup/LibPopup.vue +351 -314
- package/dist/runtime/components/LibProgressBar/LibProgressBar.stories.d.ts +10 -0
- package/dist/runtime/components/LibProgressBar/LibProgressBar.stories.js +81 -0
- package/dist/runtime/components/LibProgressBar/LibProgressBar.vue +91 -70
- package/dist/runtime/components/LibRecorder/LibRecorder.stories.d.ts +19 -0
- package/dist/runtime/components/LibRecorder/LibRecorder.stories.js +63 -0
- package/dist/runtime/components/LibRecorder/LibRecorder.vue +177 -133
- package/dist/runtime/components/LibRoot/LibRoot.vue +100 -73
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.stories.d.ts +26 -0
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.stories.js +78 -0
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.vue +77 -49
- package/dist/runtime/components/LibSuggestions/LibSuggestions.stories.d.ts +27 -0
- package/dist/runtime/components/LibSuggestions/LibSuggestions.stories.js +112 -0
- package/dist/runtime/components/LibSuggestions/LibSuggestions.vue +156 -123
- package/dist/runtime/components/LibTable/LibTable.stories.d.ts +16 -0
- package/dist/runtime/components/LibTable/LibTable.stories.js +156 -0
- package/dist/runtime/components/LibTable/LibTable.vue +99 -63
- package/dist/runtime/components/Reset.stories.d.ts +5 -0
- package/dist/runtime/components/Reset.stories.js +19 -0
- package/dist/runtime/components/Scrolling.stories.d.ts +6 -0
- package/dist/runtime/components/Scrolling.stories.js +44 -0
- package/dist/runtime/components/Template/NAME.vue +36 -15
- package/dist/runtime/components/TestControls/TestControls.vue +9 -6
- package/dist/runtime/composables/useScrollNearContainerEdges.stories.d.ts +7 -0
- package/dist/runtime/composables/useScrollNearContainerEdges.stories.js +85 -0
- package/dist/types.d.mts +6 -2
- package/dist/types.d.ts +7 -0
- package/package.json +11 -5
- package/src/module.ts +2 -1
- package/src/runtime/assets/utils.css +5 -5
- package/src/runtime/components/LibButton/LibButton.vue +2 -6
- package/src/runtime/nuxt/plugins/vue-plugin.ts +1 -1
- package/dist/runtime/components/Aria/Aria.vue.d.ts +0 -5
- package/dist/runtime/components/Icon/Icon.vue.d.ts +0 -21
- package/dist/runtime/components/LibButton/LibButton.vue.d.ts +0 -36
- package/dist/runtime/components/LibCheckbox/LibCheckbox.vue.d.ts +0 -42
- package/dist/runtime/components/LibColorInput/LibColorInput.vue.d.ts +0 -63
- package/dist/runtime/components/LibColorPicker/LibColorPicker.vue.d.ts +0 -61
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue.d.ts +0 -22
- package/dist/runtime/components/LibDatePicker/LibDatePicker.vue.d.ts +0 -40
- package/dist/runtime/components/LibDatePicker/LibRangeDatePicker.vue.d.ts +0 -34
- package/dist/runtime/components/LibDatePicker/LibSingleDatePicker.vue.d.ts +0 -34
- package/dist/runtime/components/LibDatePicker/LibTimeZonePicker.vue.d.ts +0 -22
- package/dist/runtime/components/LibDebug/LibDebug.vue.d.ts +0 -32
- package/dist/runtime/components/LibDevOnly/LibDevOnly.vue.d.ts +0 -22
- package/dist/runtime/components/LibFileInput/LibFileInput.vue.d.ts +0 -43
- package/dist/runtime/components/LibInputDeprecated/LibInputDeprecated.vue.d.ts +0 -165
- package/dist/runtime/components/LibLabel/LibLabel.vue.d.ts +0 -27
- package/dist/runtime/components/LibMultiValues/LibMultiValues.vue.d.ts +0 -29
- package/dist/runtime/components/LibNotifications/LibNotification.vue.d.ts +0 -17
- package/dist/runtime/components/LibNotifications/LibNotifications.vue.d.ts +0 -13
- package/dist/runtime/components/LibPagination/LibPagination.vue.d.ts +0 -104
- package/dist/runtime/components/LibPalette/LibPalette.vue.d.ts +0 -14
- package/dist/runtime/components/LibPopup/LibPopup.vue.d.ts +0 -46
- package/dist/runtime/components/LibProgressBar/LibProgressBar.vue.d.ts +0 -41
- package/dist/runtime/components/LibRecorder/LibRecorder.vue.d.ts +0 -77
- package/dist/runtime/components/LibRoot/LibRoot.vue.d.ts +0 -41
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.vue.d.ts +0 -35
- package/dist/runtime/components/LibSuggestions/LibSuggestions.vue.d.ts +0 -94
- package/dist/runtime/components/LibTable/LibTable.vue.d.ts +0 -45
- package/dist/runtime/components/Template/NAME.vue.d.ts +0 -17
- package/dist/runtime/components/TestControls/TestControls.vue.d.ts +0 -5
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
contenteditable=false is because of storybook, it's shortcuts interfere when not using real input elements
|
|
5
5
|
-->
|
|
6
6
|
<div
|
|
7
|
-
:id="id
|
|
8
|
-
:class="twMerge(
|
|
9
|
-
`recorder
|
|
7
|
+
:id="id?? fallbackId"
|
|
8
|
+
:class="twMerge(`recorder
|
|
10
9
|
flex items-center
|
|
11
10
|
gap-2
|
|
12
11
|
px-2
|
|
@@ -14,38 +13,37 @@
|
|
|
14
13
|
focus-outline-no-offset
|
|
15
14
|
rounded-sm
|
|
16
15
|
`,
|
|
17
|
-
|
|
16
|
+
border &&`
|
|
18
17
|
border
|
|
19
18
|
border-neutral-500
|
|
20
19
|
focus:border-accent-500
|
|
21
20
|
`,
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
(disabled || readonly) && `
|
|
23
23
|
text-neutral-400
|
|
24
24
|
dark:text-neutral-600
|
|
25
25
|
`,
|
|
26
|
-
|
|
26
|
+
(disabled || readonly) && border && `
|
|
27
27
|
bg-neutral-50
|
|
28
28
|
dark:bg-neutral-950
|
|
29
29
|
border-neutral-400
|
|
30
30
|
dark:border-neutral-600
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)"
|
|
31
|
+
`
|
|
32
|
+
, ($attrs as any).class)"
|
|
34
33
|
:aria-disabled="disabled"
|
|
35
34
|
:aria-readonly="readonly"
|
|
36
35
|
:tabindex="disabled ? -1 : 0"
|
|
37
36
|
:title="recording ? recordingTitle : tempValue"
|
|
38
37
|
contenteditable="false"
|
|
39
38
|
ref="recorderEl"
|
|
40
|
-
v-bind="{
|
|
39
|
+
v-bind="{...ariaLabel, ...$attrs, class:undefined}"
|
|
41
40
|
@blur="handleBlurRecorder($event)"
|
|
42
41
|
@click="handleClickRecorder($event)"
|
|
43
42
|
@keydown.space.prevent="handleClickRecorder($event, true)"
|
|
44
43
|
>
|
|
45
44
|
<!-- :aria-description="recording ? recordingTitle : ''" -->
|
|
46
45
|
<div
|
|
47
|
-
:class="twMerge(
|
|
48
|
-
`recorder--indicator
|
|
46
|
+
:class="twMerge(`recorder--indicator
|
|
49
47
|
inline-block
|
|
50
48
|
bg-red-700
|
|
51
49
|
rounded-full
|
|
@@ -54,145 +52,191 @@
|
|
|
54
52
|
shrink-0
|
|
55
53
|
hover:bg-red-500
|
|
56
54
|
`,
|
|
57
|
-
|
|
55
|
+
recording && `
|
|
58
56
|
animate-[blink_1s_infinite]
|
|
59
57
|
bg-red-500
|
|
60
58
|
`,
|
|
61
|
-
|
|
59
|
+
(disabled || readonly) && `
|
|
62
60
|
bg-neutral-500
|
|
63
61
|
`
|
|
64
|
-
)"
|
|
62
|
+
)"
|
|
65
63
|
ref="recorderIndicatorEl"
|
|
66
64
|
/>
|
|
67
65
|
<div class="recorder--value before:content-vertical-holder truncate">
|
|
68
|
-
{{ recording
|
|
66
|
+
{{ recording
|
|
67
|
+
? recordingValue ?? t("recorder.recording")
|
|
68
|
+
: tempValue }}
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
71
71
|
</template>
|
|
72
|
+
<script setup lang="ts">
|
|
73
|
+
import { keys } from "@alanscodelog/utils/keys.js"
|
|
74
|
+
import { computed, type HTMLAttributes ,onBeforeUnmount, onMounted, type PropType, ref, watch, watchPostEffect } from "vue"
|
|
75
|
+
|
|
76
|
+
import { useAriaLabel } from "../../composables/useAriaLabel.js"
|
|
77
|
+
import { useInjectedI18n } from "../../composables/useInjectedI18n.js"
|
|
78
|
+
import { twMerge } from "../../utils/twMerge.js"
|
|
79
|
+
import { type BaseInteractiveProps, baseInteractivePropsDefaults, getFallbackId, type LabelProps, type LinkableByIdProps,type TailwindClassProp } from "../shared/props.js"
|
|
72
80
|
|
|
73
|
-
<script setup>
|
|
74
|
-
import { keys } from "@alanscodelog/utils/keys.js";
|
|
75
|
-
import { computed, onBeforeUnmount, onMounted, ref, watch, watchPostEffect } from "vue";
|
|
76
|
-
import { useAriaLabel } from "../../composables/useAriaLabel.js";
|
|
77
|
-
import { useInjectedI18n } from "../../composables/useInjectedI18n.js";
|
|
78
|
-
import { twMerge } from "../../utils/twMerge.js";
|
|
79
|
-
import { baseInteractivePropsDefaults, getFallbackId } from "../shared/props.js";
|
|
80
81
|
defineOptions({
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
})
|
|
84
|
-
const t = useInjectedI18n()
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const recording = defineModel("recording", {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
82
|
+
name: "lib-recorder",
|
|
83
|
+
inheritAttrs: false,
|
|
84
|
+
})
|
|
85
|
+
const t = useInjectedI18n()
|
|
86
|
+
|
|
87
|
+
const emits = defineEmits<{
|
|
88
|
+
/** Recorder is blurred */
|
|
89
|
+
(e: "recorder:blur", $event: FocusEvent): void
|
|
90
|
+
/** Recorder is clicked. The component's indicator and recorder elements are passed to help filter out those clicks. */
|
|
91
|
+
(e: "recorder:click", { event, indicator, input }: { event: MouseEvent | KeyboardEvent, indicator: HTMLElement, input: HTMLInputElement }): void
|
|
92
|
+
/* User presses enter. Not emitted when multiple values are used. */
|
|
93
|
+
(e: "focus:parent"): void
|
|
94
|
+
}>()
|
|
95
|
+
const fallbackId = getFallbackId()
|
|
96
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
97
|
+
recordingTitle: "",
|
|
98
|
+
id: undefined,
|
|
99
|
+
binders: undefined,
|
|
100
|
+
recorder: undefined,
|
|
101
|
+
...baseInteractivePropsDefaults
|
|
102
|
+
})
|
|
103
|
+
/**
|
|
104
|
+
* Puts the element into recording mode if true. See {@link props.recorder}.
|
|
105
|
+
*/
|
|
106
|
+
const recording = defineModel<boolean>("recording", { required: false, default: false })
|
|
107
|
+
|
|
108
|
+
/** The final value of the recorder. For intermediate values while recording, pass a recorder and set an appropriate recording value. */
|
|
109
|
+
const modelValue = defineModel<string>({ required: true })
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const recorderEl = ref<HTMLInputElement | null>(null)
|
|
113
|
+
const recorderIndicatorEl = ref<HTMLElement | null>(null)
|
|
114
|
+
const canEdit = computed(() => !props.disabled && !props.readonly)
|
|
115
|
+
const tempValue = ref(modelValue.value)
|
|
116
|
+
|
|
111
117
|
watch([() => props.binders, () => props.binders], () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
})
|
|
118
|
+
if (recording.value) {
|
|
119
|
+
throw new Error("Component was not designed to allow swapping out of binders/recorders while recording")
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
116
123
|
watch(modelValue, () => {
|
|
117
|
-
|
|
118
|
-
})
|
|
119
|
-
const ariaLabel = useAriaLabel(props)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
124
|
+
tempValue.value = modelValue.value
|
|
125
|
+
})
|
|
126
|
+
const ariaLabel = useAriaLabel(props)
|
|
127
|
+
|
|
128
|
+
const boundListeners: Record<string, any> = {}
|
|
129
|
+
let isBound = false
|
|
130
|
+
|
|
131
|
+
const unbindListeners = (): void => {
|
|
132
|
+
if (!isBound) return
|
|
133
|
+
isBound = false
|
|
134
|
+
if (props.recorder) {
|
|
135
|
+
for (const key of keys(boundListeners)) {
|
|
136
|
+
recorderEl.value?.removeEventListener(key, boundListeners[key])
|
|
137
|
+
delete boundListeners[key]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (props.binders && recorderEl.value) {
|
|
141
|
+
props.binders.unbind(recorderEl.value as HTMLInputElement)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const bindListeners = (): void => {
|
|
145
|
+
if (!props.recorder && !props.binders) {
|
|
146
|
+
throw new Error("Record is true but no recorder or binders props was passed")
|
|
147
|
+
}
|
|
148
|
+
if (props.recorder && props.binders) {
|
|
149
|
+
throw new Error("Recording is true and was passed both a recorder and a binders prop. Both cannot be used at the same time.")
|
|
150
|
+
}
|
|
151
|
+
isBound = true
|
|
152
|
+
if (props.recorder) {
|
|
153
|
+
for (const key of keys(props.recorder)) {
|
|
154
|
+
recorderEl.value?.addEventListener(key, props.recorder[key], { passive: false })
|
|
155
|
+
boundListeners[key] = props.recorder[key]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (props.binders && recorderEl.value) {
|
|
159
|
+
props.binders.bind(recorderEl.value as HTMLInputElement)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
153
163
|
watchPostEffect(() => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
if (!canEdit.value) {
|
|
165
|
+
unbindListeners()
|
|
166
|
+
recording.value = false
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
if (recording.value) {
|
|
170
|
+
bindListeners()
|
|
171
|
+
} else {
|
|
172
|
+
if ((props.recorder || props.binders) && isBound) {
|
|
173
|
+
unbindListeners()
|
|
174
|
+
// if we just blur the input then we can't shift+tab backwards
|
|
175
|
+
// this way we can go forwards or backwards without actually focusing since parentEl is not focusable
|
|
176
|
+
// parentEl.value?.focus()
|
|
177
|
+
emits("focus:parent")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
168
182
|
onBeforeUnmount(() => {
|
|
169
|
-
|
|
170
|
-
})
|
|
183
|
+
unbindListeners()
|
|
184
|
+
})
|
|
171
185
|
onMounted(() => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (!canEdit.value) return;
|
|
184
|
-
if (!recording.value) {
|
|
185
|
-
recorderEl.value?.focus();
|
|
186
|
-
}
|
|
187
|
-
if (props.recorder || props.binders) {
|
|
188
|
-
if (isSpaceKey) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
emits("recorder:click", { event: e, indicator: recorderIndicatorEl.value, input: recorderEl.value });
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
</script>
|
|
186
|
+
if (recording.value) {
|
|
187
|
+
bindListeners()
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const handleBlurRecorder = (e: FocusEvent): void => {
|
|
192
|
+
if (!canEdit.value) return
|
|
193
|
+
if (props.recorder || props.binders) {
|
|
194
|
+
emits("recorder:blur", e)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
195
197
|
|
|
196
|
-
|
|
198
|
+
const handleClickRecorder = (e: MouseEvent | KeyboardEvent, isSpaceKey: boolean = false): void => {
|
|
199
|
+
if (!canEdit.value) return
|
|
200
|
+
if (!recording.value) {
|
|
201
|
+
recorderEl.value?.focus()
|
|
202
|
+
}
|
|
203
|
+
// toggle if clicking on the recording indicator, otherwise only allow starting recording, so if needed, clicks can be recorded
|
|
204
|
+
if (props.recorder || props.binders) {
|
|
205
|
+
if (isSpaceKey) { return }
|
|
206
|
+
emits("recorder:click", { event: e as MouseEvent, indicator: recorderIndicatorEl.value! as HTMLElement, input: recorderEl.value! as HTMLInputElement })
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
</script>
|
|
211
|
+
<script lang="ts">
|
|
212
|
+
type RealProps =
|
|
213
|
+
& LinkableByIdProps
|
|
214
|
+
& BaseInteractiveProps
|
|
215
|
+
& LabelProps
|
|
216
|
+
& {
|
|
217
|
+
border?: boolean
|
|
218
|
+
/** A value to display while recording, if none given the i18n `recorder.recording` key is used. */
|
|
219
|
+
recordingValue?: string
|
|
220
|
+
/** A title to display on the input div while recording. Is also used as the aria-description. */
|
|
221
|
+
recordingTitle?: string
|
|
222
|
+
/**
|
|
223
|
+
* The recorder object is a series of event listeners to attach to the input div while recording is started. If you need to bind directly to the element, see the `binders` prop.
|
|
224
|
+
*
|
|
225
|
+
* The listeners are then unbound when recording is set to false again.
|
|
226
|
+
*
|
|
227
|
+
* Note that the component does not handle the setting of `recording` (unless the component is disabled), `modelValue`, or `recordingValue` at all and has no mechanism for cancelling a recording. It is left to the recorder listeners and any `recorder:*` handlers to determine what to do.
|
|
228
|
+
*/
|
|
229
|
+
recorder?: undefined | Record<string, any>
|
|
230
|
+
/** This provides a way to manually attach/remove event listeners to/from the element. It is an alternative to the `recorder` prop, see it for more details. Both cannot be specified at the same time.*/
|
|
231
|
+
binders?: undefined | { bind: (el: HTMLElement) => void, unbind: (el: HTMLElement) => void }
|
|
232
|
+
/** The id of the element. If not provided, the id will be generated automatically. */
|
|
233
|
+
id?: string
|
|
234
|
+
}
|
|
197
235
|
|
|
236
|
+
interface Props
|
|
237
|
+
extends
|
|
238
|
+
/** @vue-ignore */
|
|
239
|
+
Partial<Omit<HTMLAttributes,"class"> & TailwindClassProp>,
|
|
240
|
+
RealProps
|
|
241
|
+
{ }
|
|
198
242
|
</script>
|
|
@@ -2,36 +2,34 @@
|
|
|
2
2
|
<div :id="id"
|
|
3
3
|
tabindex="-1"
|
|
4
4
|
:class="twMerge(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
)"
|
|
9
|
-
v-bind="{
|
|
5
|
+
(showOutline ? 'group outlined outlined-visible' : '[&_*]:outline-hidden'),
|
|
6
|
+
darkMode && ' dark',
|
|
7
|
+
($attrs['wrapperAttrs'] as any)?.class
|
|
8
|
+
)"
|
|
9
|
+
v-bind="{ ...($attrs['wrapperAttrs']), attrs:undefined, class: undefined }"
|
|
10
10
|
:ref="handleRef"
|
|
11
11
|
>
|
|
12
12
|
<!-- id root is useful for teleports, so they are at the topmost level where they can still be styled -->
|
|
13
13
|
<!-- See TestControls for why the margins here -->
|
|
14
14
|
<div
|
|
15
15
|
id="root"
|
|
16
|
-
v-bind="{ ...$attrs.attrs, class:
|
|
17
|
-
:class="twMerge(
|
|
18
|
-
`
|
|
16
|
+
v-bind="{ ...$attrs.attrs, class: undefined, wrapperAttrs: undefined }"
|
|
17
|
+
:class="twMerge( `
|
|
19
18
|
dark:bg-fg
|
|
20
19
|
dark:text-bg
|
|
21
20
|
bg-bg
|
|
22
21
|
text-fg
|
|
23
22
|
`,
|
|
24
|
-
|
|
23
|
+
testWrapperMode && `
|
|
25
24
|
px-10
|
|
26
25
|
pb-10
|
|
27
26
|
`,
|
|
28
|
-
|
|
27
|
+
!testWrapperMode && `
|
|
29
28
|
min-h-dvh
|
|
30
29
|
flex
|
|
31
30
|
flex-col
|
|
32
31
|
`,
|
|
33
|
-
|
|
34
|
-
)"
|
|
32
|
+
($attrs as any).attrs?.class)"
|
|
35
33
|
>
|
|
36
34
|
<TestControls v-if="testWrapperMode" :show-outline="showOutline"/>
|
|
37
35
|
<Notifications v-if="useNotifications && isClientSide"/>
|
|
@@ -40,73 +38,102 @@
|
|
|
40
38
|
</div>
|
|
41
39
|
</template>
|
|
42
40
|
|
|
43
|
-
<script setup>
|
|
44
|
-
import { unreachable } from "@alanscodelog/utils/unreachable.js"
|
|
45
|
-
import {} from "metamorphosis"
|
|
46
|
-
import { computed, onBeforeUnmount, onMounted, ref, toRaw } from "vue"
|
|
47
|
-
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import
|
|
58
|
-
import
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { unreachable } from "@alanscodelog/utils/unreachable.js"
|
|
43
|
+
import { type Theme } from "metamorphosis"
|
|
44
|
+
import { type ComponentPublicInstance, computed, onBeforeUnmount, onMounted, ref, toRaw } from "vue"
|
|
45
|
+
|
|
46
|
+
import { useAccesibilityOutline } from "../../composables/useAccesibilityOutline.js"
|
|
47
|
+
import { useDivideAttrs } from "../../composables/useDivideAttrs.js"
|
|
48
|
+
import { useNotificationHandler } from "../../composables/useNotificationHandler.js"
|
|
49
|
+
import { useSetupDarkMode } from "../../composables/useSetupDarkMode.js"
|
|
50
|
+
import { useSetupI18n } from "../../composables/useSetupI18n.js"
|
|
51
|
+
import { useSetupLocale } from "../../composables/useSetupLocale.js"
|
|
52
|
+
import { useShowDevOnlyKey } from "../../composables/useShowDevOnlyKey.js"
|
|
53
|
+
import { NotificationHandler } from "../../helpers/NotificationHandler.js"
|
|
54
|
+
import { theme as defaultTheme } from "../../theme.js"
|
|
55
|
+
import { twMerge } from "../../utils/twMerge.js"
|
|
56
|
+
import Notifications from "../LibNotifications/LibNotifications.vue"
|
|
57
|
+
import TestControls from "../TestControls/TestControls.vue"
|
|
58
|
+
|
|
59
|
+
const $attrs = useDivideAttrs(["wrapper"])
|
|
60
|
+
|
|
61
|
+
defineOptions({ name: "root", inheritAttrs: false, suspensible: false })
|
|
62
|
+
const props = withDefaults(defineProps<{
|
|
63
|
+
theme?: Theme
|
|
64
|
+
outline?: boolean
|
|
65
|
+
forceOutline?: boolean
|
|
66
|
+
testWrapperMode?: boolean
|
|
67
|
+
id?: string
|
|
68
|
+
/** You can set a ref to the root element by passing :getRef="_ => el = _" */
|
|
69
|
+
getRef?: (el: HTMLElement | null) => void
|
|
70
|
+
/** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
|
|
71
|
+
isClientSide?: boolean
|
|
72
|
+
useBuiltinTranslations?: boolean
|
|
73
|
+
useNotifications?: boolean
|
|
74
|
+
notificationHandler?: NotificationHandler
|
|
75
|
+
}>(), {
|
|
76
|
+
theme: undefined,
|
|
77
|
+
testWrapperMode: false,
|
|
78
|
+
outline: true,
|
|
79
|
+
forceOutline: false,
|
|
80
|
+
id: "app",
|
|
81
|
+
getRef: undefined,
|
|
82
|
+
isClientSide: true,
|
|
83
|
+
useBuiltinTranslations: true,
|
|
84
|
+
useNotifications: true,
|
|
85
|
+
notificationHandler: undefined
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const el = ref<HTMLElement | null>(null)
|
|
89
|
+
|
|
90
|
+
function handleRef(_: Element | ComponentPublicInstance | null): void {
|
|
91
|
+
if (_ !== null && !(_ instanceof HTMLElement)) unreachable()
|
|
92
|
+
el.value = _
|
|
93
|
+
props.getRef?.(_)
|
|
78
94
|
}
|
|
95
|
+
|
|
79
96
|
if (props.useNotifications) {
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
const handler = props.notificationHandler ?? new NotificationHandler()
|
|
98
|
+
useNotificationHandler(handler, props.isClientSide)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const autoOutline = useAccesibilityOutline(el).outline
|
|
102
|
+
|
|
103
|
+
const showOutline = computed(() => (props.outline && autoOutline.value) || props.forceOutline)
|
|
104
|
+
|
|
105
|
+
const theme = computed(() => props.theme ?? defaultTheme)
|
|
106
|
+
const themeCb = (): void => {
|
|
107
|
+
toRaw(theme.value).attach(el.value!)
|
|
82
108
|
}
|
|
83
|
-
const autoOutline = useAccesibilityOutline(el).outline;
|
|
84
|
-
const showOutline = computed(() => props.outline && autoOutline.value || props.forceOutline);
|
|
85
|
-
const theme = computed(() => props.theme ?? defaultTheme);
|
|
86
|
-
const themeCb = () => {
|
|
87
|
-
toRaw(theme.value).attach(el.value);
|
|
88
|
-
};
|
|
89
109
|
if (props.isClientSide) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
onMounted(() => {
|
|
111
|
+
toRaw(theme.value).on("change", themeCb)
|
|
112
|
+
themeCb()
|
|
113
|
+
})
|
|
114
|
+
onBeforeUnmount(() => {
|
|
115
|
+
toRaw(theme.value).off("change", themeCb)
|
|
116
|
+
})
|
|
97
117
|
}
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
118
|
+
|
|
119
|
+
const darkModeSetup = useSetupDarkMode({ isClientSide: props.isClientSide })
|
|
120
|
+
|
|
121
|
+
const darkMode = darkModeSetup.darkMode
|
|
122
|
+
|
|
123
|
+
useShowDevOnlyKey()
|
|
124
|
+
|
|
101
125
|
defineExpose({
|
|
102
|
-
|
|
103
|
-
})
|
|
126
|
+
darkMode: darkModeSetup,
|
|
127
|
+
})
|
|
128
|
+
|
|
104
129
|
if (props.useBuiltinTranslations) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
const { languageLocale } = useSetupLocale()
|
|
131
|
+
void useSetupI18n({
|
|
132
|
+
locale: languageLocale,
|
|
133
|
+
useBuiltinTranslations: true,
|
|
134
|
+
useDummyMessageSetWhileLoading: true,
|
|
135
|
+
})
|
|
111
136
|
}
|
|
137
|
+
|
|
112
138
|
</script>
|
|
139
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/vue3";
|
|
2
|
+
import * as components from "../index.js.js";
|
|
3
|
+
declare const meta: {
|
|
4
|
+
component: any;
|
|
5
|
+
title: string;
|
|
6
|
+
args: {
|
|
7
|
+
modelValue: string;
|
|
8
|
+
placeholder: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof components.LibSimpleInput>;
|
|
13
|
+
/** Input */
|
|
14
|
+
export declare const Primary: Story;
|
|
15
|
+
/** Has more reasonable min-width inside a flexbox. */
|
|
16
|
+
export declare const InsideAFlexbox: Story;
|
|
17
|
+
export declare const Disabled: any;
|
|
18
|
+
export declare const Readonly: any;
|
|
19
|
+
export declare const Invalid: any;
|
|
20
|
+
/**
|
|
21
|
+
* Intended for being wrapped.
|
|
22
|
+
* Should not have any border or focus outline styles.
|
|
23
|
+
*/
|
|
24
|
+
export declare const Borderless: any;
|
|
25
|
+
export declare const Numerical: any;
|
|
26
|
+
export declare const NumericalInsideAFlexbox: any;
|