@poveste/controls 0.1.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 (92) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +5 -0
  3. package/dist/components/HstCopyIcon.story.vue.d.ts +2 -0
  4. package/dist/components/HstCopyIcon.vue.d.ts +7 -0
  5. package/dist/components/HstWrapper.vue.d.ts +20 -0
  6. package/dist/components/button/HstButton.story.vue.d.ts +2 -0
  7. package/dist/components/button/HstButton.vue.d.ts +13 -0
  8. package/dist/components/button/HstButtonGroup.story.vue.d.ts +2 -0
  9. package/dist/components/button/HstButtonGroup.vue.d.ts +22 -0
  10. package/dist/components/checkbox/HstCheckbox.story.vue.d.ts +2 -0
  11. package/dist/components/checkbox/HstCheckbox.vue.d.ts +19 -0
  12. package/dist/components/checkbox/HstCheckboxList.story.vue.d.ts +2 -0
  13. package/dist/components/checkbox/HstCheckboxList.vue.d.ts +22 -0
  14. package/dist/components/checkbox/HstSimpleCheckbox.story.vue.d.ts +2 -0
  15. package/dist/components/checkbox/HstSimpleCheckbox.vue.d.ts +12 -0
  16. package/dist/components/colorselect/HstColorSelect.story.vue.d.ts +2 -0
  17. package/dist/components/colorselect/HstColorSelect.vue.d.ts +19 -0
  18. package/dist/components/design-tokens/HstColorShades.story.vue.d.ts +2 -0
  19. package/dist/components/design-tokens/HstColorShades.vue.d.ts +19 -0
  20. package/dist/components/design-tokens/HstTokenGrid.story.vue.d.ts +2 -0
  21. package/dist/components/design-tokens/HstTokenGrid.vue.d.ts +26 -0
  22. package/dist/components/design-tokens/HstTokenList.story.vue.d.ts +2 -0
  23. package/dist/components/design-tokens/HstTokenList.vue.d.ts +21 -0
  24. package/dist/components/json/HstJson.story.vue.d.ts +2 -0
  25. package/dist/components/json/HstJson.vue.d.ts +19 -0
  26. package/dist/components/number/HstNumber.story.vue.d.ts +2 -0
  27. package/dist/components/number/HstNumber.vue.d.ts +19 -0
  28. package/dist/components/radio/HstRadio.story.vue.d.ts +2 -0
  29. package/dist/components/radio/HstRadio.vue.d.ts +22 -0
  30. package/dist/components/select/CustomSelect.vue.d.ts +22 -0
  31. package/dist/components/select/HstSelect.story.vue.d.ts +2 -0
  32. package/dist/components/select/HstSelect.vue.d.ts +22 -0
  33. package/dist/components/slider/HstSlider.story.vue.d.ts +2 -0
  34. package/dist/components/slider/HstSlider.vue.d.ts +16 -0
  35. package/dist/components/text/HstText.story.vue.d.ts +2 -0
  36. package/dist/components/text/HstText.vue.d.ts +19 -0
  37. package/dist/components/textarea/HstTextarea.story.vue.d.ts +2 -0
  38. package/dist/components/textarea/HstTextarea.vue.d.ts +19 -0
  39. package/dist/index.d.ts +1047 -0
  40. package/dist/index.es.css +1 -0
  41. package/dist/index.es.js +16392 -0
  42. package/dist/style-standalone.css +973 -0
  43. package/dist/types.d.ts +4 -0
  44. package/dist/utils.d.ts +1 -0
  45. package/package.json +72 -0
  46. package/postcss.config.cjs +8 -0
  47. package/src/components/HstCopyIcon.story.vue +26 -0
  48. package/src/components/HstCopyIcon.vue +38 -0
  49. package/src/components/HstWrapper.vue +41 -0
  50. package/src/components/button/HstButton.story.vue +30 -0
  51. package/src/components/button/HstButton.vue +26 -0
  52. package/src/components/button/HstButtonGroup.story.vue +79 -0
  53. package/src/components/button/HstButtonGroup.vue +71 -0
  54. package/src/components/checkbox/HstCheckbox.spec.ts +28 -0
  55. package/src/components/checkbox/HstCheckbox.story.vue +47 -0
  56. package/src/components/checkbox/HstCheckbox.vue +56 -0
  57. package/src/components/checkbox/HstCheckboxList.story.vue +49 -0
  58. package/src/components/checkbox/HstCheckboxList.vue +82 -0
  59. package/src/components/checkbox/HstSimpleCheckbox.story.vue +28 -0
  60. package/src/components/checkbox/HstSimpleCheckbox.vue +82 -0
  61. package/src/components/checkbox/__snapshots__/HstCheckbox.spec.ts.snap +5 -0
  62. package/src/components/colorselect/HstColorSelect.story.vue +28 -0
  63. package/src/components/colorselect/HstColorSelect.vue +89 -0
  64. package/src/components/design-tokens/HstColorShades.story.vue +392 -0
  65. package/src/components/design-tokens/HstColorShades.vue +101 -0
  66. package/src/components/design-tokens/HstTokenGrid.story.vue +36 -0
  67. package/src/components/design-tokens/HstTokenGrid.vue +88 -0
  68. package/src/components/design-tokens/HstTokenList.story.vue +81 -0
  69. package/src/components/design-tokens/HstTokenList.vue +62 -0
  70. package/src/components/json/HstJson.story.vue +41 -0
  71. package/src/components/json/HstJson.vue +151 -0
  72. package/src/components/number/HstNumber.story.vue +42 -0
  73. package/src/components/number/HstNumber.vue +95 -0
  74. package/src/components/radio/HstRadio.story.vue +51 -0
  75. package/src/components/radio/HstRadio.vue +109 -0
  76. package/src/components/select/CustomSelect.vue +119 -0
  77. package/src/components/select/HstSelect.story.vue +161 -0
  78. package/src/components/select/HstSelect.vue +40 -0
  79. package/src/components/slider/HstSlider.story.vue +37 -0
  80. package/src/components/slider/HstSlider.vue +103 -0
  81. package/src/components/text/HstText.story.vue +63 -0
  82. package/src/components/text/HstText.vue +44 -0
  83. package/src/components/textarea/HstTextarea.story.vue +35 -0
  84. package/src/components/textarea/HstTextarea.vue +44 -0
  85. package/src/end.d.ts +8 -0
  86. package/src/index.ts +54 -0
  87. package/src/style/main.css +17 -0
  88. package/src/types.ts +4 -0
  89. package/src/utils.ts +12 -0
  90. package/tailwind.config.cjs +13 -0
  91. package/tsconfig.json +53 -0
  92. package/vite.config.ts +46 -0
@@ -0,0 +1,101 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstColorShades',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import type { CSSProperties } from 'vue'
9
+ import { VTooltip as vTooltip } from 'floating-vue'
10
+ import { computed, ref } from 'vue'
11
+ import HstCopyIcon from '../HstCopyIcon.vue'
12
+
13
+ const props = defineProps<{
14
+ shades: Record<string, any>
15
+ getName?: (key: string, color: string) => string
16
+ search?: string
17
+ }>()
18
+
19
+ function flattenShades(shades: Record<string, any>, path = ''): Record<string, string> {
20
+ return Object.entries(shades).reduce((acc, [key, color]) => {
21
+ const nextPath = path ? key === 'DEFAULT' ? path : `${path}-${key}` : key
22
+ const obj = typeof color === 'object' ? flattenShades(color, nextPath) : { [nextPath]: color }
23
+ return { ...acc, ...obj }
24
+ }, {})
25
+ }
26
+
27
+ const shadesWithName = computed(() => {
28
+ const shades = props.shades
29
+ const getName = props.getName
30
+ const flatShades = flattenShades(shades)
31
+ return Object.entries(flatShades).map(([key, color]) => {
32
+ const name = getName ? getName(key, color) : key
33
+ return {
34
+ key,
35
+ color,
36
+ name,
37
+ }
38
+ })
39
+ })
40
+
41
+ const displayedShades = computed(() => {
42
+ let list = shadesWithName.value
43
+ if (props.search) {
44
+ const reg = new RegExp(props.search, 'i')
45
+ list = list.filter(({ name }) => reg.test(name))
46
+ }
47
+ return list
48
+ })
49
+
50
+ const hover = ref<string>(null)
51
+ </script>
52
+
53
+ <template>
54
+ <div
55
+ v-if="displayedShades.length"
56
+ class="histoire-color-shades htw-grid htw-gap-4 htw-grid-cols-[repeat(auto-fill,minmax(200px,1fr))] htw-m-4"
57
+ >
58
+ <div
59
+ v-for="shade of displayedShades"
60
+ :key="shade.key"
61
+ class="htw-flex htw-flex-col htw-gap-2"
62
+ @mouseenter="hover = shade.key"
63
+ @mouseleave="hover = null"
64
+ >
65
+ <slot
66
+ :color="shade.color"
67
+ >
68
+ <div
69
+ class="htw-rounded-full htw-w-16 htw-h-16"
70
+ :style="{
71
+ backgroundColor: shade.color,
72
+ } as CSSProperties"
73
+ />
74
+ </slot>
75
+ <div>
76
+ <div class="htw-flex htw-gap-1">
77
+ <pre
78
+ v-tooltip="shade.name.length > 23 ? shade.name : ''"
79
+ class="htw-my-0 htw-truncate htw-shrink"
80
+ >{{ shade.name }}</pre>
81
+ <HstCopyIcon
82
+ v-if="hover === shade.key"
83
+ :content="shade.name"
84
+ class="htw-flex-none"
85
+ />
86
+ </div>
87
+ <div class="htw-flex htw-gap-1">
88
+ <pre
89
+ v-tooltip="shade.color.length > 23 ? shade.color : ''"
90
+ class="htw-my-0 htw-opacity-50 htw-truncate htw-shrink"
91
+ >{{ shade.color }}</pre>
92
+ <HstCopyIcon
93
+ v-if="hover === shade.key"
94
+ :content="shade.color"
95
+ class="htw-flex-none"
96
+ />
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </template>
@@ -0,0 +1,36 @@
1
+ <script lang="ts" setup>
2
+ import HstTokenGrid from './HstTokenGrid.vue'
3
+
4
+ const tokens = {
5
+ 'sm': 'filter: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05))',
6
+ 'DEFAULT': 'filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06))',
7
+ 'md': 'filter: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))',
8
+ 'lg': 'filter: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1))',
9
+ 'xl': 'filter: drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08))',
10
+ '2xl': 'filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15))',
11
+ 'none': 'filter: drop-shadow(0 0 #0000)',
12
+ }
13
+ </script>
14
+
15
+ <template>
16
+ <Story
17
+ title="HstTokenGrid"
18
+ group="design-system"
19
+ responsive-disabled
20
+ >
21
+ <Variant title="default">
22
+ <HstTokenGrid
23
+ :tokens="tokens"
24
+ :get-name="key => key === 'DEFAULT' ? 'drop-shadow' : `drop-shadow-${key}`"
25
+ :col-size="160"
26
+ >
27
+ <template #default="{ token }">
28
+ <div
29
+ class="htw-w-32 htw-h-32 htw-bg-white dark:htw-bg-black htw-rounded htw-mb-2"
30
+ :style="token.value"
31
+ />
32
+ </template>
33
+ </HstTokenGrid>
34
+ </Variant>
35
+ </Story>
36
+ </template>
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstTokenGrid',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { VTooltip as vTooltip } from 'floating-vue'
9
+ import { computed, ref } from 'vue'
10
+ import HstCopyIcon from '../HstCopyIcon.vue'
11
+
12
+ const props = withDefaults(defineProps<{
13
+ tokens: Record<string, string | number | any[] | Record<string, any>>
14
+ colSize?: number
15
+ getName?: (key: string, value: string | number | any[] | Record<string, any>) => string
16
+ }>(), {
17
+ colSize: 180,
18
+ getName: null,
19
+ })
20
+
21
+ const processedTokens = computed(() => {
22
+ const list = props.tokens
23
+ const getName = props.getName
24
+ return Object.entries(list).map(([key, value]) => {
25
+ const name = getName ? getName(key, value) : key
26
+ return {
27
+ key,
28
+ name,
29
+ value: typeof value === 'number' ? value.toString() : value,
30
+ }
31
+ })
32
+ })
33
+
34
+ const colSizePx = computed(() => `${props.colSize}px`)
35
+
36
+ const hover = ref<string>(null)
37
+ </script>
38
+
39
+ <template>
40
+ <div
41
+ class="histoire-token-grid htw-bind-col-size htw-grid htw-gap-4 htw-m-4"
42
+ :style="{
43
+ '--histoire-col-size': colSizePx,
44
+ }"
45
+ >
46
+ <div
47
+ v-for="token of processedTokens"
48
+ :key="token.key"
49
+ class="htw-flex htw-flex-col htw-gap-2"
50
+ @mouseenter="hover = token.key"
51
+ @mouseleave="hover = null"
52
+ >
53
+ <slot
54
+ :token="token"
55
+ />
56
+ <div>
57
+ <div class="htw-flex htw-gap-1">
58
+ <pre
59
+ v-tooltip="token.name.length > colSize / 8 ? token.name : ''"
60
+ class="htw-my-0 htw-truncate htw-shrink"
61
+ >{{ token.name }}</pre>
62
+ <HstCopyIcon
63
+ v-if="hover === token.key"
64
+ :content="token.name"
65
+ class="htw-flex-none"
66
+ />
67
+ </div>
68
+ <div class="htw-flex htw-gap-1">
69
+ <pre
70
+ v-tooltip="token.value.length > colSize / 8 ? token.value : ''"
71
+ class="htw-my-0 htw-opacity-50 htw-truncate htw-shrink"
72
+ >{{ token.value }}</pre>
73
+ <HstCopyIcon
74
+ v-if="hover === token.key"
75
+ :content="typeof token.value === 'string' ? token.value : JSON.stringify(token.value)"
76
+ class="htw-flex-none"
77
+ />
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </template>
83
+
84
+ <style>
85
+ .htw-bind-col-size {
86
+ grid-template-columns: repeat(auto-fill, minmax(var(--histoire-col-size), 1fr));
87
+ }
88
+ </style>
@@ -0,0 +1,81 @@
1
+ <script lang="ts" setup>
2
+ import type { CSSProperties } from 'vue'
3
+ import HstTokenList from './HstTokenList.vue'
4
+
5
+ const tokens = {
6
+ '0': 0,
7
+ '1': '0.25rem',
8
+ '2': '0.5rem',
9
+ '3': '0.75rem',
10
+ '4': '1rem',
11
+ '5': '1.25rem',
12
+ '6': '1.5rem',
13
+ '8': '2rem',
14
+ '10': '2.5rem',
15
+ '12': '3rem',
16
+ '16': '4rem',
17
+ '20': '5rem',
18
+ '24': '6rem',
19
+ '32': '8rem',
20
+ '40': '10rem',
21
+ '48': '12rem',
22
+ '56': '14rem',
23
+ '64': '16rem',
24
+ 'auto': 'auto',
25
+ 'px': '1px',
26
+ 'screen': '100vw',
27
+ '1/2': '50%',
28
+ '1/3': '33.333333%',
29
+ '2/3': '66.666667%',
30
+ '1/4': '25%',
31
+ '2/4': '50%',
32
+ '3/4': '75%',
33
+ '1/5': '20%',
34
+ '2/5': '40%',
35
+ '3/5': '60%',
36
+ '4/5': '80%',
37
+ '1/6': '16.666667%',
38
+ '2/6': '33.333333%',
39
+ '3/6': '50%',
40
+ '4/6': '66.666667%',
41
+ '5/6': '83.333333%',
42
+ '1/12': '8.333333%',
43
+ '2/12': '16.666667%',
44
+ '3/12': '25%',
45
+ '4/12': '33.333333%',
46
+ '5/12': '41.666667%',
47
+ '6/12': '50%',
48
+ '7/12': '58.333333%',
49
+ '8/12': '66.666667%',
50
+ '9/12': '75%',
51
+ '10/12': '83.333333%',
52
+ '11/12': '91.666667%',
53
+ 'full': '100%',
54
+ }
55
+ </script>
56
+
57
+ <template>
58
+ <Story
59
+ title="HstTokenList"
60
+ group="design-system"
61
+ responsive-disabled
62
+ >
63
+ <Variant title="default">
64
+ <HstTokenList
65
+ :tokens="tokens"
66
+ :get-name="key => `w-${key}`"
67
+ >
68
+ <template #default="{ token }">
69
+ <div class="htw-bg-gray-500/10">
70
+ <div
71
+ class="htw-h-20 htw-bg-gray-500/50"
72
+ :style="{
73
+ width: token.value as string,
74
+ } as CSSProperties"
75
+ />
76
+ </div>
77
+ </template>
78
+ </HstTokenList>
79
+ </Variant>
80
+ </Story>
81
+ </template>
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstTokenList',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { computed, ref } from 'vue'
9
+ import HstCopyIcon from '../HstCopyIcon.vue'
10
+
11
+ const props = defineProps<{
12
+ tokens: Record<string, string | number | any[] | Record<string, any>>
13
+ getName?: (key: string, value: string | number | any[] | Record<string, any>) => string
14
+ }>()
15
+
16
+ const processedTokens = computed(() => {
17
+ const list = props.tokens
18
+ const getName = props.getName
19
+ return Object.entries(list).map(([key, value]) => {
20
+ const name = getName ? getName(key, value) : key
21
+ return {
22
+ key,
23
+ name,
24
+ value: typeof value === 'number' ? value.toString() : value,
25
+ }
26
+ })
27
+ })
28
+
29
+ const hover = ref<string>(null)
30
+ </script>
31
+
32
+ <template>
33
+ <div
34
+ v-for="token of processedTokens"
35
+ :key="token.key"
36
+ class="histoire-token-list htw-flex htw-flex-col htw-gap-2 htw-my-8"
37
+ @mouseenter="hover = token.key"
38
+ @mouseleave="hover = null"
39
+ >
40
+ <slot
41
+ :token="token"
42
+ />
43
+ <div class="htw-mx-4">
44
+ <div class="htw-flex htw-gap-1">
45
+ <pre class="htw-my-0 htw-truncate htw-shrink">{{ token.name }}</pre>
46
+ <HstCopyIcon
47
+ v-if="hover === token.key"
48
+ :content="token.name"
49
+ class="htw-flex-none"
50
+ />
51
+ </div>
52
+ <div class="htw-flex htw-gap-1">
53
+ <pre class="htw-my-0 htw-opacity-50 htw-truncate htw-shrink">{{ token.value }}</pre>
54
+ <HstCopyIcon
55
+ v-if="hover === token.key"
56
+ :content="typeof token.value === 'string' ? token.value : JSON.stringify(token.value)"
57
+ class="htw-flex-none"
58
+ />
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </template>
@@ -0,0 +1,41 @@
1
+ <script lang="ts" setup>
2
+ import HstJson from './HstJson.vue'
3
+
4
+ function initState() {
5
+ return {
6
+ film: {
7
+ year: 2017,
8
+ title: 'Blade Runner 2049',
9
+ actors: ['Ryan Gosling', 'Harrison Ford', 'Ana de Armas', 'Sylvia Hoeks'],
10
+ },
11
+ }
12
+ }
13
+ </script>
14
+
15
+ <template>
16
+ <Story
17
+ title="HstJson"
18
+ group="controls"
19
+ :layout="{ type: 'single', iframe: false }"
20
+ >
21
+ <Variant
22
+ title="default"
23
+ :init-state="initState"
24
+ >
25
+ <template #default="{ state }">
26
+ <HstJson
27
+ v-model="state.film"
28
+ title="Textarea"
29
+ />
30
+ <pre>{{ state.film }}</pre>
31
+ </template>
32
+
33
+ <template #controls="{ state }">
34
+ <HstJson
35
+ v-model="state.film"
36
+ title="Text"
37
+ />
38
+ </template>
39
+ </Variant>
40
+ </Story>
41
+ </template>
@@ -0,0 +1,151 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstJson',
4
+ inheritAttrs: false,
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts" setup>
9
+ import type {
10
+ ViewUpdate,
11
+ } from '@codemirror/view'
12
+ import { defaultKeymap } from '@codemirror/commands'
13
+ import { json } from '@codemirror/lang-json'
14
+ import {
15
+ bracketMatching,
16
+ defaultHighlightStyle,
17
+ foldGutter,
18
+ foldKeymap,
19
+ indentOnInput,
20
+ syntaxHighlighting,
21
+ } from '@codemirror/language'
22
+ import { lintKeymap } from '@codemirror/lint'
23
+ import { Compartment } from '@codemirror/state'
24
+ import { oneDarkHighlightStyle, oneDarkTheme } from '@codemirror/theme-one-dark'
25
+ import {
26
+ EditorView,
27
+ highlightActiveLine,
28
+ highlightActiveLineGutter,
29
+ highlightSpecialChars,
30
+ keymap,
31
+ } from '@codemirror/view'
32
+ import { Icon } from '@iconify/vue'
33
+ import { VTooltip as vTooltip } from 'floating-vue'
34
+ import { onMounted, ref, watch, watchEffect } from 'vue'
35
+ import { isDark } from '../../utils'
36
+ import HstWrapper from '../HstWrapper.vue'
37
+
38
+ const props = defineProps<{
39
+ title?: string
40
+ modelValue: unknown
41
+ }>()
42
+
43
+ const emit = defineEmits({
44
+ 'update:modelValue': (newValue: unknown) => true,
45
+ })
46
+
47
+ let editorView: EditorView
48
+ const internalValue = ref('')
49
+ const invalidValue = ref(false)
50
+ const editorElement = ref<HTMLInputElement>()
51
+
52
+ const themes = {
53
+ light: [EditorView.baseTheme({}), syntaxHighlighting(defaultHighlightStyle)],
54
+ dark: [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)],
55
+ }
56
+
57
+ const themeConfig = new Compartment()
58
+
59
+ const extensions = [
60
+ highlightActiveLineGutter(),
61
+ highlightActiveLine(),
62
+ highlightSpecialChars(),
63
+ json(),
64
+ bracketMatching(),
65
+ indentOnInput(),
66
+ foldGutter(),
67
+ keymap.of([
68
+ ...defaultKeymap,
69
+ ...foldKeymap,
70
+ ...lintKeymap,
71
+ ]),
72
+ EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
73
+ internalValue.value = viewUpdate.view.state.doc.toString()
74
+ }),
75
+ themeConfig.of(themes.light),
76
+ ]
77
+
78
+ onMounted(() => {
79
+ editorView = new EditorView({
80
+ doc: JSON.stringify(props.modelValue, null, 2),
81
+ extensions,
82
+ parent: editorElement.value,
83
+ })
84
+
85
+ watchEffect(() => {
86
+ editorView.dispatch({
87
+ effects: [
88
+ themeConfig.reconfigure(themes[isDark.value ? 'dark' : 'light']),
89
+ ],
90
+ })
91
+ })
92
+ })
93
+
94
+ watch(() => props.modelValue, () => {
95
+ let sameDocument
96
+
97
+ try {
98
+ sameDocument = (JSON.stringify(JSON.parse(internalValue.value)) === JSON.stringify(props.modelValue))
99
+ }
100
+ catch (e) {
101
+ sameDocument = false
102
+ }
103
+
104
+ if (!sameDocument) {
105
+ editorView.dispatch({ changes: [{ from: 0, to: editorView.state.doc.length, insert: JSON.stringify(props.modelValue, null, 2) }] })
106
+ }
107
+ }, { deep: true })
108
+
109
+ watch(() => internalValue.value, () => {
110
+ invalidValue.value = false
111
+ try {
112
+ emit('update:modelValue', JSON.parse(internalValue.value))
113
+ }
114
+ catch (e) {
115
+ invalidValue.value = true
116
+ }
117
+ })
118
+ </script>
119
+
120
+ <template>
121
+ <HstWrapper
122
+ :title="title"
123
+ class="histoire-json htw-cursor-text"
124
+ :class="$attrs.class"
125
+ :style="$attrs.style"
126
+ >
127
+ <div
128
+ ref="editorElement"
129
+ class="__histoire-json-code htw-w-full htw-border htw-border-solid htw-border-black/25 dark:htw-border-white/25 focus-within:htw-border-primary-500 dark:focus-within:htw-border-primary-500 htw-rounded-sm htw-box-border htw-overflow-auto htw-resize-y htw-min-h-32 htw-h-48 htw-relative"
130
+ v-bind="{ ...$attrs, class: null, style: null }"
131
+ />
132
+
133
+ <template #actions>
134
+ <Icon
135
+ v-if="invalidValue"
136
+ v-tooltip="'JSON error'"
137
+ icon="carbon:warning-alt"
138
+ class="htw-text-orange-500"
139
+ />
140
+
141
+ <slot name="actions" />
142
+ </template>
143
+ </HstWrapper>
144
+ </template>
145
+
146
+ <style scoped>
147
+ .__histoire-json-code :deep(.cm-editor) {
148
+ height: 100%;
149
+ min-width: 280px;
150
+ }
151
+ </style>
@@ -0,0 +1,42 @@
1
+ <script lang="ts" setup>
2
+ import HstNumber from './HstNumber.vue'
3
+
4
+ function initState() {
5
+ return {
6
+ count: 20,
7
+ step: 1,
8
+ }
9
+ }
10
+ </script>
11
+
12
+ <template>
13
+ <Story
14
+ title="HstNumber"
15
+ group="controls"
16
+ >
17
+ <Variant
18
+ title="default"
19
+ :init-state="initState"
20
+ >
21
+ <template #default="{ state }">
22
+ <HstNumber
23
+ v-model="state.count"
24
+ :step="state.step"
25
+ title="Count"
26
+ />
27
+ </template>
28
+
29
+ <template #controls="{ state }">
30
+ <HstNumber
31
+ v-model="state.count"
32
+ title="Count"
33
+ />
34
+
35
+ <HstNumber
36
+ v-model="state.step"
37
+ title="Step"
38
+ />
39
+ </template>
40
+ </Variant>
41
+ </Story>
42
+ </template>
@@ -0,0 +1,95 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'HstNumber',
4
+ inheritAttrs: false,
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts" setup>
9
+ import { computed, onUnmounted, ref } from 'vue'
10
+ import HstWrapper from '../HstWrapper.vue'
11
+
12
+ const props = defineProps<{
13
+ title?: string
14
+ modelValue?: number | null
15
+ }>()
16
+
17
+ const emit = defineEmits({
18
+ 'update:modelValue': (newValue: number) => true,
19
+ })
20
+
21
+ const numberModel = computed({
22
+ get: () => props.modelValue,
23
+ set: (value) => {
24
+ emit('update:modelValue', value)
25
+ },
26
+ })
27
+
28
+ const input = ref<HTMLInputElement>()
29
+
30
+ function focusAndSelect() {
31
+ input.value.focus()
32
+ input.value.select()
33
+ }
34
+
35
+ // Drag to modify
36
+
37
+ const isDragging = ref(false)
38
+ let startX: number
39
+ let startValue: number
40
+
41
+ function onMouseDown(event: MouseEvent) {
42
+ isDragging.value = true
43
+ startX = event.clientX
44
+ startValue = numberModel.value
45
+ window.addEventListener('mousemove', onMouseMove)
46
+ window.addEventListener('mouseup', stopDragging)
47
+ }
48
+
49
+ function onMouseMove(event: MouseEvent) {
50
+ let step = Number.parseFloat(input.value.step)
51
+ if (Number.isNaN(step)) {
52
+ step = 1
53
+ }
54
+ numberModel.value = startValue + Math.round((event.clientX - startX) / 10 / step) * step
55
+ }
56
+
57
+ function stopDragging() {
58
+ isDragging.value = false
59
+ window.removeEventListener('mousemove', onMouseMove)
60
+ window.removeEventListener('mouseup', stopDragging)
61
+ }
62
+
63
+ onUnmounted(() => {
64
+ stopDragging()
65
+ })
66
+ </script>
67
+
68
+ <template>
69
+ <HstWrapper
70
+ class="histoire-number htw-cursor-ew-resize htw-items-center"
71
+ :title="title"
72
+ :class="[
73
+ $attrs.class,
74
+ { 'htw-select-none': isDragging },
75
+ ]"
76
+ :style="$attrs.style"
77
+ @click="focusAndSelect"
78
+ @mousedown="onMouseDown"
79
+ >
80
+ <input
81
+ ref="input"
82
+ v-bind="{ ...$attrs, class: null, style: null }"
83
+ v-model.number="numberModel"
84
+ type="number"
85
+ :class="{
86
+ 'htw-select-none': isDragging,
87
+ }"
88
+ class="htw-text-inherit htw-bg-transparent htw-w-full htw-outline-none htw-pl-2 htw-py-1 -htw-my-1 htw-border htw-border-solid htw-border-black/25 dark:htw-border-white/25 focus:htw-border-primary-500 dark:focus:htw-border-primary-500 htw-rounded-sm htw-cursor-ew-resize htw-box-border"
89
+ >
90
+
91
+ <template #actions>
92
+ <slot name="actions" />
93
+ </template>
94
+ </HstWrapper>
95
+ </template>