@brickflow/ui 0.0.53 → 0.0.55

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 (30) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +42 -26
  3. package/dist/runtime/brickflow-ui.d.ts +4 -0
  4. package/dist/runtime/components/Button/icons.demo.vue +1 -1
  5. package/dist/runtime/components/Button/index.vue +42 -8
  6. package/dist/runtime/components/Button/size.demo.vue +3 -1
  7. package/dist/runtime/components/Button/state.demo.vue +3 -2
  8. package/dist/runtime/components/Button/variants.demo.vue +3 -2
  9. package/dist/runtime/components/Icon/basic.demo.vue +1 -1
  10. package/dist/runtime/components/Icon/gallery.demo.vue +1 -1
  11. package/dist/runtime/components/Icon/index.vue +0 -5
  12. package/dist/runtime/components/Input/align.demo.vue +25 -0
  13. package/dist/runtime/components/Input/auto-width.demo.vue +20 -0
  14. package/dist/runtime/components/Input/index.vue +316 -0
  15. package/dist/runtime/components/Input/label.demo.vue +20 -0
  16. package/dist/runtime/components/Input/readonly.demo.vue +26 -0
  17. package/dist/runtime/components/Input/size.demo.vue +51 -0
  18. package/dist/runtime/components/Input/type.demo.vue +25 -0
  19. package/dist/runtime/components/Input/variants.demo.vue +35 -0
  20. package/dist/runtime/components/InputNumber/basic.demo.vue +33 -0
  21. package/dist/runtime/components/InputNumber/index.vue +141 -0
  22. package/dist/runtime/components/TransitionSize/index.vue +121 -0
  23. package/dist/runtime/config.d.ts +0 -13
  24. package/dist/runtime/icon-font.d.ts +0 -1
  25. package/dist/runtime/nuxt-components.d.ts +7 -0
  26. package/dist/runtime/ui-config.d.ts +10 -0
  27. package/dist/runtime/ui.css +1 -1
  28. package/dist/runtime/utils/checkup.d.ts +32 -0
  29. package/dist/runtime/utils/checkup.js +67 -0
  30. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "configKey": "brickflowUi",
6
6
  "name": "@brickflow/ui",
7
- "version": "0.0.53",
7
+ "version": "0.0.55",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -265,7 +265,6 @@ const collectUiStylePathsFromCode = (code) => {
265
265
  }
266
266
  return paths.filter((path) => path.length > 0);
267
267
  };
268
- const collectUiStyleConfigPathsFromCode = (code, id) => collectUiStylePathsFromCode(code).map((path) => resolveUiStyleConfigPath(id, path));
269
268
  const collectUiConfigValuePaths = (value, prefix) => {
270
269
  if (typeof value === "string" || isBrickflowUiStyleReference(value)) {
271
270
  return [prefix];
@@ -345,18 +344,31 @@ const collectUiConfigSchemaEntriesFromCode = (code, id) => {
345
344
  }
346
345
  return entries;
347
346
  };
348
- const collectUiConfigStyleReferencePathsFromValue = (value, componentPath) => {
349
- if (isBrickflowUiStyleReference(value)) {
350
- return [[...componentPath, ...getBrickflowUiStyleReferencePath(value)]];
351
- }
352
- if (typeof value === "string") {
353
- return [];
347
+ const collectUiStylePathsWithConfig = (code, id, config) => {
348
+ const [componentName] = resolveUiStyleConfigPath(id, []);
349
+ const componentConfig = componentName ? config[componentName] : void 0;
350
+ const configReferences = [];
351
+ const addConfigReferences = (value, configPath = []) => {
352
+ if (isBrickflowUiStyleReference(value)) {
353
+ const referencePath = getBrickflowUiStyleReferencePath(value);
354
+ configReferences.push(`UI_STYLE.${referencePath.join(".")}`);
355
+ if (configPath.length >= 2 && referencePath.length >= configPath.length) {
356
+ const prefixLength = referencePath.length - configPath.length + 1;
357
+ const requiredPath = [...referencePath.slice(0, prefixLength), ...configPath.slice(1)];
358
+ configReferences.push(`UI_STYLE.${requiredPath.join(".")}`);
359
+ }
360
+ } else if (typeof value !== "string") {
361
+ for (const [key, childValue] of Object.entries(value)) {
362
+ addConfigReferences(childValue, [...configPath, key]);
363
+ }
364
+ }
365
+ };
366
+ if (componentConfig) {
367
+ addConfigReferences(componentConfig);
354
368
  }
355
- return Object.values(value).flatMap(
356
- (childValue) => collectUiConfigStyleReferencePathsFromValue(childValue, componentPath)
357
- );
369
+ return collectUiStylePathsFromCode(`${configReferences.join("\n")}
370
+ ${code}`);
358
371
  };
359
- const collectUiConfigStyleReferencePaths = (config) => Object.entries(config).flatMap(([key, value]) => collectUiConfigStyleReferencePathsFromValue(value, [key]));
360
372
  const renderInterfaceDeclaration = (name, paths, objectType, leafType = "string") => {
361
373
  const tree = createUiStylePathTree();
362
374
  for (const path of paths) {
@@ -575,10 +587,7 @@ const toComponentStyleKey = (path) => {
575
587
  const componentName = basename(dirname(path));
576
588
  return `${componentName[0]?.toLowerCase()}${componentName.slice(1)}`;
577
589
  };
578
- const collectComponentStyleValues = (source, componentPath, config) => [
579
- ...collectUiStylePathsFromCode(source),
580
- ...collectUiConfigStyleReferencePaths(config.uiConfig).filter(([component]) => component === toComponentStyleKey(componentPath)).map((path) => path.slice(1))
581
- ].map((path) => path.join(".")).filter(Boolean).filter((path, index, paths) => paths.indexOf(path) === index).sort().map((path) => {
590
+ const collectComponentStyleValues = (source, componentPath, config) => collectUiStylePathsWithConfig(source, componentPath, config.uiConfig).map((path) => path.join(".")).filter(Boolean).filter((path, index, paths) => paths.indexOf(path) === index).sort().map((path) => {
582
591
  const segments = path.split(".");
583
592
  const componentValue = readObjectPath(config.uiStyles, [toComponentStyleKey(componentPath), ...segments]);
584
593
  const globalValue = readObjectPath(config.uiStyles, segments);
@@ -736,15 +745,14 @@ const module$1 = defineNuxtModule({
736
745
  }))
737
746
  );
738
747
  const uiConfig = (await loadUiConfig()).uiConfig;
739
- const uiConfigStyleReferencePaths = collectUiConfigStyleReferencePaths(uiConfig);
740
- const paths = [
741
- ...fileContents.flatMap(({ content }) => collectUiStylePathsFromCode(content)),
742
- ...uiConfigStyleReferencePaths.map((path) => path.slice(1))
743
- ];
744
- const configPaths = fileContents.flatMap(
745
- ({ content, file }) => collectUiStyleConfigPathsFromCode(content, file)
748
+ const stylePaths = fileContents.map(({ content, file }) => ({
749
+ file,
750
+ paths: collectUiStylePathsWithConfig(content, file, uiConfig)
751
+ }));
752
+ const paths = stylePaths.flatMap(({ paths: componentPaths }) => componentPaths);
753
+ const configPaths = stylePaths.flatMap(
754
+ ({ file, paths: componentPaths }) => componentPaths.map((path) => resolveUiStyleConfigPath(file, path))
746
755
  );
747
- configPaths.push(...uiConfigStyleReferencePaths);
748
756
  const uiConfigPaths = fileContents.flatMap(
749
757
  ({ content, file }) => collectUiConfigPathsFromCode(content, file, uiConfig)
750
758
  );
@@ -783,15 +791,14 @@ const module$1 = defineNuxtModule({
783
791
  });
784
792
  nuxt.hook("prepare:types", ({ references }) => {
785
793
  references.push({ path: resolver.resolve("./runtime/config.d.ts") });
794
+ references.push({ path: resolver.resolve("./runtime/ui-config.d.ts") });
795
+ references.push({ path: resolver.resolve("./runtime/brickflow-ui.d.ts") });
786
796
  });
787
797
  const iconFontTemplate = addTemplate({
788
798
  filename: "brickflow/brickflow-ui-icons.mjs",
789
799
  getContents: async () => {
790
800
  const iconNames = iconFont ? await getIconFontNames(iconFont.inputDir) : [];
791
801
  return [
792
- "import { uiConfig } from '#brickflow-ui-config'",
793
- "",
794
- "export const buttonConfig = uiConfig.button",
795
802
  `export const iconNames = ${JSON.stringify(iconNames)}`,
796
803
  `export const firstIconName = ${JSON.stringify(iconNames[0] ?? "")}`,
797
804
  ""
@@ -814,6 +821,14 @@ const module$1 = defineNuxtModule({
814
821
  ""
815
822
  ].join("\n")
816
823
  });
824
+ const brickflowUiTemplate = addTemplate({
825
+ filename: "brickflow/brickflow-ui.mjs",
826
+ getContents: () => [
827
+ "export { UI_CONFIG, UI_STYLE, uiConfig, uiStyles } from './brickflow-ui-config.mjs'",
828
+ "export { firstIconName, iconNames } from './brickflow-ui-icons.mjs'",
829
+ ""
830
+ ].join("\n")
831
+ });
817
832
  const uiOptionsTemplate = addTemplate({
818
833
  filename: "brickflow/brickflow-ui-options.mjs",
819
834
  getContents: () => `export const uiThemeEnabled = ${JSON.stringify(options.theme === true)}
@@ -909,6 +924,7 @@ const module$1 = defineNuxtModule({
909
924
  ].join("\n");
910
925
  }
911
926
  });
927
+ nuxt.options.alias["#brickflow-ui"] = brickflowUiTemplate.dst;
912
928
  nuxt.options.alias["#brickflow-ui-config"] = uiConfigTemplate.dst;
913
929
  nuxt.options.alias["#brickflow-ui-catalog"] = uiCatalogTemplate.dst;
914
930
  nuxt.options.alias["#brickflow-ui-icons"] = iconFontTemplate.dst;
@@ -0,0 +1,4 @@
1
+ declare module '#brickflow-ui' {
2
+ export { UI_CONFIG, UI_STYLE, uiConfig, uiStyles } from '#brickflow-ui-config'
3
+ export { firstIconName, iconNames } from '#brickflow-ui-icons'
4
+ }
@@ -6,7 +6,7 @@
6
6
  </script>
7
7
 
8
8
  <script lang="ts" setup>
9
- import { firstIconName } from '#brickflow-ui-icons'
9
+ import { firstIconName } from '#brickflow-ui'
10
10
 
11
11
  import Button from './index.vue'
12
12
  </script>
@@ -21,6 +21,8 @@
21
21
 
22
22
  import Icon from '../Icon/index.vue'
23
23
  import BaseLink from '../Link/index.vue'
24
+ import TransitionSize from '../TransitionSize/index.vue'
25
+
24
26
  const props = withDefaults(
25
27
  defineProps<{
26
28
  block?: boolean
@@ -57,6 +59,8 @@
57
59
  type ButtonSize = Extract<keyof typeof sizeClasses, string>
58
60
  type ButtonVariant = Extract<keyof (typeof colorClasses)[ButtonColor], string>
59
61
 
62
+ const baseRef = shallowRef()
63
+
60
64
  const isButton = computed(() => props.to === undefined)
61
65
  const component = computed(() => (isButton.value ? 'button' : BaseLink))
62
66
  const disabled = computed(() => props.disabled || props.loading)
@@ -82,6 +86,7 @@
82
86
  <template>
83
87
  <component
84
88
  :is="component"
89
+ ref="baseRef"
85
90
  v-bind="animationTapClass ? { onPointerupPassive: handleTap } : {}"
86
91
  :to="props.to"
87
92
  :target="isButton ? undefined : props.target"
@@ -97,7 +102,23 @@
97
102
  }"
98
103
  @contextmenu.prevent
99
104
  >
105
+ <TransitionSize
106
+ v-if="!$slots.leading && !props.icon && !props.trailingIcon"
107
+ dimension="width"
108
+ :flexParent="baseRef?.$el || baseRef"
109
+ flexDirection="start"
110
+ >
111
+ <span v-if="props.loading">
112
+ <Icon
113
+ :name="UI_CONFIG.loadingIconName"
114
+ :class="[UI_STYLE.state.loading, sizeIconClasses[props.size]]"
115
+ aria-hidden="true"
116
+ />
117
+ </span>
118
+ </TransitionSize>
119
+
100
120
  <Transition
121
+ v-if="$slots.leading || props.icon"
101
122
  :name="UI_CONFIG.loadingIconTransition"
102
123
  mode="out-in"
103
124
  >
@@ -109,7 +130,7 @@
109
130
  />
110
131
  </span>
111
132
  <span
112
- v-else-if="$slots.leading || props.icon"
133
+ v-else
113
134
  :class="UI_STYLE.slot.leading"
114
135
  >
115
136
  <slot name="leading">
@@ -129,17 +150,30 @@
129
150
  <slot />
130
151
  </span>
131
152
 
132
- <span
153
+ <Transition
133
154
  v-if="$slots.trailing || props.trailingIcon"
134
- :class="UI_STYLE.slot.trailing"
155
+ :name="UI_CONFIG.loadingIconTransition"
156
+ mode="out-in"
135
157
  >
136
- <slot name="trailing">
158
+ <span v-if="props.loading && !props.icon">
137
159
  <Icon
138
- :name="props.trailingIcon!"
139
- :class="sizeIconClasses[props.size]"
160
+ :name="UI_CONFIG.loadingIconName"
161
+ :class="[UI_STYLE.state.loading, sizeIconClasses[props.size]]"
140
162
  aria-hidden="true"
141
163
  />
142
- </slot>
143
- </span>
164
+ </span>
165
+ <span
166
+ v-else
167
+ :class="UI_STYLE.slot.trailing"
168
+ >
169
+ <slot name="trailing">
170
+ <Icon
171
+ :name="props.trailingIcon!"
172
+ :class="sizeIconClasses[props.size]"
173
+ aria-hidden="true"
174
+ />
175
+ </slot>
176
+ </span>
177
+ </Transition>
144
178
  </component>
145
179
  </template>
@@ -5,9 +5,11 @@
5
5
  </script>
6
6
 
7
7
  <script lang="ts" setup>
8
- import { buttonConfig, firstIconName } from '#brickflow-ui-icons'
8
+ import { firstIconName, uiConfig } from '#brickflow-ui'
9
9
 
10
10
  import Button from './index.vue'
11
+
12
+ const buttonConfig = uiConfig.button
11
13
  </script>
12
14
 
13
15
  <template>
@@ -7,11 +7,12 @@
7
7
  <script lang="ts" setup>
8
8
  import { ref } from 'vue'
9
9
 
10
- import { buttonConfig, firstIconName } from '#brickflow-ui-icons'
10
+ import { firstIconName, uiConfig } from '#brickflow-ui'
11
11
 
12
12
  import Button from './index.vue'
13
13
 
14
- const firstColorClasses = Object.values(buttonConfig.colorClasses)[0] ?? {}
14
+ const buttonConfig = uiConfig.button
15
+ const firstColorClasses = buttonConfig.colorClasses[buttonConfig.colorDefault]
15
16
 
16
17
  const stateActive = ref(true)
17
18
  </script>
@@ -5,11 +5,12 @@
5
5
  </script>
6
6
 
7
7
  <script lang="ts" setup>
8
- import { buttonConfig } from '#brickflow-ui-icons'
8
+ import { uiConfig } from '#brickflow-ui'
9
9
 
10
10
  import Button from './index.vue'
11
11
 
12
- const firstColorClasses = Object.values(buttonConfig.colorClasses)[0] ?? {}
12
+ const buttonConfig = uiConfig.button
13
+ const firstColorClasses = buttonConfig.colorClasses[buttonConfig.colorDefault]
13
14
  </script>
14
15
 
15
16
  <template>
@@ -8,7 +8,7 @@
8
8
  <script lang="ts" setup>
9
9
  import { ref } from 'vue'
10
10
 
11
- import { firstIconName } from '#brickflow-ui-icons'
11
+ import { firstIconName } from '#brickflow-ui'
12
12
 
13
13
  import Icon from './index.vue'
14
14
 
@@ -8,7 +8,7 @@
8
8
  <script lang="ts" setup>
9
9
  import { computed, onBeforeUnmount, ref } from 'vue'
10
10
 
11
- import { iconNames } from '#brickflow-ui-icons'
11
+ import { iconNames } from '#brickflow-ui'
12
12
 
13
13
  import Icon from './index.vue'
14
14
 
@@ -1,10 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
3
 
4
- defineOptions({
5
- inheritAttrs: false,
6
- })
7
-
8
4
  const props = withDefaults(
9
5
  defineProps<{
10
6
  name: string
@@ -17,7 +13,6 @@
17
13
 
18
14
  <template>
19
15
  <i
20
- v-bind="$attrs"
21
16
  :class="classes"
22
17
  role="img"
23
18
  />
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Alignment',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { capitalize } from 'vue'
9
+
10
+ import Input from './index.vue'
11
+
12
+ const alignments = ['left', 'center', 'right'] as const
13
+ </script>
14
+
15
+ <template>
16
+ <div class="flex max-w-sm flex-col gap-3">
17
+ <Input
18
+ v-for="align in alignments"
19
+ :key="align"
20
+ :align="align"
21
+ :label="`${capitalize(align)} aligned`"
22
+ placeholder="Type here"
23
+ />
24
+ </div>
25
+ </template>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Auto Width',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import Input from './index.vue'
9
+ </script>
10
+
11
+ <template>
12
+ <div class="flex flex-col gap-3">
13
+ <Input
14
+ placeholder="Type here"
15
+ label="Auto Width"
16
+ autoWidth
17
+ class="max-w-sm"
18
+ />
19
+ </div>
20
+ </template>
@@ -0,0 +1,316 @@
1
+ <script lang="ts">
2
+ const UI_CONFIG = defineUiConfig<{
3
+ hidePasswordIconName: string
4
+ searchClearIconName: string
5
+ showPasswordIconName: string
6
+ sizeClasses: Record<
7
+ string,
8
+ {
9
+ description: string
10
+ field: string
11
+ fieldWrap: string
12
+ icon: string
13
+ label: string
14
+ }
15
+ >
16
+ sizeDefault: string
17
+ variantClasses: Record<
18
+ string,
19
+ {
20
+ description: string
21
+ field: string
22
+ fieldWrap: string
23
+ icon: string
24
+ label: string
25
+ }
26
+ >
27
+ variantDefault: string
28
+ }>()
29
+
30
+ export type InputConfig = typeof UI_CONFIG
31
+ </script>
32
+
33
+ <script setup lang="ts">
34
+ import { twMerge } from 'tailwind-merge'
35
+ import { computed, ref, shallowRef, useAttrs, useId, useSlots, watch } from 'vue'
36
+
37
+ import Icon from '../Icon/index.vue'
38
+
39
+ defineOptions({ inheritAttrs: false })
40
+
41
+ const props = withDefaults(
42
+ defineProps<{
43
+ align?: 'center' | 'left' | 'right'
44
+ autoWidth?: boolean
45
+ description?: string
46
+ disabled?: boolean
47
+ icon?: string
48
+ label?: string
49
+ onBlur?: (value: string, target: HTMLInputElement) => void
50
+ onChange?: (value: string, target: HTMLInputElement) => void
51
+ onFocus?: (value: string, target: HTMLInputElement | null) => void
52
+ onIconClick?: () => void
53
+ onInput?: (value: string, target: HTMLInputElement | null) => void
54
+ onTrailingIconClick?: () => void
55
+ placeholder?: string
56
+ readonly?: boolean
57
+ size?: InputSize
58
+ trailingIcon?: string
59
+ type?: 'decimal' | 'email' | 'numeric' | 'password' | 'search' | 'tel' | 'text' | 'url'
60
+ value?: null | string
61
+ variant?: InputVariant
62
+ }>(),
63
+ {
64
+ align: 'left',
65
+ autoWidth: false,
66
+ description: undefined,
67
+ disabled: false,
68
+ icon: undefined,
69
+ label: undefined,
70
+ onBlur: undefined,
71
+ onChange: undefined,
72
+ onFocus: undefined,
73
+ onIconClick: undefined,
74
+ onInput: undefined,
75
+ onTrailingIconClick: undefined,
76
+ placeholder: '',
77
+ readonly: false,
78
+ size: 'md',
79
+ trailingIcon: undefined,
80
+ type: 'text',
81
+ value: undefined,
82
+ variant: UI_CONFIG.variantDefault,
83
+ },
84
+ )
85
+
86
+ const model = shallowRef('')
87
+
88
+ const attrs = useAttrs()
89
+ const slots = useSlots()
90
+ const generatedId = useId()
91
+ const inputRef = ref<HTMLInputElement | null>(null)
92
+ const widthMeasureRef = ref<HTMLSpanElement | null>(null)
93
+ const measuredWidth = ref(0)
94
+ const hasFocus = ref(false)
95
+ const showPassword = ref(false)
96
+
97
+ const sizeClasses = UI_CONFIG.sizeClasses
98
+ const variantClasses = UI_CONFIG.variantClasses
99
+ type InputSize = Extract<keyof typeof sizeClasses, string>
100
+ type InputVariant = Extract<keyof typeof variantClasses, string>
101
+ const sizeClass = computed(() => sizeClasses[props.size])
102
+ const variantClass = computed(() => variantClasses[props.variant])
103
+
104
+ const fieldId = computed(() => String(attrs.id ?? generatedId))
105
+ const descriptionId = computed(() => `${fieldId.value}-description`)
106
+ const currentValue = computed(() => (props.value === undefined ? model.value : (props.value ?? '')))
107
+ const widthMeasureText = computed(() => currentValue.value || props.placeholder || ' ')
108
+ const isEmptyField = computed(() => {
109
+ return Boolean(currentValue.value)
110
+ })
111
+
112
+ const hasDescription = computed(() => Boolean(props.description || slots.description || slots.error))
113
+ const inputType = computed(() => {
114
+ return props.type === 'password' && !showPassword.value ? 'password' : 'text'
115
+ })
116
+ const classes = computed(() => [
117
+ UI_STYLE.base,
118
+ props.autoWidth && UI_STYLE.state.autoWidth,
119
+ props.disabled && UI_STYLE.state.disabled,
120
+ ])
121
+ const fieldWrapClasses = computed(() => [
122
+ twMerge(UI_STYLE.fieldWrap, sizeClass.value.fieldWrap, variantClass.value.fieldWrap),
123
+ props.readonly && UI_STYLE.state.readonly,
124
+ ])
125
+ const fieldClasses = computed(() => [
126
+ twMerge(UI_STYLE.field, sizeClass.value.field, variantClass.value.field),
127
+ props.align === 'center' && UI_STYLE.align.center,
128
+ props.align === 'right' && UI_STYLE.align.right,
129
+ props.autoWidth && UI_STYLE.state.autoWidthField,
130
+ ])
131
+ const labelClasses = computed(() => twMerge(UI_STYLE.label, sizeClass.value.label, variantClass.value.label))
132
+ const descriptionClasses = computed(() =>
133
+ twMerge(UI_STYLE.description, sizeClass.value.description, variantClass.value.description),
134
+ )
135
+ const leadingIconClasses = computed(() =>
136
+ twMerge(
137
+ UI_STYLE.slot.leading,
138
+ sizeClass.value.icon,
139
+ variantClass.value.icon,
140
+ props.onIconClick && UI_STYLE.slot.hasAction,
141
+ ),
142
+ )
143
+ const trailingIconClasses = computed(() =>
144
+ twMerge(
145
+ UI_STYLE.slot.trailing,
146
+ sizeClass.value.icon,
147
+ variantClass.value.icon,
148
+ props.onTrailingIconClick && UI_STYLE.slot.hasAction,
149
+ ),
150
+ )
151
+ const fieldStyle = computed(() =>
152
+ props.autoWidth ? { flexShrink: 1, maxWidth: '100%', width: `${measuredWidth.value}px` } : { undefined },
153
+ )
154
+
155
+ watch(
156
+ [widthMeasureText, () => props.autoWidth, () => props.size, () => props.variant, widthMeasureRef],
157
+ () => {
158
+ if (props.autoWidth && widthMeasureRef.value) {
159
+ measuredWidth.value = Math.ceil(widthMeasureRef.value.getBoundingClientRect().width) + 2
160
+ }
161
+ },
162
+ { flush: 'post', immediate: true },
163
+ )
164
+
165
+ function clear(): void {
166
+ setValue('')
167
+ focus()
168
+ }
169
+
170
+ function focus(): void {
171
+ inputRef.value?.focus()
172
+ }
173
+
174
+ function handleBlur(event: FocusEvent): void {
175
+ hasFocus.value = false
176
+ props.onBlur?.(currentValue.value, event.target as HTMLInputElement)
177
+ }
178
+
179
+ function handleChange(event: Event): void {
180
+ const target = event.target as HTMLInputElement
181
+ props.onChange?.(target.value, target)
182
+ }
183
+
184
+ function handleFocus(event: FocusEvent): void {
185
+ hasFocus.value = true
186
+ props.onFocus?.(currentValue.value, event.target as HTMLInputElement)
187
+ }
188
+
189
+ function handleInput(event: Event): void {
190
+ setValue((event.target as HTMLInputElement).value)
191
+ }
192
+
193
+ function setValue(value: string): void {
194
+ model.value = value
195
+ props.onInput?.(value, inputRef.value)
196
+ }
197
+
198
+ defineExpose({ clear, fieldId, focus, hasFocus, inputRef })
199
+ </script>
200
+
201
+ <template>
202
+ <div :class="[classes, $attrs.class]">
203
+ <label
204
+ v-if="props.label || $slots.label"
205
+ :for="fieldId"
206
+ :class="labelClasses"
207
+ >
208
+ <slot name="label">{{ props.label }}</slot>
209
+ </label>
210
+
211
+ <label
212
+ :for="fieldId"
213
+ :class="fieldWrapClasses"
214
+ :style="props.autoWidth ? { maxWidth: '100%', width: 'fit-content' } : undefined"
215
+ >
216
+ <span
217
+ v-if="props.autoWidth"
218
+ ref="widthMeasureRef"
219
+ aria-hidden="true"
220
+ :class="[fieldClasses, 'pointer-events-none invisible absolute w-max whitespace-pre']"
221
+ >
222
+ {{ widthMeasureText }}
223
+ </span>
224
+ <slot name="leading">
225
+ <component
226
+ :is="props.onIconClick ? 'button' : 'label'"
227
+ v-if="props.icon"
228
+ type="button"
229
+ :for="fieldId"
230
+ :disabled="props.disabled || !props.onIconClick"
231
+ :class="leadingIconClasses"
232
+ @click="props.onIconClick?.()"
233
+ >
234
+ <Icon
235
+ :name="props.icon"
236
+ aria-hidden="true"
237
+ />
238
+ </component>
239
+ </slot>
240
+
241
+ <input
242
+ v-bind="{ ...$attrs, class: undefined, id: undefined }"
243
+ :id="fieldId"
244
+ ref="inputRef"
245
+ :value="currentValue"
246
+ :type="inputType"
247
+ :inputmode="props.type === 'password' ? undefined : props.type"
248
+ :disabled="props.disabled"
249
+ :readonly="props.readonly"
250
+ :placeholder="props.placeholder"
251
+ :aria-describedby="hasDescription ? descriptionId : undefined"
252
+ :class="fieldClasses"
253
+ :style="fieldStyle"
254
+ @input="handleInput"
255
+ @change="handleChange"
256
+ @focus="handleFocus"
257
+ @blur="handleBlur"
258
+ />
259
+
260
+ <button
261
+ v-if="props.type === 'password'"
262
+ type="button"
263
+ :disabled="props.disabled"
264
+ :class="[trailingIconClasses, UI_STYLE.slot.hasAction]"
265
+ :aria-label="showPassword ? 'Hide password' : 'Show password'"
266
+ @click="showPassword = !showPassword"
267
+ >
268
+ <Icon
269
+ :name="showPassword ? UI_CONFIG.hidePasswordIconName : UI_CONFIG.showPasswordIconName"
270
+ aria-hidden="true"
271
+ />
272
+ </button>
273
+ <button
274
+ v-else-if="props.type === 'search' && isEmptyField"
275
+ type="button"
276
+ :disabled="props.disabled || props.readonly"
277
+ :class="[trailingIconClasses, UI_STYLE.slot.hasAction]"
278
+ aria-label="Clear search"
279
+ @click="clear"
280
+ >
281
+ <Icon
282
+ :name="UI_CONFIG.searchClearIconName"
283
+ aria-hidden="true"
284
+ />
285
+ </button>
286
+ <slot
287
+ v-else
288
+ name="trailing"
289
+ >
290
+ <component
291
+ :is="props.onTrailingIconClick ? 'button' : 'label'"
292
+ v-if="props.trailingIcon"
293
+ type="button"
294
+ :for="fieldId"
295
+ :disabled="props.disabled || !props.onTrailingIconClick"
296
+ :class="trailingIconClasses"
297
+ @click="props.onTrailingIconClick?.()"
298
+ >
299
+ <Icon
300
+ :name="props.trailingIcon"
301
+ aria-hidden="true"
302
+ />
303
+ </component>
304
+ </slot>
305
+ </label>
306
+
307
+ <div
308
+ v-if="hasDescription"
309
+ :id="descriptionId"
310
+ :class="descriptionClasses"
311
+ >
312
+ <slot name="error" />
313
+ <slot name="description">{{ props.description }}</slot>
314
+ </div>
315
+ </div>
316
+ </template>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Label & Description, Placeholder',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import Input from './index.vue'
9
+ </script>
10
+
11
+ <template>
12
+ <div class="max-w-sm">
13
+ <Input
14
+ label="Email address"
15
+ description="We will use this address to contact you."
16
+ placeholder="name@example.com"
17
+ type="email"
18
+ />
19
+ </div>
20
+ </template>
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Read-only & Slot',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { firstIconName } from '#brickflow-ui'
9
+
10
+ import Input from './index.vue'
11
+ </script>
12
+
13
+ <template>
14
+ <div class="max-w-sm">
15
+ <Input
16
+ label="Read-only value"
17
+ value="You can select this text"
18
+ :trailingIcon="firstIconName"
19
+ readonly
20
+ >
21
+ <template #trailing>
22
+ <div class="h-full bg-red-900 text-red-300">SLOT TEXT</div>
23
+ </template>
24
+ </Input>
25
+ </div>
26
+ </template>
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Sizes & Icons',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { ref } from 'vue'
9
+
10
+ import { firstIconName, uiConfig } from '#brickflow-ui'
11
+
12
+ import Button from '../Button/index.vue'
13
+ import Input from './index.vue'
14
+
15
+ const inputConfig = uiConfig.input
16
+
17
+ function copy(): void {
18
+ console.log('Click')
19
+ }
20
+ const text = ref('')
21
+ </script>
22
+
23
+ <template>
24
+ <div class="flex flex-col gap-3">
25
+ <div
26
+ v-for="(_, size) in inputConfig.sizeClasses"
27
+ :key="size"
28
+ class="grid gap-x-2 gap-y-1.5"
29
+ style="grid-template-columns: minmax(0, 24rem) max-content; grid-template-rows: repeat(3, auto)"
30
+ >
31
+ <Input
32
+ :value="text"
33
+ :size="size"
34
+ :icon="firstIconName"
35
+ :trailingIcon="firstIconName"
36
+ description="Some text description"
37
+ placeholder="Type here"
38
+ :label="`Size: ${size}`"
39
+ class="row-span-3 grid! grid-rows-subgrid"
40
+ @iconClick="copy"
41
+ @input="text = $event"
42
+ />
43
+ <Button
44
+ :size="size"
45
+ class="col-start-2 row-start-2 self-center"
46
+ >
47
+ Button
48
+ </Button>
49
+ </div>
50
+ </div>
51
+ </template>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Input Types',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { capitalize } from 'vue'
9
+
10
+ import Input from './index.vue'
11
+
12
+ const types = ['text', 'email', 'decimal', 'numeric', 'password', 'search', 'tel', 'url'] as const
13
+ </script>
14
+
15
+ <template>
16
+ <div class="flex max-w-sm flex-col gap-3">
17
+ <Input
18
+ v-for="type in types"
19
+ :key="type"
20
+ :type="type"
21
+ :label="capitalize(type)"
22
+ :placeholder="`Enter ${type}`"
23
+ />
24
+ </div>
25
+ </template>
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Variants & Disabled',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { uiConfig } from '#brickflow-ui'
9
+
10
+ import Input from './index.vue'
11
+
12
+ const inputConfig = uiConfig.input
13
+ </script>
14
+
15
+ <template>
16
+ <div class="flex max-w-sm flex-col gap-4">
17
+ <div
18
+ v-for="(_, variant) in inputConfig.variantClasses"
19
+ :key="variant"
20
+ class="flex flex-col gap-3"
21
+ >
22
+ <Input
23
+ :variant="variant"
24
+ :label="`Variant ${variant}`"
25
+ placeholder="Type here"
26
+ />
27
+ <Input
28
+ :variant="variant"
29
+ :label="`Disabled ${variant}`"
30
+ value="Unavailable"
31
+ disabled
32
+ />
33
+ </div>
34
+ </div>
35
+ </template>
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Number & Bounds',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { ref } from 'vue'
9
+
10
+ import InputNumber from './index.vue'
11
+
12
+ const price = ref<number>(25)
13
+ function handleInput(event: number): void {
14
+ price.value = event
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <div class="flex max-w-sm flex-col gap-4">
20
+ <InputNumber
21
+ :value="price"
22
+ label="Price"
23
+ description="Min: 0 — Max: 200"
24
+ :min="0"
25
+ :max="200"
26
+ :step="5"
27
+ @input="handleInput"
28
+ >
29
+ <template #leading><span class="text-green-400">$</span></template>
30
+ </InputNumber>
31
+ <p class="text-gray-500">Value: {{ price }}</p>
32
+ </div>
33
+ </template>
@@ -0,0 +1,141 @@
1
+ <script setup lang="ts" generic="T extends boolean = false">
2
+ import { shallowRef, watch } from 'vue'
3
+
4
+ import { isEmpty } from '../../utils/checkup.js'
5
+ import Input from '../Input/index.vue'
6
+
7
+ defineOptions({ inheritAttrs: false })
8
+
9
+ const props = withDefaults(
10
+ defineProps<{
11
+ denominator?: number
12
+ description?: string
13
+ disabled?: boolean
14
+ label?: string
15
+ max?: number
16
+ min?: number
17
+ nullable?: T
18
+ onBlur?: (value: true extends NoInfer<T> ? null | number : number) => void
19
+ onFocus?: (value: true extends NoInfer<T> ? null | number : number) => void
20
+ onInput?: (value: true extends NoInfer<T> ? null | number : number) => void
21
+ onKeydown?: (event: KeyboardEvent) => void
22
+ step?: number
23
+ value: true extends NoInfer<T> ? null | number : number
24
+ }>(),
25
+ {
26
+ denominator: 0,
27
+ description: undefined,
28
+ disabled: false,
29
+ label: undefined,
30
+ max: undefined,
31
+ min: undefined,
32
+ nullable: undefined,
33
+ onBlur: undefined,
34
+ onFocus: undefined,
35
+ onInput: undefined,
36
+ onKeydown: undefined,
37
+ onValueChange: undefined,
38
+ step: 1,
39
+ },
40
+ )
41
+
42
+ const inputValue = shallowRef(prepareValue(props.value))
43
+
44
+ watch(
45
+ () => props.value,
46
+ () => {
47
+ inputValue.value = prepareValue(props.value)
48
+ },
49
+ )
50
+
51
+ function handleBlur(value: string, target: HTMLInputElement): void {
52
+ if (!target) {
53
+ return
54
+ }
55
+ const nextValue = prepareValue(props.value)
56
+ target.value = nextValue
57
+ inputValue.value = nextValue
58
+ props.onBlur?.(props.value)
59
+ }
60
+ const allowedRegexp = /^-?\d*(?:\.\d*)?$/
61
+ const validNumberRegexp = /^-?(?:\d+(?:\.\d+)?|\.\d+)$/
62
+
63
+ function handleInput(rawValue: string, target: HTMLInputElement | null): void {
64
+ if (!target) {
65
+ return
66
+ }
67
+ const nextValue = rawValue.replace(/,/, '.')
68
+ target.value = nextValue
69
+
70
+ if (isEmpty(nextValue)) {
71
+ return
72
+ }
73
+ if (!allowedRegexp.test(nextValue)) {
74
+ target.value = inputValue.value
75
+ return
76
+ }
77
+ if (!validNumberRegexp.test(nextValue)) {
78
+ return
79
+ }
80
+ let nextValueNumber = Number(nextValue)
81
+ if (!isEmpty(props.min) && nextValueNumber < props.min) {
82
+ target.value = prepareValue(props.min)
83
+ nextValueNumber = props.min
84
+ }
85
+ if (!isEmpty(props.max) && nextValueNumber > props.max) {
86
+ target.value = prepareValue(props.max)
87
+ nextValueNumber = props.max
88
+ }
89
+ props.onInput?.(nextValueNumber)
90
+ }
91
+ function prepareValue(value: typeof props.value): string {
92
+ return value === null ? '' : String(value)
93
+ }
94
+ </script>
95
+
96
+ <template>
97
+ <Input
98
+ v-bind="{
99
+ ...$attrs,
100
+ // onBlur: handleBlur,
101
+ // onKeydown: handleKeydown,
102
+ }"
103
+ :value="inputValue"
104
+ :label="props.label"
105
+ :description="props.description"
106
+ type="decimal"
107
+ @input="handleInput"
108
+ @blur="handleBlur"
109
+ >
110
+ <template
111
+ v-if="$slots.label"
112
+ #label
113
+ >
114
+ <slot name="label" />
115
+ </template>
116
+ <template
117
+ v-if="$slots.leading"
118
+ #leading
119
+ >
120
+ <slot name="leading" />
121
+ </template>
122
+ <template
123
+ v-if="$slots.trailing"
124
+ #trailing
125
+ >
126
+ <slot name="trailing" />
127
+ </template>
128
+ <template
129
+ v-if="$slots.error"
130
+ #error
131
+ >
132
+ <slot name="error" />
133
+ </template>
134
+ <template
135
+ v-if="$slots.description"
136
+ #description
137
+ >
138
+ <slot name="description" />
139
+ </template>
140
+ </Input>
141
+ </template>
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <Transition
3
+ :name="props.name"
4
+ :mode="props.mode"
5
+ @beforeEnter="beforeStart"
6
+ @enter="start"
7
+ @afterEnter="afterEnter"
8
+ @beforeLeave="beforeLeave"
9
+ @leave="leave"
10
+ @afterLeave="afterLeave"
11
+ >
12
+ <slot></slot>
13
+ </Transition>
14
+ </template>
15
+
16
+ <script lang="ts" setup>
17
+ import { computed, nextTick, ref } from 'vue'
18
+
19
+ const props = withDefaults(
20
+ defineProps<{
21
+ dimension?: 'height' | 'width'
22
+ duration?: number
23
+ expandClasses?: ''
24
+ flexDirection?: 'end' | 'start'
25
+ flexParent?: Element
26
+ mode?: 'default' | 'in-out' | 'out-in'
27
+ name?: string
28
+ }>(),
29
+ {
30
+ dimension: 'height',
31
+ duration: 300,
32
+ expandClasses: '',
33
+ flexDirection: 'start',
34
+ flexParent: undefined,
35
+ gap: 0,
36
+ mode: undefined,
37
+ name: undefined,
38
+ },
39
+ )
40
+
41
+ const gap = ref(0)
42
+
43
+ const sizeName = computed(() => props.dimension)
44
+ const marginName = computed(() => {
45
+ const xDirection = props.flexDirection === 'start' ? 'marginLeft' : 'marginRight'
46
+ const yDirection = props.flexDirection === 'start' ? 'marginTop' : 'marginBottom'
47
+ return props.dimension === 'height' ? yDirection : xDirection
48
+ })
49
+ const scrollName = computed(() => (props.dimension === 'height' ? 'scrollHeight' : 'scrollWidth'))
50
+ const expandClass = computed(() => {
51
+ return (props.expandClasses || '!transition-all !ease-in-out !overflow-hidden will-change-transform').split(
52
+ ' ',
53
+ )
54
+ })
55
+
56
+ const beforeStart = (event: Element): void => {
57
+ const el = event as HTMLElement
58
+ shrink(el)
59
+ }
60
+ const start = (event: Element): void => {
61
+ const el = event as HTMLElement
62
+ el.dataset.oldOverflow = el.style.overflow
63
+
64
+ // Wait until component mounted to get proper height
65
+ nextTick(() => {
66
+ if (el[scrollName.value] === 0) {
67
+ el.style[sizeName.value] = ''
68
+ } else {
69
+ el.style[sizeName.value] = `${el[scrollName.value]}px`
70
+ el.style.padding = ''
71
+ if (props.flexParent) {
72
+ el.style[marginName.value] = ''
73
+ }
74
+ }
75
+ el.style.overflow = 'hidden'
76
+ })
77
+ }
78
+ const afterEnter = (event: Element): void => {
79
+ const el = event as HTMLElement
80
+ release(el)
81
+ }
82
+ const beforeLeave = (event: Element): void => {
83
+ const el = event as HTMLElement
84
+ const computedStyle = getComputedStyle(el)
85
+ el.dataset.oldOverflow = computedStyle.overflow
86
+
87
+ el.style[sizeName.value] = `${el[scrollName.value]}px`
88
+ if (props.flexParent) {
89
+ el.style[marginName.value] = ''
90
+ }
91
+ el.style.overflow = 'hidden'
92
+ }
93
+ const leave = (event: Element): void => {
94
+ const el = event as HTMLElement
95
+ if (el[scrollName.value] !== 0) {
96
+ shrink(el)
97
+ }
98
+ }
99
+ const afterLeave = (event: Element): void => {
100
+ const el = event as HTMLElement
101
+ release(el)
102
+ }
103
+
104
+ function release(el: HTMLElement): void {
105
+ el.classList.add(...expandClass.value)
106
+ el.style.transitionDuration = ''
107
+ el.style[sizeName.value] = ''
108
+ el.style[marginName.value] = ''
109
+ el.style.overflow = String(el.dataset.oldOverflow)
110
+ }
111
+ function shrink(el: HTMLElement): void {
112
+ el.classList.add(...expandClass.value)
113
+ el.style.transitionDuration = `${props.duration}ms`
114
+ el.style[sizeName.value] = '0'
115
+ el.style.padding = '0'
116
+ if (props.flexParent) {
117
+ gap.value = parseInt(window.getComputedStyle(props.flexParent)?.gap) || 0
118
+ el.style[marginName.value] = `-${gap.value}px`
119
+ }
120
+ }
121
+ </script>
@@ -1,18 +1,5 @@
1
1
  export {}
2
2
 
3
- declare module '#brickflow-ui-config' {
4
- import type { BrickflowUiConfig } from './tailwind'
5
-
6
- export const UI_CONFIG: BrickflowUiConfigType
7
- export const UI_STYLE: BrickflowUiStyleType
8
- export const uiConfig: BrickflowUiConfig['uiConfig']
9
- export const uiStyles: BrickflowUiConfig['uiStyles']
10
-
11
- const config: BrickflowUiConfig
12
-
13
- export default config
14
- }
15
-
16
3
  declare global {
17
4
  type BrickflowUiConfigFor<TExpected extends BrickflowUiConfigObject> = {
18
5
  [TKey in keyof BrickflowUiConfigLiteralPaths]: BrickflowUiConfigLiteralPaths[TKey] extends TExpected
@@ -1,5 +1,4 @@
1
1
  declare module '#brickflow-ui-icons' {
2
- export const buttonConfig: import('./components/Button/index.vue').ButtonConfig
3
2
  export const firstIconName: string
4
3
  export const iconNames: readonly string[]
5
4
  }
@@ -0,0 +1,7 @@
1
+ import type { defineNuxtLink } from 'nuxt/app'
2
+
3
+ declare module 'vue' {
4
+ interface GlobalComponents {
5
+ NuxtLink: ReturnType<typeof defineNuxtLink>
6
+ }
7
+ }
@@ -0,0 +1,10 @@
1
+ declare module '#brickflow-ui-config' {
2
+ export const UI_CONFIG: BrickflowUiConfigType
3
+ export const UI_STYLE: BrickflowUiStyleType
4
+ export const uiConfig: BrickflowUiConfigLiteralPaths
5
+ export const uiStyles: BrickflowUiStyleType
6
+
7
+ const config: { uiConfig: typeof uiConfig; uiStyles: typeof uiStyles }
8
+
9
+ export default config
10
+ }
@@ -1 +1 @@
1
- @import "./ui-breakpoint.css";@media (width < 1440px){.dkHidden{display:none!important}}@media (width < 1024px),(width >= 1440px){.lpHidden{display:none!important}}@media (width < 768px),(width >= 1024px){.tbHidden{display:none!important}}@media (width < 480px),(width >= 768px){.mbHidden{display:none!important}}@media (width >= 480px){.msHidden{display:none!important}}
1
+ @import "./ui-breakpoint.css";@source "./components";@media (width < 1440px){.dkHidden{display:none!important}}@media (width < 1024px),(width >= 1440px){.lpHidden{display:none!important}}@media (width < 768px),(width >= 1024px){.tbHidden{display:none!important}}@media (width < 480px),(width >= 768px){.mbHidden{display:none!important}}@media (width >= 480px){.msHidden{display:none!important}}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @returns isEmpty('') => true
3
+ * @returns isEmpty([]) => true
4
+ * @returns isEmpty([1]) => false
5
+ * @returns isEmpty([null]) => false
6
+ * @returns isEmpty({}) => true
7
+ * @returns isEmpty({ key: '' }) => false
8
+ * @returns isEmpty(undefined) => true
9
+ * @returns isEmpty(null) => true
10
+ * @returns isEmpty(new Date()) => false
11
+ * @returns isEmpty(new Date('Invalid Date')) => false
12
+ * @returns isEmpty(true) => false
13
+ * @returns isEmpty(false) => false
14
+ * @returns isEmpty(0), 0, false
15
+ * @returns isEmpty(() => undefined) => false
16
+ * @returns isEmpty(Infinity) => false
17
+ * @returns isEmpty(-Infinity) => false
18
+ * @returns isEmpty(new Promise((resolve) => resolve(''))) => true
19
+ */
20
+ export declare function isEmpty<T>(value: T): value is Extract<T, [] | null | undefined>;
21
+ export declare function isFloat(value: number): boolean;
22
+ export declare function isNumber(value: unknown): value is number;
23
+ export declare function isPromise<T>(value: T): value is T extends Promise<unknown> ? T : never;
24
+ export declare function isString(value: unknown): value is string;
25
+ export declare function isValidDate(date: Date | number | string): boolean;
26
+ export declare const isNonNullable: <T>(value: T) => value is NonNullable<T>;
27
+ export declare function isElement<T>(value: T): value is Extract<T, Element>;
28
+ export declare function isExternalLink(link: string): boolean;
29
+ export declare function isFormData(value?: FormData | Record<string, unknown> | unknown): value is FormData;
30
+ export declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
31
+ export declare function isTouchDevice(): boolean;
32
+ //# sourceMappingURL=checkup.d.ts.map
@@ -0,0 +1,67 @@
1
+ export function isEmpty(value) {
2
+ if (typeof value === "boolean" || typeof value === "function") {
3
+ return false;
4
+ }
5
+ if (typeof value === "undefined" || value === null || typeof value === "string" && value.length === 0) {
6
+ return true;
7
+ }
8
+ if (typeof value === "string" || typeof value === "number") {
9
+ return false;
10
+ }
11
+ if (Array.isArray(value) && value.length === 0) {
12
+ return true;
13
+ }
14
+ if (isDate(value)) {
15
+ return false;
16
+ }
17
+ return Object.keys(value).length === 0;
18
+ }
19
+ export function isFloat(value) {
20
+ return Number(value) === value && value % 1 !== 0;
21
+ }
22
+ export function isNumber(value) {
23
+ return typeof value === "number";
24
+ }
25
+ export function isPromise(value) {
26
+ return Boolean(value) && (typeof value === "object" || typeof value === "function") && typeof value?.then === "function";
27
+ }
28
+ export function isString(value) {
29
+ return typeof value === "string";
30
+ }
31
+ export function isValidDate(date) {
32
+ if (typeof date === "string") {
33
+ return Boolean(Date.parse(date));
34
+ }
35
+ if (isNaN(date)) {
36
+ return false;
37
+ }
38
+ return typeof date === "number" || date instanceof Date;
39
+ }
40
+ function isDate(value) {
41
+ if (Object.prototype.toString.call(value) === "[object Date]") {
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ export const isNonNullable = (value) => {
47
+ return value !== void 0 && value !== null;
48
+ };
49
+ export function isElement(value) {
50
+ return value instanceof Element;
51
+ }
52
+ export function isExternalLink(link) {
53
+ const EXTERNAL_LINK_REGEX = /^(?:https?:\/\/|mailto:|tel:)/;
54
+ return EXTERNAL_LINK_REGEX.test(link);
55
+ }
56
+ export function isFormData(value) {
57
+ if (!value) {
58
+ return false;
59
+ }
60
+ return typeof value === "object" && typeof value.append === "function" && typeof value.has === "function";
61
+ }
62
+ export function isFunction(value) {
63
+ return typeof value === "function";
64
+ }
65
+ export function isTouchDevice() {
66
+ return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0;
67
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickflow/ui",
3
- "version": "0.0.53",
3
+ "version": "0.0.55",
4
4
  "type": "module",
5
5
  "description": "brickflow UI Nuxt module.",
6
6
  "files": [