@leaflink/stash 53.0.0 → 53.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 (37) hide show
  1. package/README.md +2 -4
  2. package/dist/AddressSelect.js.map +1 -1
  3. package/dist/Carousel.js.map +1 -1
  4. package/dist/CurrencyInput.js.map +1 -1
  5. package/dist/DatePicker.js.map +1 -1
  6. package/dist/Image.vue_vue_type_script_setup_true_lang-CAj0FH9h.js.map +1 -1
  7. package/dist/InputOptions.js.map +1 -1
  8. package/dist/ListView.vue.d.ts +4 -14
  9. package/dist/Modals.js +1 -1
  10. package/dist/Modals.js.map +1 -1
  11. package/dist/Select.js +1 -1
  12. package/dist/Select.js.map +1 -1
  13. package/dist/TextEditor.js.map +1 -1
  14. package/dist/Timeline.js +4 -4
  15. package/dist/Timeline.js.map +1 -1
  16. package/dist/Timeline.vue.d.ts +2 -2
  17. package/dist/Toast.js.map +1 -1
  18. package/dist/Tooltip.vue_vue_type_script_setup_true_lang-mzBLSXy3.js.map +1 -1
  19. package/dist/components.css +2 -2
  20. package/dist/directives/tooltip.js.map +1 -1
  21. package/dist/floating-ui.vue-CuGrC-z8.js.map +1 -1
  22. package/dist/formatDateTime-Dz8bXV0R.js.map +1 -1
  23. package/dist/index-D6bxWkZ1.js.map +1 -1
  24. package/dist/index-DBV9Uz0C.js.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/isValid-DN-HkCoi.js.map +1 -1
  27. package/dist/parseISO-wlfIB_QJ.js.map +1 -1
  28. package/dist/searchFuzzy-B3TsUO-V.js.map +1 -1
  29. package/dist/tailwind-base.js +4 -4
  30. package/dist/tailwind-base.js.d.ts +10 -10
  31. package/dist/tailwind-base.js.map +1 -1
  32. package/dist/toTimeZone-Coq1oPTt.js.map +1 -1
  33. package/dist/useGoogleMaps.js.map +1 -1
  34. package/dist/useValidation.js.map +1 -1
  35. package/package.json +15 -85
  36. package/styles/backwards-compat.css +2 -3
  37. package/LICENSE +0 -21
@@ -1 +1 @@
1
- {"version":3,"file":"Image.vue_vue_type_script_setup_true_lang-CAj0FH9h.js","sources":["../src/components/Image/Image.types.ts","../src/components/Image/providers/utils.ts","../src/components/Image/providers/cloudinary.ts","../src/components/Image/providers/static.ts","../src/components/Image/providers/index.ts","../src/components/Image/Image.vue"],"sourcesContent":["// Sizes used to generate resized and optimized versions of an image.\n// Generated in `srcset`, and informed by `sizes`.\nexport const Screens = {\n xs: 160,\n sm: 338,\n md: 676,\n lg: 1352,\n xl: 2704,\n} as const;\n\nexport interface ImageSizeCondition {\n mediaQuery: string;\n screenMinWidth: number;\n size: string;\n}\n\nexport enum ImageRadius {\n None = 'none',\n Rounded = 'rounded',\n Circle = 'circle',\n}\n\nexport type ImageRadii = `${ImageRadius}`;\n\nexport enum ImageProviders {\n Cloudinary = 'cloudinary',\n Static = 'static',\n}\n","export interface ImageModifiers {\n format?: string;\n height?: number;\n width?: number;\n [key: string]: any /* eslint-disable-line @typescript-eslint/no-explicit-any */;\n}\n\nexport type ParamFormatter = (key: string, value: string) => string;\n\nexport type ParamMapper = { [key: string]: string } | ((key: string) => string);\n\nexport interface ProviderUrlBuilder {\n keyMap?: ParamMapper;\n formatter?: ParamFormatter;\n joinWith?: string;\n valueMap?: {\n [key: string]: ParamMapper;\n };\n}\n\nfunction createMapper(map: ParamMapper) {\n return (key: string) => {\n return map[key] || key;\n };\n}\n\n/**\n * Builds the parameterized Cloudinary url\n */\nexport function buildProviderUrl({\n formatter = (key, value) => `${key}=${value}`,\n keyMap,\n joinWith = '/',\n valueMap = {},\n}: ProviderUrlBuilder = {}) {\n const keyMapper = typeof keyMap === 'function' ? keyMap : createMapper(keyMap || {});\n\n Object.keys(valueMap).forEach((valueKey) => {\n if (typeof valueMap[valueKey] !== 'function') {\n valueMap[valueKey] = createMapper(valueMap[valueKey]);\n }\n });\n\n return (modifiers: { [key: string]: string } = {}) => {\n const operations = Object.entries(modifiers).map(([key, value]) => {\n const mapper = valueMap[key];\n const newKey = keyMapper(key);\n let newVal = value;\n\n if (typeof mapper === 'function') {\n newVal = mapper(modifiers[key]);\n }\n\n return formatter(newKey, newVal);\n });\n\n return operations.join(joinWith);\n };\n}\n\n/**\n * Checks if a (sub)domain is included in a list of acceptable domains\n * @param str the (sub)domain to check\n * @param domains an array of valid domains\n */\nexport function isDomainValid(str = '', domains: string[] = []): boolean {\n const url = new URL(str);\n const host = url.host;\n\n return domains.some((domain) => {\n if (domain === host) {\n return true;\n }\n\n return domain.endsWith(`.${host}`);\n });\n}\n","import merge from 'lodash-es/merge';\n\nimport { IMAGE_PROVIDER_URLS } from '../../../constants';\nimport { buildProviderUrl, ImageModifiers } from './utils';\n\nconst BASE_URL = IMAGE_PROVIDER_URLS.CLOUDINARY;\n\nconst convertHextoRGBFormat = (value: string) => (value.startsWith('#') ? value.replace('#', 'rgb_') : value);\n\n/**\n * Parameters (option and value pairs) that can be used in the <transformations> segment of the Cloudinary transformation URL.\n * Options and their respective values are connected by an underscore (eg `w_250` for width of 250px.).\n * Multiple parameters are comma separated (eg. `w_250,h_250`).\n *\n * `keyMap` maps the option to its option prefix.\n * `valueMap` is used for grouping options using the same `keyMap` value under a 'category', allowing for easier usage and custom labelling in the component context.\n *\n * Transformation URL structure:\n * https://cloudinary.com/documentation/transformation_reference\n *\n * Transformation URL parameters (options and values):\n * https://cloudinary.com/documentation/image_transformations#transformation_url_syntax\n *\n */\nexport const operationsGenerator = buildProviderUrl({\n keyMap: {\n fit: 'c',\n width: 'w',\n height: 'h',\n format: 'f',\n quality: 'q',\n background: 'b',\n dpr: 'dpr',\n },\n valueMap: {\n fit: {\n fill: 'fill',\n inside: 'pad',\n outside: 'lpad',\n cover: 'fit',\n contain: 'scale',\n },\n format: {\n jpeg: 'jpg',\n },\n background(value: string) {\n return convertHextoRGBFormat(value);\n },\n },\n joinWith: ',',\n formatter: (key, value) => `${key}_${value}`,\n});\n\n// Note: Not configurable via Image props (for now).\nconst defaultModifiers = {\n format: 'auto',\n quality: 'auto:best',\n};\n\nexport function getImageUrl(src: string, modifiers: Partial<ImageModifiers> = {}): string {\n const mergeModifiers = merge(defaultModifiers, modifiers);\n const operations = operationsGenerator(mergeModifiers);\n\n return `${BASE_URL}/${operations}/${src}`;\n}\n","export function getImageUrl(src = ''): string {\n return src;\n}\n","import * as cloudinary from './cloudinary';\nimport * as staticProvider from './static';\n\nexport default {\n cloudinary,\n static: staticProvider,\n};\n","<script lang=\"ts\">\n export * from './Image.types';\n</script>\n\n<script setup lang=\"ts\">\n import { computed, inject, useAttrs } from 'vue';\n\n import { StashImageProviders, StashProvideState } from '../../../types/misc';\n import { SCREEN_SIZES } from '../../constants';\n import { ImageProviders, ImageRadii, ImageRadius, ImageSizeCondition, Screens } from './Image.types';\n import providers from './providers';\n import { ImageModifiers } from './providers/utils';\n\n export interface ImageProps {\n /**\n * The path to the image you want to embed.\n */\n src: string;\n\n /**\n * Native srcset attribute.\n * One or more strings separated by commas, indicating possible image sources\n * Can only be used with provider=static, otherwise it's ignored and auto-generated with `sizes` usage\n */\n srcset?: string;\n\n /**\n * For specifying responsive sizes\n */\n sizes?: string;\n\n /**\n * Where the image is served from.\n * If not provided, the provider will inherit from the Stash config `stashOptions.images.provider` (default: `static`).\n * - `static` for relative or absolute paths\n * - `cloudinary` for images served via Cloudinary\n */\n provider?: StashImageProviders;\n\n /**\n * For applying border radius\n */\n radius?: ImageRadii;\n\n /**\n * A custom static path the image src will be appended onto when provider=static.\n * Can be used to override the library-level default.\n */\n staticPath?: string;\n\n /**\n * TODO - https://leaflink.atlassian.net/browse/GRO-204\n * A custom function used to resolve a URL string for the image\n */\n // loader?: () => string;\n }\n\n defineOptions({\n inheritAttrs: false,\n });\n\n const BREAKPOINTS = {\n md: SCREEN_SIZES.md,\n lg: SCREEN_SIZES.lg,\n };\n const stashOptions = inject<StashProvideState>('stashOptions');\n const props = withDefaults(defineProps<ImageProps>(), {\n srcset: undefined,\n sizes: undefined,\n staticPath: undefined,\n provider: undefined,\n radius: 'none',\n // loader: undefined, // TODO - https://leaflink.atlassian.net/browse/GRO-204\n });\n\n const attrs = computed(() => {\n const { src, ...attrs } = useAttrs();\n\n attrs.sizes = imgSizes.value;\n attrs.srcset = imgSrcset.value;\n\n return attrs;\n });\n\n const isAbsoluteUrl = computed(() => {\n // return true if not an absolute url\n try {\n new URL(props.src);\n return true;\n } catch (e) {\n return false;\n }\n });\n\n const computedProvider = computed(() => props.provider || stashOptions?.images?.provider || ImageProviders.Static);\n\n const computedStaticPath = computed(() => props.staticPath ?? stashOptions?.staticPath);\n\n const isStatic = computed(() => computedProvider.value === ImageProviders.Static);\n\n const imgProvider = computed(() => providers[computedProvider.value]);\n\n const imgSrc = computed(() => (isStatic.value ? getProviderImage() : getProviderImage({ width: Screens.md })));\n\n const imgSizes = computed(() => (props.sizes ? getSizes() : props.sizes));\n\n const imgSrcset = computed(() => (props.sizes && !props.srcset && !isStatic.value ? getSources() : props.srcset));\n\n const parsedSizes = computed(() => {\n return props.sizes ? parseSizes(props.sizes) : [];\n });\n\n function getProviderImage(modifiers: ImageModifiers = {}) {\n if (isStatic.value && isAbsoluteUrl.value) {\n return props.src;\n }\n\n const src = isStatic.value && computedStaticPath.value ? `${computedStaticPath.value}/${props.src}` : props.src;\n\n return imgProvider.value.getImageUrl(src, modifiers);\n }\n\n function getSources() {\n return Object.values(Screens)\n .map((width) => {\n const src = getProviderImage({ width });\n\n return `${src} ${width}w`;\n })\n .join(', ');\n }\n\n function getSizes() {\n // return if using native media conditions\n if (props.sizes?.includes('(')) {\n return props.sizes;\n }\n\n return parsedSizes.value.map((v) => `${v.mediaQuery ? v.mediaQuery + ' ' : ''}${v.size}`).join(', ');\n }\n\n function parseSizes(providedSizes = '') {\n const conditions: ImageSizeCondition[] = [];\n const sizes = {\n default: '100vw',\n };\n\n // parse sizes and convert to object\n if (typeof providedSizes === 'string') {\n const definitions = providedSizes.split(/[\\s]+/).filter((size) => size);\n\n for (const entry of definitions) {\n const size = entry.split(':');\n\n if (size.length !== 2) {\n sizes['default'] = size[0].trim();\n continue;\n }\n\n sizes[size[0].trim()] = size[1].trim();\n }\n } else {\n throw new Error('`sizes` needs to be a string');\n }\n\n for (const key in sizes) {\n const screenMinWidth = parseInt(BREAKPOINTS[key] || 0);\n let size = String(sizes[key]);\n const isFluidSize = size.endsWith('vw');\n\n // convert integer to pixels\n if (!isFluidSize && /^\\d+$/.test(size)) {\n size = `${size}px`;\n }\n\n if (!isFluidSize && size.endsWith('%')) {\n throw new Error('Image: `sizes` does not support percentage values');\n }\n\n const condition = {\n mediaQuery: screenMinWidth ? `(min-width: ${screenMinWidth}px)` : '',\n screenMinWidth,\n size,\n };\n\n conditions.push(condition);\n }\n\n conditions.sort((v1, v2) => (v1.screenMinWidth > v2.screenMinWidth ? -1 : 1));\n\n return conditions;\n }\n</script>\n\n<template>\n <img\n ref=\"img\"\n :key=\"imgSrc\"\n data-test=\"stash-image\"\n class=\"stash-image\"\n :class=\"{\n rounded: props.radius === ImageRadius.Rounded,\n 'rounded-full': props.radius === ImageRadius.Circle,\n }\"\n :src=\"imgSrc\"\n v-bind=\"attrs\"\n />\n</template>\n"],"names":["Screens","ImageRadius","ImageProviders","createMapper","map","key","buildProviderUrl","formatter","value","keyMap","joinWith","valueMap","keyMapper","valueKey","modifiers","mapper","newKey","newVal","BASE_URL","IMAGE_PROVIDER_URLS","convertHextoRGBFormat","operationsGenerator","defaultModifiers","getImageUrl","src","mergeModifiers","merge","operations","providers","cloudinary","staticProvider","BREAKPOINTS","SCREEN_SIZES","stashOptions","inject","props","__props","attrs","computed","useAttrs","imgSizes","imgSrcset","isAbsoluteUrl","computedProvider","_a","computedStaticPath","isStatic","imgProvider","imgSrc","getProviderImage","getSizes","getSources","parsedSizes","parseSizes","width","v","providedSizes","conditions","sizes","definitions","size","entry","screenMinWidth","isFluidSize","condition","v1","v2"],"mappings":"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"}
1
+ {"version":3,"file":"Image.vue_vue_type_script_setup_true_lang-CAj0FH9h.js","sources":["../src/components/Image/Image.types.ts","../src/components/Image/providers/utils.ts","../src/components/Image/providers/cloudinary.ts","../src/components/Image/providers/static.ts","../src/components/Image/providers/index.ts","../src/components/Image/Image.vue"],"sourcesContent":["// Sizes used to generate resized and optimized versions of an image.\n// Generated in `srcset`, and informed by `sizes`.\nexport const Screens = {\n xs: 160,\n sm: 338,\n md: 676,\n lg: 1352,\n xl: 2704,\n} as const;\n\nexport interface ImageSizeCondition {\n mediaQuery: string;\n screenMinWidth: number;\n size: string;\n}\n\nexport enum ImageRadius {\n None = 'none',\n Rounded = 'rounded',\n Circle = 'circle',\n}\n\nexport type ImageRadii = `${ImageRadius}`;\n\nexport enum ImageProviders {\n Cloudinary = 'cloudinary',\n Static = 'static',\n}\n","export interface ImageModifiers {\n format?: string;\n height?: number;\n width?: number;\n [key: string]: any;\n}\n\nexport type ParamFormatter = (key: string, value: string) => string;\n\nexport type ParamMapper = { [key: string]: string } | ((key: string) => string);\n\nexport interface ProviderUrlBuilder {\n keyMap?: ParamMapper;\n formatter?: ParamFormatter;\n joinWith?: string;\n valueMap?: {\n [key: string]: ParamMapper;\n };\n}\n\nfunction createMapper(map: ParamMapper) {\n return (key: string) => {\n return map[key] || key;\n };\n}\n\n/**\n * Builds the parameterized Cloudinary url\n */\nexport function buildProviderUrl({\n formatter = (key, value) => `${key}=${value}`,\n keyMap,\n joinWith = '/',\n valueMap = {},\n}: ProviderUrlBuilder = {}) {\n const keyMapper = typeof keyMap === 'function' ? keyMap : createMapper(keyMap || {});\n\n Object.keys(valueMap).forEach((valueKey) => {\n if (typeof valueMap[valueKey] !== 'function') {\n valueMap[valueKey] = createMapper(valueMap[valueKey]);\n }\n });\n\n return (modifiers: { [key: string]: string } = {}) => {\n const operations = Object.entries(modifiers).map(([key, value]) => {\n const mapper = valueMap[key];\n const newKey = keyMapper(key);\n let newVal = value;\n\n if (typeof mapper === 'function') {\n newVal = mapper(modifiers[key]);\n }\n\n return formatter(newKey, newVal);\n });\n\n return operations.join(joinWith);\n };\n}\n\n/**\n * Checks if a (sub)domain is included in a list of acceptable domains\n * @param str the (sub)domain to check\n * @param domains an array of valid domains\n */\nexport function isDomainValid(str = '', domains: string[] = []): boolean {\n const url = new URL(str);\n const host = url.host;\n\n return domains.some((domain) => {\n if (domain === host) {\n return true;\n }\n\n return domain.endsWith(`.${host}`);\n });\n}\n","import merge from 'lodash-es/merge';\n\nimport { IMAGE_PROVIDER_URLS } from '../../../constants';\nimport { buildProviderUrl, ImageModifiers } from './utils';\n\nconst BASE_URL = IMAGE_PROVIDER_URLS.CLOUDINARY;\n\nconst convertHextoRGBFormat = (value: string) => (value.startsWith('#') ? value.replace('#', 'rgb_') : value);\n\n/**\n * Parameters (option and value pairs) that can be used in the <transformations> segment of the Cloudinary transformation URL.\n * Options and their respective values are connected by an underscore (eg `w_250` for width of 250px.).\n * Multiple parameters are comma separated (eg. `w_250,h_250`).\n *\n * `keyMap` maps the option to its option prefix.\n * `valueMap` is used for grouping options using the same `keyMap` value under a 'category', allowing for easier usage and custom labelling in the component context.\n *\n * Transformation URL structure:\n * https://cloudinary.com/documentation/transformation_reference\n *\n * Transformation URL parameters (options and values):\n * https://cloudinary.com/documentation/image_transformations#transformation_url_syntax\n *\n */\nexport const operationsGenerator = buildProviderUrl({\n keyMap: {\n fit: 'c',\n width: 'w',\n height: 'h',\n format: 'f',\n quality: 'q',\n background: 'b',\n dpr: 'dpr',\n },\n valueMap: {\n fit: {\n fill: 'fill',\n inside: 'pad',\n outside: 'lpad',\n cover: 'fit',\n contain: 'scale',\n },\n format: {\n jpeg: 'jpg',\n },\n background(value: string) {\n return convertHextoRGBFormat(value);\n },\n },\n joinWith: ',',\n formatter: (key, value) => `${key}_${value}`,\n});\n\n// Note: Not configurable via Image props (for now).\nconst defaultModifiers = {\n format: 'auto',\n quality: 'auto:best',\n};\n\nexport function getImageUrl(src: string, modifiers: Partial<ImageModifiers> = {}): string {\n const mergeModifiers = merge(defaultModifiers, modifiers);\n const operations = operationsGenerator(mergeModifiers);\n\n return `${BASE_URL}/${operations}/${src}`;\n}\n","export function getImageUrl(src = ''): string {\n return src;\n}\n","import * as cloudinary from './cloudinary';\nimport * as staticProvider from './static';\n\nexport default {\n cloudinary,\n static: staticProvider,\n};\n","<script lang=\"ts\">\n export * from './Image.types';\n</script>\n\n<script setup lang=\"ts\">\n import { computed, inject, useAttrs } from 'vue';\n\n import { StashImageProviders, StashProvideState } from '../../../types/misc';\n import { SCREEN_SIZES } from '../../constants';\n import { ImageProviders, ImageRadii, ImageRadius, ImageSizeCondition, Screens } from './Image.types';\n import providers from './providers';\n import { ImageModifiers } from './providers/utils';\n\n export interface ImageProps {\n /**\n * The path to the image you want to embed.\n */\n src: string;\n\n /**\n * Native srcset attribute.\n * One or more strings separated by commas, indicating possible image sources\n * Can only be used with provider=static, otherwise it's ignored and auto-generated with `sizes` usage\n */\n srcset?: string;\n\n /**\n * For specifying responsive sizes\n */\n sizes?: string;\n\n /**\n * Where the image is served from.\n * If not provided, the provider will inherit from the Stash config `stashOptions.images.provider` (default: `static`).\n * - `static` for relative or absolute paths\n * - `cloudinary` for images served via Cloudinary\n */\n provider?: StashImageProviders;\n\n /**\n * For applying border radius\n */\n radius?: ImageRadii;\n\n /**\n * A custom static path the image src will be appended onto when provider=static.\n * Can be used to override the library-level default.\n */\n staticPath?: string;\n\n /**\n * TODO - https://leaflink.atlassian.net/browse/GRO-204\n * A custom function used to resolve a URL string for the image\n */\n // loader?: () => string;\n }\n\n defineOptions({\n inheritAttrs: false,\n });\n\n const BREAKPOINTS = {\n md: SCREEN_SIZES.md,\n lg: SCREEN_SIZES.lg,\n };\n const stashOptions = inject<StashProvideState>('stashOptions');\n const props = withDefaults(defineProps<ImageProps>(), {\n srcset: undefined,\n sizes: undefined,\n staticPath: undefined,\n provider: undefined,\n radius: 'none',\n // loader: undefined, // TODO - https://leaflink.atlassian.net/browse/GRO-204\n });\n\n const attrs = computed(() => {\n const { src, ...attrs } = useAttrs();\n\n attrs.sizes = imgSizes.value;\n attrs.srcset = imgSrcset.value;\n\n return attrs;\n });\n\n const isAbsoluteUrl = computed(() => {\n // return true if not an absolute url\n try {\n new URL(props.src);\n return true;\n } catch (e) {\n return false;\n }\n });\n\n const computedProvider = computed(() => props.provider || stashOptions?.images?.provider || ImageProviders.Static);\n\n const computedStaticPath = computed(() => props.staticPath ?? stashOptions?.staticPath);\n\n const isStatic = computed(() => computedProvider.value === ImageProviders.Static);\n\n const imgProvider = computed(() => providers[computedProvider.value]);\n\n const imgSrc = computed(() => (isStatic.value ? getProviderImage() : getProviderImage({ width: Screens.md })));\n\n const imgSizes = computed(() => (props.sizes ? getSizes() : props.sizes));\n\n const imgSrcset = computed(() => (props.sizes && !props.srcset && !isStatic.value ? getSources() : props.srcset));\n\n const parsedSizes = computed(() => {\n return props.sizes ? parseSizes(props.sizes) : [];\n });\n\n function getProviderImage(modifiers: ImageModifiers = {}) {\n if (isStatic.value && isAbsoluteUrl.value) {\n return props.src;\n }\n\n const src = isStatic.value && computedStaticPath.value ? `${computedStaticPath.value}/${props.src}` : props.src;\n\n return imgProvider.value.getImageUrl(src, modifiers);\n }\n\n function getSources() {\n return Object.values(Screens)\n .map((width) => {\n const src = getProviderImage({ width });\n\n return `${src} ${width}w`;\n })\n .join(', ');\n }\n\n function getSizes() {\n // return if using native media conditions\n if (props.sizes?.includes('(')) {\n return props.sizes;\n }\n\n return parsedSizes.value.map((v) => `${v.mediaQuery ? v.mediaQuery + ' ' : ''}${v.size}`).join(', ');\n }\n\n function parseSizes(providedSizes = '') {\n const conditions: ImageSizeCondition[] = [];\n const sizes = {\n default: '100vw',\n };\n\n // parse sizes and convert to object\n if (typeof providedSizes === 'string') {\n const definitions = providedSizes.split(/[\\s]+/).filter((size) => size);\n\n for (const entry of definitions) {\n const size = entry.split(':');\n\n if (size.length !== 2) {\n sizes['default'] = size[0].trim();\n continue;\n }\n\n sizes[size[0].trim()] = size[1].trim();\n }\n } else {\n throw new Error('`sizes` needs to be a string');\n }\n\n for (const key in sizes) {\n const screenMinWidth = parseInt(BREAKPOINTS[key] || 0);\n let size = String(sizes[key]);\n const isFluidSize = size.endsWith('vw');\n\n // convert integer to pixels\n if (!isFluidSize && /^\\d+$/.test(size)) {\n size = `${size}px`;\n }\n\n if (!isFluidSize && size.endsWith('%')) {\n throw new Error('Image: `sizes` does not support percentage values');\n }\n\n const condition = {\n mediaQuery: screenMinWidth ? `(min-width: ${screenMinWidth}px)` : '',\n screenMinWidth,\n size,\n };\n\n conditions.push(condition);\n }\n\n conditions.sort((v1, v2) => (v1.screenMinWidth > v2.screenMinWidth ? -1 : 1));\n\n return conditions;\n }\n</script>\n\n<template>\n <img\n ref=\"img\"\n :key=\"imgSrc\"\n data-test=\"stash-image\"\n class=\"stash-image\"\n :class=\"{\n rounded: props.radius === ImageRadius.Rounded,\n 'rounded-full': props.radius === ImageRadius.Circle,\n }\"\n :src=\"imgSrc\"\n v-bind=\"attrs\"\n />\n</template>\n"],"names":["Screens","ImageRadius","ImageProviders","createMapper","map","key","buildProviderUrl","formatter","value","keyMap","joinWith","valueMap","keyMapper","valueKey","modifiers","mapper","newKey","newVal","BASE_URL","IMAGE_PROVIDER_URLS","convertHextoRGBFormat","operationsGenerator","defaultModifiers","getImageUrl","src","mergeModifiers","merge","operations","providers","cloudinary","staticProvider","BREAKPOINTS","SCREEN_SIZES","stashOptions","inject","props","__props","attrs","computed","useAttrs","imgSizes","imgSrcset","isAbsoluteUrl","computedProvider","_a","computedStaticPath","isStatic","imgProvider","imgSrc","getProviderImage","getSizes","getSources","parsedSizes","parseSizes","width","v","providedSizes","conditions","sizes","definitions","size","entry","screenMinWidth","isFluidSize","condition","v1","v2"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InputOptions.js","sources":["../src/components/InputOptions/InputOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n import { FieldProps } from '../Field/Field.types';\n import Field from '../Field/Field.vue';\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type Option = any;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type SelectedOption = any;\n\n export interface InputOptionsProps extends FieldProps {\n /**\n * The current value inclusive of the input & select\n */\n modelValue?: { value: string; option?: SelectedOption };\n\n /**\n * Input type\n */\n type?: string extends 'button' | 'checkbox' | 'radio' | 'submit' ? never : string;\n\n /**\n * Prevents the Selected Option from being truncated, if true\n */\n noTruncate?: boolean;\n\n /**\n * Options for the select\n */\n options?: Option[];\n\n /**\n * Placeholder text for the input\n * **Note:** placeholders should be used to display examples; they should not be used as labels because they are not accessible as labels. If a real label cannot be used, use the `aria-label` attribute.\n */\n placeholder?: string;\n\n /**\n * If `options` are an object, this is what prop to use for display.\n */\n displayBy?: string;\n\n /**\n * Default field to track selected options by.\n */\n trackBy?: string;\n }\n</script>\n\n<script lang=\"ts\" setup>\n import { ref, useAttrs, useCssModule, watch, watchEffect } from 'vue';\n\n import Input from '../Input/Input.vue';\n import Select from '../Select/Select.vue';\n\n defineOptions({\n name: 'll-input-options',\n });\n\n const props = withDefaults(defineProps<InputOptionsProps>(), {\n modelValue: () => ({ value: '', option: undefined }),\n noTruncate: false,\n options: () => [],\n type: 'text',\n placeholder: undefined,\n displayBy: undefined,\n trackBy: undefined,\n });\n\n const emit = defineEmits<{\n /**\n * Emitted when the model value changes\n */\n (\n e: 'update:model-value',\n v: { value?: string; option?: SelectedOption; isValueChange: boolean; type: 'input' | 'select' },\n ): void;\n /**\n * Emitted when either the input or select changes\n */\n (\n e: 'change',\n v: { value?: string; option?: SelectedOption; isValueChange: boolean; type: 'input' | 'select' },\n ): void;\n }>();\n\n const slots = defineSlots<{\n /**\n * Default slot for rendering a custom input field.\n */\n default: (props: { fieldId: string }) => void;\n /**\n * Hint slot for rendering a custom hint text.\n */\n hint: () => unknown;\n }>();\n\n const attrs = useAttrs();\n const classes = useCssModule();\n const internalInput = ref<string>();\n const isInputFocused = ref(false);\n const selectedOption = ref<Option | undefined>();\n\n // Input value changed\n function handleInput(val?: string | number) {\n internalInput.value = String(val);\n\n emit('update:model-value', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: true,\n type: 'input',\n });\n }\n\n // Input blurred\n function handleInputChange() {\n emit('change', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: true,\n type: 'input',\n });\n }\n\n function handleSelectChange(val?: Option) {\n selectedOption.value = val;\n\n emit('change', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: false,\n type: 'select',\n });\n emit('update:model-value', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: false,\n type: 'select',\n });\n }\n\n watchEffect(() => {\n if (!selectedOption.value) {\n selectedOption.value = props.options[0];\n }\n });\n\n watch(\n () => props.modelValue.value,\n () => {\n internalInput.value = props.modelValue.value;\n },\n { immediate: true },\n );\n\n watch(\n () => props.modelValue.option,\n () => {\n selectedOption.value = props.modelValue.option;\n },\n { immediate: true },\n );\n\n if (attrs.value) {\n throw new Error('ll-input-options: use :model-value or v-model instead of :value.');\n }\n\n if (attrs.onInput) {\n throw new Error('ll-input-options: use the @update:model-value event instead of @input');\n }\n</script>\n\n<template>\n <Field v-bind=\"props\" class=\"stash-input-options\" data-test=\"stash-input-options\">\n <template #default=\"{ fieldId, labelId }\">\n <div v-if=\"props.isReadOnly\" class=\"flex h-input items-center text-sm\">\n <span :id=\"fieldId\" :aria-labelledby=\"labelId\" class=\"h-min\">\n {{ internalInput || 0 }} {{ selectedOption?.name }}\n </span>\n </div>\n <div v-else class=\"flex\" :class=\"[classes['input-wrapper'], { [classes['has-error']]: !!props.errorText }]\">\n <slot :field-id=\"fieldId\">\n <Input\n :id=\"fieldId\"\n class=\"stash-input-options__input\"\n data-test=\"stash-input-options|input\"\n :type=\"props.type\"\n :model-value=\"internalInput\"\n :disabled=\"props.isDisabled || props.disabled\"\n :placeholder=\"props.placeholder\"\n @change=\"handleInputChange\"\n @update:model-value=\"handleInput\"\n @blur=\"isInputFocused = false\"\n @focus=\"isInputFocused = true\"\n />\n </slot>\n\n <Select\n single\n hide-search\n prevent-empty\n class=\"stash-input-options__select min-w-20\"\n data-test=\"stash-input-options|select\"\n :class=\"classes.select\"\n :no-truncate=\"noTruncate\"\n :options=\"options\"\n :model-value=\"selectedOption\"\n :display-by=\"props.displayBy\"\n :track-by=\"props.trackBy\"\n :disabled=\"props.isDisabled || props.disabled\"\n @update:model-value=\"handleSelectChange\"\n />\n </div>\n </template>\n <template v-if=\"slots.hint?.()\" #hint>\n <!-- @slot Hint slot for rendering text below the input -->\n <slot name=\"hint\"></slot>\n </template>\n </Field>\n</template>\n\n<style module>\n @layer utilities {\n .input-wrapper {\n & > div:has(input) {\n display: inline-block;\n flex: 1;\n margin-right: -1px;\n }\n\n & input {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n }\n\n &.has-error input,\n &.has-error input:hover:not(:focus),\n &.has-error :global(.stash-select__content),\n &.has-error :global(.stash-select__content:hover:not(:focus)) {\n border-color: var(--color-red-500);\n }\n\n & div:has(input:focus) {\n z-index: 200 !important;\n }\n }\n\n .select :global(.stash-select__content) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n }\n\n .select:global(.stash-select--active .stash-select__content) {\n min-width: 0;\n }\n }\n</style>\n"],"names":["props","__props","emit","__emit","slots","_useSlots","attrs","useAttrs","classes","useCssModule","internalInput","ref","isInputFocused","selectedOption","handleInput","val","handleInputChange","handleSelectChange","watchEffect","watch"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DE,UAAMA,IAAQC,GAURC,IAAOC,GAiBPC,IAAQC,EAAA,GAWRC,IAAQC,EAAA,GACRC,IAAUC,EAAA,GACVC,IAAgBC,EAAA,GAChBC,IAAiBD,EAAI,EAAK,GAC1BE,IAAiBF,EAAA;AAGvB,aAASG,EAAYC,GAAuB;AAC1C,MAAAL,EAAc,QAAQ,OAAOK,CAAG,GAEhCb,EAAK,sBAAsB;AAAA,QACzB,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAGA,aAASG,IAAoB;AAC3B,MAAAd,EAAK,UAAU;AAAA,QACb,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAEA,aAASI,EAAmBF,GAAc;AACxC,MAAAF,EAAe,QAAQE,GAEvBb,EAAK,UAAU;AAAA,QACb,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP,GACDX,EAAK,sBAAsB;AAAA,QACzB,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAwBA,QAtBAK,EAAY,MAAM;AAChB,MAAKL,EAAe,UAClBA,EAAe,QAAQb,EAAM,QAAQ,CAAC;AAAA,IAE1C,CAAC,GAEDmB;AAAA,MACE,MAAMnB,EAAM,WAAW;AAAA,MACvB,MAAM;AACJ,QAAAU,EAAc,QAAQV,EAAM,WAAW;AAAA,MACzC;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAK,GAGpBmB;AAAA,MACE,MAAMnB,EAAM,WAAW;AAAA,MACvB,MAAM;AACJ,QAAAa,EAAe,QAAQb,EAAM,WAAW;AAAA,MAC1C;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAK,GAGhBM,EAAM;AACR,YAAM,IAAI,MAAM,kEAAkE;AAGpF,QAAIA,EAAM;AACR,YAAM,IAAI,MAAM,uEAAuE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"InputOptions.js","sources":["../src/components/InputOptions/InputOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n import { FieldProps } from '../Field/Field.types';\n import Field from '../Field/Field.vue';\n\n type Option = any;\n\n type SelectedOption = any;\n\n export interface InputOptionsProps extends FieldProps {\n /**\n * The current value inclusive of the input & select\n */\n modelValue?: { value: string; option?: SelectedOption };\n\n /**\n * Input type\n */\n type?: string extends 'button' | 'checkbox' | 'radio' | 'submit' ? never : string;\n\n /**\n * Prevents the Selected Option from being truncated, if true\n */\n noTruncate?: boolean;\n\n /**\n * Options for the select\n */\n options?: Option[];\n\n /**\n * Placeholder text for the input\n * **Note:** placeholders should be used to display examples; they should not be used as labels because they are not accessible as labels. If a real label cannot be used, use the `aria-label` attribute.\n */\n placeholder?: string;\n\n /**\n * If `options` are an object, this is what prop to use for display.\n */\n displayBy?: string;\n\n /**\n * Default field to track selected options by.\n */\n trackBy?: string;\n }\n</script>\n\n<script lang=\"ts\" setup>\n import { ref, useAttrs, useCssModule, watch, watchEffect } from 'vue';\n\n import Input from '../Input/Input.vue';\n import Select from '../Select/Select.vue';\n\n defineOptions({\n name: 'll-input-options',\n });\n\n const props = withDefaults(defineProps<InputOptionsProps>(), {\n modelValue: () => ({ value: '', option: undefined }),\n noTruncate: false,\n options: () => [],\n type: 'text',\n placeholder: undefined,\n displayBy: undefined,\n trackBy: undefined,\n });\n\n const emit = defineEmits<{\n /**\n * Emitted when the model value changes\n */\n (\n e: 'update:model-value',\n v: { value?: string; option?: SelectedOption; isValueChange: boolean; type: 'input' | 'select' },\n ): void;\n /**\n * Emitted when either the input or select changes\n */\n (\n e: 'change',\n v: { value?: string; option?: SelectedOption; isValueChange: boolean; type: 'input' | 'select' },\n ): void;\n }>();\n\n const slots = defineSlots<{\n /**\n * Default slot for rendering a custom input field.\n */\n default: (props: { fieldId: string }) => void;\n /**\n * Hint slot for rendering a custom hint text.\n */\n hint: () => unknown;\n }>();\n\n const attrs = useAttrs();\n const classes = useCssModule();\n const internalInput = ref<string>();\n const isInputFocused = ref(false);\n const selectedOption = ref<Option | undefined>();\n\n // Input value changed\n function handleInput(val?: string | number) {\n internalInput.value = String(val);\n\n emit('update:model-value', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: true,\n type: 'input',\n });\n }\n\n // Input blurred\n function handleInputChange() {\n emit('change', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: true,\n type: 'input',\n });\n }\n\n function handleSelectChange(val?: Option) {\n selectedOption.value = val;\n\n emit('change', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: false,\n type: 'select',\n });\n emit('update:model-value', {\n value: internalInput.value,\n option: selectedOption.value,\n isValueChange: false,\n type: 'select',\n });\n }\n\n watchEffect(() => {\n if (!selectedOption.value) {\n selectedOption.value = props.options[0];\n }\n });\n\n watch(\n () => props.modelValue.value,\n () => {\n internalInput.value = props.modelValue.value;\n },\n { immediate: true },\n );\n\n watch(\n () => props.modelValue.option,\n () => {\n selectedOption.value = props.modelValue.option;\n },\n { immediate: true },\n );\n\n if (attrs.value) {\n throw new Error('ll-input-options: use :model-value or v-model instead of :value.');\n }\n\n if (attrs.onInput) {\n throw new Error('ll-input-options: use the @update:model-value event instead of @input');\n }\n</script>\n\n<template>\n <Field v-bind=\"props\" class=\"stash-input-options\" data-test=\"stash-input-options\">\n <template #default=\"{ fieldId, labelId }\">\n <div v-if=\"props.isReadOnly\" class=\"flex h-input items-center text-sm\">\n <span :id=\"fieldId\" :aria-labelledby=\"labelId\" class=\"h-min\">\n {{ internalInput || 0 }} {{ selectedOption?.name }}\n </span>\n </div>\n <div v-else class=\"flex\" :class=\"[classes['input-wrapper'], { [classes['has-error']]: !!props.errorText }]\">\n <slot :field-id=\"fieldId\">\n <Input\n :id=\"fieldId\"\n class=\"stash-input-options__input\"\n data-test=\"stash-input-options|input\"\n :type=\"props.type\"\n :model-value=\"internalInput\"\n :disabled=\"props.isDisabled || props.disabled\"\n :placeholder=\"props.placeholder\"\n @change=\"handleInputChange\"\n @update:model-value=\"handleInput\"\n @blur=\"isInputFocused = false\"\n @focus=\"isInputFocused = true\"\n />\n </slot>\n\n <Select\n single\n hide-search\n prevent-empty\n class=\"stash-input-options__select min-w-20\"\n data-test=\"stash-input-options|select\"\n :class=\"classes.select\"\n :no-truncate=\"noTruncate\"\n :options=\"options\"\n :model-value=\"selectedOption\"\n :display-by=\"props.displayBy\"\n :track-by=\"props.trackBy\"\n :disabled=\"props.isDisabled || props.disabled\"\n @update:model-value=\"handleSelectChange\"\n />\n </div>\n </template>\n <template v-if=\"slots.hint?.()\" #hint>\n <!-- @slot Hint slot for rendering text below the input -->\n <slot name=\"hint\"></slot>\n </template>\n </Field>\n</template>\n\n<style module>\n @layer utilities {\n .input-wrapper {\n & > div:has(input) {\n display: inline-block;\n flex: 1;\n margin-right: -1px;\n }\n\n & input {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n }\n\n &.has-error input,\n &.has-error input:hover:not(:focus),\n &.has-error :global(.stash-select__content),\n &.has-error :global(.stash-select__content:hover:not(:focus)) {\n border-color: var(--color-red-500);\n }\n\n & div:has(input:focus) {\n z-index: 200 !important;\n }\n }\n\n .select :global(.stash-select__content) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n }\n\n .select:global(.stash-select--active .stash-select__content) {\n min-width: 0;\n }\n }\n</style>\n"],"names":["props","__props","emit","__emit","slots","_useSlots","attrs","useAttrs","classes","useCssModule","internalInput","ref","isInputFocused","selectedOption","handleInput","val","handleInputChange","handleSelectChange","watchEffect","watch"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDE,UAAMA,IAAQC,GAURC,IAAOC,GAiBPC,IAAQC,EAAA,GAWRC,IAAQC,EAAA,GACRC,IAAUC,EAAA,GACVC,IAAgBC,EAAA,GAChBC,IAAiBD,EAAI,EAAK,GAC1BE,IAAiBF,EAAA;AAGvB,aAASG,EAAYC,GAAuB;AAC1C,MAAAL,EAAc,QAAQ,OAAOK,CAAG,GAEhCb,EAAK,sBAAsB;AAAA,QACzB,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAGA,aAASG,IAAoB;AAC3B,MAAAd,EAAK,UAAU;AAAA,QACb,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAEA,aAASI,EAAmBF,GAAc;AACxC,MAAAF,EAAe,QAAQE,GAEvBb,EAAK,UAAU;AAAA,QACb,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP,GACDX,EAAK,sBAAsB;AAAA,QACzB,OAAOQ,EAAc;AAAA,QACrB,QAAQG,EAAe;AAAA,QACvB,eAAe;AAAA,QACf,MAAM;AAAA,MAAA,CACP;AAAA,IACH;AAwBA,QAtBAK,EAAY,MAAM;AAChB,MAAKL,EAAe,UAClBA,EAAe,QAAQb,EAAM,QAAQ,CAAC;AAAA,IAE1C,CAAC,GAEDmB;AAAA,MACE,MAAMnB,EAAM,WAAW;AAAA,MACvB,MAAM;AACJ,QAAAU,EAAc,QAAQV,EAAM,WAAW;AAAA,MACzC;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAK,GAGpBmB;AAAA,MACE,MAAMnB,EAAM,WAAW;AAAA,MACvB,MAAM;AACJ,QAAAa,EAAe,QAAQb,EAAM,WAAW;AAAA,MAC1C;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAK,GAGhBM,EAAM;AACR,YAAM,IAAI,MAAM,kEAAkE;AAGpF,QAAIA,EAAM;AACR,YAAM,IAAI,MAAM,uEAAuE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2322,16 +2322,12 @@ noTruncate: boolean;
2322
2322
  $slots: Readonly<{
2323
2323
  default: (props: {
2324
2324
  fieldId: string;
2325
- }) => void; /**
2326
- * Make search fuzzy. Only for client side search.
2327
- */
2325
+ }) => void;
2328
2326
  hint: () => unknown;
2329
2327
  }> & {
2330
2328
  default: (props: {
2331
2329
  fieldId: string;
2332
- }) => void; /**
2333
- * Make search fuzzy. Only for client side search.
2334
- */
2330
+ }) => void;
2335
2331
  hint: () => unknown;
2336
2332
  };
2337
2333
  });
@@ -3012,10 +3008,7 @@ teleportTo: string | HTMLElement;
3012
3008
  $slots: Readonly<{
3013
3009
  selected(props: {
3014
3010
  option: any;
3015
- onRemove: () => void; /**
3016
- * Helps determine if the list is filtered or not when there is an incomplete
3017
- * result list, such as when isServerSide is true.
3018
- */
3011
+ onRemove: () => void;
3019
3012
  chipSelectedClass: string;
3020
3013
  }): unknown;
3021
3014
  option(props: {
@@ -3026,10 +3019,7 @@ hint(): unknown;
3026
3019
  }> & {
3027
3020
  selected(props: {
3028
3021
  option: any;
3029
- onRemove: () => void; /**
3030
- * Helps determine if the list is filtered or not when there is an incomplete
3031
- * result list, such as when isServerSide is true.
3032
- */
3022
+ onRemove: () => void;
3033
3023
  chipSelectedClass: string;
3034
3024
  }): unknown;
3035
3025
  option(props: {
package/dist/Modals.js CHANGED
@@ -30,7 +30,7 @@ const k = ["innerHTML"], H = /* @__PURE__ */ d({
30
30
  _: 1
31
31
  }));
32
32
  }
33
- }), B = /* @__PURE__ */ h(H, [["__scopeId", "data-v-18d97348"]]);
33
+ }), B = /* @__PURE__ */ h(H, [["__scopeId", "data-v-27678e71"]]);
34
34
  export {
35
35
  B as default
36
36
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Modals.js","sources":["../src/components/Modals/Modals.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { computed, DeepReadonly } from 'vue';\n\n import useModals, { type Modal } from '../../composables/useModals/useModals';\n\n const modals = useModals();\n\n const currentModal = computed(() => modals.current);\n\n // Stupid TS version issues. Todo: remove ` | any` when TS/vite/vue deps are updated.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function getListeners(modal: DeepReadonly<Modal> | any) {\n if (modal.options?.disableDefaultListeners) {\n return {};\n }\n\n return {\n dismiss: () => modals.close({ index: 0 }),\n close: () => modals.close({ index: 0 }),\n cancel: () => modals.close({ index: 0 }),\n };\n }\n</script>\n\n<template>\n <Transition name=\"modals\" mode=\"out-in\">\n <component\n :is=\"currentModal.component\"\n v-if=\"currentModal\"\n v-bind=\"currentModal.attributes\"\n :is-open=\"true\"\n v-on=\"getListeners(currentModal)\"\n >\n <template v-for=\"(value, name) in currentModal.slots\" :key=\"name\" #[name]>\n <!-- eslint-disable-next-line vue/no-v-html -->\n <div v-html=\"value\" />\n </template>\n </component>\n </Transition>\n</template>\n\n<style scoped>\n @layer utilities {\n .modals-enter-active,\n .modals-leave-active {\n --swing-timing-function: cubic-bezier(0.25, 0.8, 0.5, 1); /* \"swing\" from the $transition sass map */\n transition: opacity 165ms var(--swing-timing-function);\n }\n\n .modals-enter-from,\n .modals-leave-to {\n opacity: 0;\n }\n\n .modals-enter-active :deep(.stash-modal__dialog--position-center),\n .modals-leave-active :deep(.stash-modal__dialog--position-center) {\n transition: transform 165ms var(--swing-timing-function);\n }\n\n .modals-enter-from :deep(.stash-modal__dialog--position-center),\n .modals-leave-to :deep(.stash-modal__dialog--position-center) {\n transform: translateY(20px);\n }\n\n .modals-enter-active :deep(.stash-modal__dialog--is-drawer),\n .modals-leave-active :deep(.stash-modal__dialog--is-drawer) {\n transition: transform 165ms var(--swing-timing-function);\n }\n\n .modals-enter-from :deep(.stash-modal__dialog--position-left),\n .modals-leave-to :deep(.stash-modal__dialog--position-left) {\n transform: translateX(-50px);\n }\n\n .modals-enter-from :deep(.stash-modal__dialog--position-right),\n .modals-leave-to :deep(.stash-modal__dialog--position-right) {\n transform: translateX(50px);\n }\n }\n</style>\n"],"names":["modals","useModals","currentModal","computed","getListeners","modal","_a"],"mappings":";;;;;;AAKE,UAAMA,IAASC,EAAA,GAETC,IAAeC,EAAS,MAAMH,EAAO,OAAO;AAIlD,aAASI,EAAaC,GAAkC;;AACtD,cAAIC,IAAAD,EAAM,YAAN,QAAAC,EAAe,0BACV,CAAA,IAGF;AAAA,QACL,SAAS,MAAMN,EAAO,MAAM,EAAE,OAAO,GAAG;AAAA,QACxC,OAAO,MAAMA,EAAO,MAAM,EAAE,OAAO,GAAG;AAAA,QACtC,QAAQ,MAAMA,EAAO,MAAM,EAAE,OAAO,GAAG;AAAA,MAAA;AAAA,IAE3C;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"Modals.js","sources":["../src/components/Modals/Modals.vue"],"sourcesContent":["<script lang=\"ts\" setup>\n import { computed, DeepReadonly } from 'vue';\n\n import useModals, { type Modal } from '../../composables/useModals/useModals';\n\n const modals = useModals();\n\n const currentModal = computed(() => modals.current);\n\n // Stupid TS version issues. Todo: remove ` | any` when TS/vite/vue deps are updated.\n\n function getListeners(modal: DeepReadonly<Modal> | any) {\n if (modal.options?.disableDefaultListeners) {\n return {};\n }\n\n return {\n dismiss: () => modals.close({ index: 0 }),\n close: () => modals.close({ index: 0 }),\n cancel: () => modals.close({ index: 0 }),\n };\n }\n</script>\n\n<template>\n <Transition name=\"modals\" mode=\"out-in\">\n <component\n :is=\"currentModal.component\"\n v-if=\"currentModal\"\n v-bind=\"currentModal.attributes\"\n :is-open=\"true\"\n v-on=\"getListeners(currentModal)\"\n >\n <template v-for=\"(value, name) in currentModal.slots\" :key=\"name\" #[name]>\n <!-- eslint-disable-next-line vue/no-v-html -->\n <div v-html=\"value\" />\n </template>\n </component>\n </Transition>\n</template>\n\n<style scoped>\n @layer utilities {\n .modals-enter-active,\n .modals-leave-active {\n --swing-timing-function: cubic-bezier(0.25, 0.8, 0.5, 1); /* \"swing\" from the $transition sass map */\n transition: opacity 165ms var(--swing-timing-function);\n }\n\n .modals-enter-from,\n .modals-leave-to {\n opacity: 0;\n }\n\n .modals-enter-active :deep(.stash-modal__dialog--position-center),\n .modals-leave-active :deep(.stash-modal__dialog--position-center) {\n transition: transform 165ms var(--swing-timing-function);\n }\n\n .modals-enter-from :deep(.stash-modal__dialog--position-center),\n .modals-leave-to :deep(.stash-modal__dialog--position-center) {\n transform: translateY(20px);\n }\n\n .modals-enter-active :deep(.stash-modal__dialog--is-drawer),\n .modals-leave-active :deep(.stash-modal__dialog--is-drawer) {\n transition: transform 165ms var(--swing-timing-function);\n }\n\n .modals-enter-from :deep(.stash-modal__dialog--position-left),\n .modals-leave-to :deep(.stash-modal__dialog--position-left) {\n transform: translateX(-50px);\n }\n\n .modals-enter-from :deep(.stash-modal__dialog--position-right),\n .modals-leave-to :deep(.stash-modal__dialog--position-right) {\n transform: translateX(50px);\n }\n }\n</style>\n"],"names":["modals","useModals","currentModal","computed","getListeners","modal","_a"],"mappings":";;;;;;AAKE,UAAMA,IAASC,EAAA,GAETC,IAAeC,EAAS,MAAMH,EAAO,OAAO;AAIlD,aAASI,EAAaC,GAAkC;;AACtD,cAAIC,IAAAD,EAAM,YAAN,QAAAC,EAAe,0BACV,CAAA,IAGF;AAAA,QACL,SAAS,MAAMN,EAAO,MAAM,EAAE,OAAO,GAAG;AAAA,QACxC,OAAO,MAAMA,EAAO,MAAM,EAAE,OAAO,GAAG;AAAA,QACtC,QAAQ,MAAMA,EAAO,MAAM,EAAE,OAAO,GAAG;AAAA,MAAA;AAAA,IAE3C;;;;;;;;;;;;;;;;;;;"}
package/dist/Select.js CHANGED
@@ -496,7 +496,7 @@ const rt = { class: "flex h-input items-center text-sm" }, it = ["id", "aria-lab
496
496
  ]), 1040, ["class", "error-text", "hint-text"]);
497
497
  };
498
498
  }
499
- }), Ut = /* @__PURE__ */ ot(kt, [["__scopeId", "data-v-aac0d067"]]);
499
+ }), Ut = /* @__PURE__ */ ot(kt, [["__scopeId", "data-v-7aa12005"]]);
500
500
  export {
501
501
  Ut as default
502
502
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","sources":["../src/components/Select/Select.vue"],"sourcesContent":["<script lang=\"ts\">\n import { FieldProps } from '../Field/Field.types';\n\n // Note: the `trackBy` prop, the `single` prop, and the fact that options can be strings prevents us from knowing the type of the selected options\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type Option = any;\n\n type SelectedOptions = Option | Option[];\n\n export interface SelectProps extends FieldProps {\n /**\n * If there is only 1 option selected, it prevents that option from being de-selected\n */\n preventEmpty?: boolean;\n\n /**\n * @deprecated The `allowEmpty` prop is no longer supported; use `preventEmpty` instead.\n */\n allowEmpty?: string | boolean | null;\n\n /**\n * If `options` are an object, this is what prop to use for display.\n */\n displayBy?: string;\n\n /**\n * List of one or more fields to search on. When empty, displayBy will be used\n */\n searchBy?: string[];\n\n /**\n * Default field to track selected options by.\n */\n trackBy?: string;\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n\n /**\n * @deprecated Use the `error-text` prop instead\n */\n error?: string;\n\n /**\n * @deprecated Use the `hint-text` prop instead\n */\n hint?: string;\n\n /**\n * Sets a custom icon.\n */\n icon?: string | boolean;\n\n /**\n * Sets a name attribute on the native (hidden) select element\n *\n * Setting the default value to `undefined` so the attribute isn't rendered if not explicitly provided to ensure backwards compatibility with MP django forms\n */\n name?: string;\n\n /**\n * The list of all options to select from.\n */\n options?: Option[] | readonly Option[];\n\n /**\n * Lazily evaluate and set component `options`. Will execute upon user mouseover.\n */\n lazy?: () => Option[];\n\n /**\n * Sets the currently-selected value(s) for the component.\n * Accepts an array of Objects, or a single Object (if `single` is true), or a string\n */\n modelValue?: SelectedOptions;\n\n /**\n * @deprecated Use :model-value or v-model instead of :value.\n */\n value?: SelectedOptions | null;\n\n /**\n * Hides the search input\n */\n hideSearch?: boolean;\n\n /**\n * @deprecated The `searchable` prop is no longer supported; Use `hideSearch` instead.\n */\n searchable?: string | boolean | null;\n\n /**\n * If true, prevents the search term from being cleared when the drawer is dismissed.\n */\n preserveSearchTerm?: boolean;\n\n /**\n * Prevents the options from being filtered when the search term changes.\n * Allows the parent to filter the options.\n */\n disableFiltering?: boolean;\n\n /**\n * Functions as a single select, if true\n */\n single?: boolean;\n\n /**\n * Prevents the Selected Option from being truncated, if true\n */\n noTruncate?: boolean;\n\n /**\n * On an ajaxed request when the dropdown is open, we may be experiencing slower load times.\n * This flag will drop a loader into the dropdown. Note: This has never been design reviewed.\n */\n loading?: boolean;\n\n /**\n * @deprecated Instead, use the `@search` event (the onSearch prop) and ensure its event handler returns a Promise that resolves after the search is complete.\n */\n searchLoading?: boolean;\n\n /**\n * In the selection text we use this to give more visual distinction to what type of item(s) are selected\n * E.g. 2 customers selected. An empty string will result in `2 selected`.\n */\n selectItemType?: string;\n\n /**\n * Hides the \"check\" icon if truthy\n */\n hideCheck?: boolean;\n\n searchPlaceholder?: string;\n\n /**\n * Equivalent to emitting a \"search\" event, except this prop can be used to populate the list of `options` using an HTTP request because a prop's return value can be received and awaited, unlike an event.\n *\n * **Tip:** to show a loading indicator while searching, return a Promise that resolves after the search is complete.\n *\n * **Warning:** the search input is debounced so there is no need to debounce this function.\n */\n onSearch?: (searchTerm: string) => Promise<void> | void;\n\n /**\n * Uses the \"fuzzy search\" algorithm when searching, if true\n */\n useFuzzySearch?: boolean;\n\n /**\n * Sets the placement of the dropdown\n * @default 'bottom-start'\n */\n menuPlacement?: Placement;\n\n /**\n * Enables teleporting the dropdown\n * @default false\n */\n enableTeleport?: boolean;\n\n /**\n * The selector or element to which the dropdown should be teleported\n * @default `'#stash-menus-mount-node'`\n */\n teleportTo?: string | HTMLElement;\n }\n</script>\n\n<script setup lang=\"ts\">\n import { autoUpdate, flip, offset, type Placement, type Side, size, useFloating } from '@floating-ui/vue';\n import logger from '@leaflink/snitch';\n import debounce from 'lodash-es/debounce';\n import isEmpty from 'lodash-es/isEmpty';\n import isEqual from 'lodash-es/isEqual';\n import isPlainObject from 'lodash-es/isPlainObject';\n import uniqueId from 'lodash-es/uniqueId';\n import { computed, nextTick, onMounted, onUnmounted, type Ref, ref, useAttrs, watch } from 'vue';\n\n import useSearch from '../../composables/useSearch/useSearch';\n import { DEBOUNCE, KEY_CODES } from '../../constants';\n import vClickoutside from '../../directives/clickoutside/clickoutside';\n import { DEFAULT_MENUS_PLUGIN_NODE_ID } from '../../plugins/MenusPlugin';\n import Chip from '../Chip/Chip.vue';\n import Field from '../Field/Field.vue';\n import Icon, { IconName } from '../Icon/Icon.vue';\n\n const GAP_HEIGHT = 6;\n\n const MENU_MAX_HEIGHT_PX = 300;\n\n defineOptions({\n name: 'll-select',\n });\n\n const props = withDefaults(defineProps<SelectProps>(), {\n preventEmpty: false,\n allowEmpty: null,\n displayBy: 'name',\n searchBy: () => [],\n trackBy: 'id',\n placeholder: 'Select option',\n error: '',\n hint: '',\n icon: 'caret-down',\n name: undefined,\n options: () => [],\n lazy: undefined,\n modelValue: () => [],\n value: null,\n hideSearch: false,\n searchable: null,\n preserveSearchTerm: false,\n disableFiltering: false,\n single: false,\n noTruncate: false,\n loading: false,\n searchLoading: false,\n selectItemType: '',\n hideCheck: false,\n searchPlaceholder: 'Search',\n onSearch: undefined,\n useFuzzySearch: false,\n menuPlacement: 'bottom-start',\n enableTeleport: false,\n teleportTo: `#${DEFAULT_MENUS_PLUGIN_NODE_ID}`,\n });\n\n const emit = defineEmits<{\n /**\n * Emitted when the model value changes.\n */\n (e: 'update:model-value', selectedOptions: SelectedOptions): void;\n /**\n * Emitted the selected value(s) a cleared.\n */\n (e: 'clear'): void;\n /**\n * Emitted when an option is added.\n */\n (e: 'add', optionAdded: Option): void;\n /**\n * Emitted when an option is removed.\n */\n (e: 'remove', optionRemoved: Option, index: number): void;\n /**\n * Emitted when the select is opened.\n */\n (e: 'opened'): void;\n /**\n * Emitted when the select is closed.\n */\n (e: 'closed', selectedOptions: SelectedOptions): void;\n }>();\n\n const attrs = useAttrs();\n const slots = defineSlots<{\n /**\n * Selected value(s) custom text. Exposes the option object and remove function.\n */\n selected(props: {\n /**\n * The selected option object.\n */\n option: Option;\n /**\n * Function to remove the selected option from the selection.\n */\n onRemove: () => void;\n /**\n * Class to apply to the selected chip.\n */\n chipSelectedClass: string;\n }): unknown;\n /**\n * Select custom option text. Exposes the option object\n */\n option(props: {\n /**\n * Option The option instance for this list item.\n */\n option: Option;\n }): unknown;\n /**\n * Slot for when there are no options available.\n */\n 'no-options'(): unknown;\n /**\n * Slot to display a helpful hint message below the select field.\n */\n hint(): unknown;\n }>();\n\n const selectRef = ref<HTMLDivElement | null>(null);\n const contentRef = ref<HTMLDivElement | null>(null);\n const optionsWrapperRef = ref<HTMLDivElement | null>(null);\n const chipsRef = ref<HTMLUListElement | null>(null);\n const searchRef = ref<HTMLInputElement | null>(null);\n\n const chipsHeight = ref(0);\n const contentHeight = ref(0);\n const internalValue = ref<Option[]>([]) as Ref<Option[]>;\n const searchFor = ref<(searchTerm: string) => Option[]>(() => []); // initialized in the watch handler for options\n const searchTerm = ref('');\n const activeIndex = ref(-1);\n const isActive = ref(false);\n const pendingSearchRequests = ref({});\n const shouldUseLazyOptions = ref(false);\n\n const { floatingStyles, update: updateFloating } = useFloating(selectRef, optionsWrapperRef, {\n whileElementsMounted: autoUpdate,\n placement: props.menuPlacement,\n middleware: [\n flip(),\n offset(({ rects, placement }) => {\n // gets the current placement side, especially after flip() is run\n const placementSide = placement.split('-')[0] as Side;\n const isBottom = placementSide === ('bottom' satisfies Side);\n\n // if the placement is at the bottom, we need to adjust the offset\n if (isBottom && chipsHeight.value > contentHeight.value) {\n return chipsHeight.value - rects.reference.height + GAP_HEIGHT;\n } else {\n return GAP_HEIGHT;\n }\n }),\n size({\n apply({ availableHeight, elements, rects }) {\n if (props.enableTeleport) {\n Object.assign(elements.floating.style, {\n maxWidth: `${rects.reference.width}px`,\n maxHeight: `${Math.min(availableHeight, MENU_MAX_HEIGHT_PX)}px`,\n });\n }\n },\n }),\n ],\n });\n const resizeObserverChips = new ResizeObserver(() => debouncedUpdateChipsHeight());\n\n const isSearchable = computed(() => !props.hideSearch && props.searchable !== 'false' && props.searchable !== false);\n\n const internalOptions = computed(() => {\n let OPTIONS = (shouldUseLazyOptions.value && props.lazy ? props.lazy() : props.options).filter(Boolean); // prevent [undefined]\n\n // if `options` is an array... but not an array of objects\n if (!isPlainObject(OPTIONS[0])) {\n OPTIONS = OPTIONS.map((name, id) => ({ name, id }) as unknown as Option);\n }\n\n return OPTIONS;\n });\n\n const readOnlyValue = computed(() => {\n if (!internalValue.value.length) {\n return '';\n }\n\n return internalValue.value.map((option) => option[props.displayBy] || option).join(', ');\n });\n\n const isSearching = computed(() => props.searchLoading || Object.keys(pendingSearchRequests.value).length > 0);\n\n const filteredOptions = computed(() => {\n if (props.disableFiltering || !(searchTerm.value || '').trim()) {\n return internalOptions.value;\n }\n\n return searchFor.value(searchTerm.value);\n });\n\n const isChipsOneLine = computed(() => chipsHeight.value <= contentHeight.value);\n\n const shouldShowTotal = computed(\n () => !isActive.value && !props.single && !isChipsOneLine.value && internalValue.value.length,\n );\n\n const clearSelectionText = computed(() => {\n return [internalValue.value.length, props.selectItemType, 'selected'].filter(Boolean).join(' ');\n });\n\n const shouldPreventEmpty = computed(() => {\n return props.preventEmpty || props.allowEmpty === 'false' || props.allowEmpty === false;\n });\n\n watch(searchTerm, (currentValue, oldValue) => oldValue !== currentValue && setIndex(-1));\n\n watch(internalValue, () => debouncedUpdateChipsHeight(), { deep: true });\n\n watch(\n () => props.modelValue,\n (value) => {\n // multiple\n if (Array.isArray(value)) {\n const newValue = value.filter(Boolean); // sometimes modelValue is [undefined]\n\n if (!isEqual(newValue, internalValue.value)) {\n internalValue.value = newValue;\n }\n\n return;\n }\n\n // single; if value is a non-empty string or a non-empty object\n if (!isEmpty(value)) {\n if (!isEqual(value, internalValue.value[0])) {\n internalValue.value[0] = value;\n }\n\n return;\n }\n\n // if value is an empty object or falsy\n internalValue.value = [];\n },\n { immediate: true },\n );\n\n watch(\n () => props.options,\n () => {\n const { searchFor: searchForFn } = useSearch<Option>({\n items: computed(() => internalOptions.value),\n fieldNames: props.searchBy.length ? props.searchBy : [props.displayBy],\n trackBy: props.trackBy,\n });\n\n searchFor.value = (searchTerm) => searchForFn(searchTerm, { fuzzy: props.useFuzzySearch });\n },\n { immediate: true },\n );\n\n watch(chipsHeight, () => debouncedUpdateFloating());\n\n const debouncedUpdateFloating = debounce(() => updateFloating(), 50);\n const debouncedUpdateChipsHeight = debounce(() => updateChipsHeight(), 50);\n\n const isOptionDisabled = (option: Option) => {\n return option && typeof option === 'object' && 'disabled' in option && option?.disabled;\n };\n\n const clear = () => {\n internalValue.value = [];\n emit('update:model-value', props.single ? undefined : internalValue.value);\n emit('clear');\n };\n\n /**\n * Adds (or selects) an option.\n * @param {Option} option The selected option\n */\n const handleSelect = (option?: Option) => {\n if (props.disabled || !option || isOptionDisabled(option)) {\n return;\n }\n\n if (!isSelected(option)) {\n if (props.single) {\n internalValue.value = [];\n }\n\n internalValue.value.push(option);\n emit('update:model-value', props.single ? internalValue.value[0] : internalValue.value);\n emit('add', option);\n } else {\n remove(option);\n }\n\n if (props.single) {\n dismiss();\n }\n };\n\n const handleSelectChange = (evt) => {\n // If this is a single select, just pass along the option\n // This short-circuit is purely for readability, as the iterations for multi would also work for single selects\n if (props.single) {\n const selectedOption = internalOptions.value.find((opt) => opt[props.trackBy]?.toString() === evt.target.value);\n handleSelect(selectedOption);\n return;\n }\n\n // Spread the HTMLCollection (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection) to array so we can invoke `find`\n const selectedOptions = [...evt.target.selectedOptions];\n\n // If it's a multiselect, the HTML select used for the screen reader can have multiple items\n // deselected and a new one selected in one change event.\n // We need to reconcile the new selected options with our internal state\n\n // 1. remove all the items in our internal state that are no longer present\n // (Spread this array so we can remove items from it while iterating)\n for (const internalOption of [...internalValue.value]) {\n const selectedOption = selectedOptions.find((opt) => opt.value === internalOption[props.trackBy]?.toString());\n\n if (!selectedOption) {\n remove(internalOption);\n }\n }\n\n // 2. add all the items that are now selected if needed\n for (const selectedOption of selectedOptions) {\n const internalOption = internalOptions.value.find(\n (opt) => opt[props.trackBy]?.toString() === selectedOption.value,\n );\n\n if (isSelected(internalOption)) {\n continue;\n } else {\n handleSelect(internalOption);\n }\n }\n };\n\n /**\n * Removes (or un-selects) an option.\n * @param {Option} option The option to be removed\n */\n const remove = (option: Option) => {\n if (props.disabled || (shouldPreventEmpty.value && internalValue.value.length === 1)) {\n return;\n }\n\n const index = internalValue.value.findIndex((opt) => opt[props.trackBy] === option[props.trackBy]);\n\n if (index === -1) {\n logger.warn('ll-select: could not find option to remove', option);\n return;\n }\n\n internalValue.value.splice(index, 1);\n emit('update:model-value', props.single ? internalValue.value[0] : internalValue.value);\n emit('remove', option, index);\n };\n\n /**\n * Opens the `options` drawer and provides focus.\n */\n const open = () => {\n if (isActive.value) {\n return;\n }\n\n debouncedUpdateChipsHeight();\n\n isActive.value = true;\n emit('opened');\n\n if (isSearchable.value) {\n nextTick(() => {\n searchRef.value?.focus({ preventScroll: true });\n });\n }\n };\n\n /**\n * Closes the drawer and cleans up a few settings.\n */\n const dismiss = () => {\n if (isActive.value) {\n emit('closed', internalValue.value);\n }\n\n setIndex(-1);\n isActive.value = false;\n\n if (!props.preserveSearchTerm) {\n searchTerm.value = '';\n }\n\n // The optional chaining here is required.\n // There are cases when the component is removed from the DOM and the ref is lost.\n // .ie when the component is used in a modal and the modal is dismissed.\n // Tracked in this ticket: F-1893\n contentRef.value?.blur(); // if the component remains in focus, clicking it again will not open the options list\n };\n\n /**\n * Scroll list items when up and down keys are pressed\n * @param {KeyboardEvent} e The native keydown event.\n */\n const scrollItems = async (e: KeyboardEvent) => {\n await nextTick();\n\n if (!contentRef.value) {\n return;\n }\n\n const allowedKeyCodes = [KEY_CODES.ENTER, KEY_CODES.ESCAPE] as number[];\n\n // Wait for highlighted item update after using keyboard (`onKeyDown`)\n if (!allowedKeyCodes.includes(e.keyCode)) {\n const el = contentRef.value.querySelector('.stash-select__option--highlighted');\n const elBCR = el?.getBoundingClientRect();\n const parentBCR = contentRef.value.getBoundingClientRect();\n\n if (!elBCR || !parentBCR) {\n return;\n }\n\n if (elBCR.bottom >= parentBCR.bottom || elBCR.top <= parentBCR.top + elBCR.height) {\n el?.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n }\n };\n\n /**\n * Sets the `activeIndex` on hover, so that the user may then fine-tune\n * their selection with the arrow keys.\n */\n const setIndex = (index: number) => {\n activeIndex.value = index;\n };\n\n /**\n * Handles all user keyboard input on the Dropdown.\n * @param {KeyboardEvent} e The native keydown event.\n */\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.keyCode === KEY_CODES.ESCAPE) {\n dismiss();\n } else if (e.keyCode === KEY_CODES.DOWN && activeIndex.value < filteredOptions.value.length - 1 && isActive.value) {\n activeIndex.value++;\n } else if (e.keyCode === KEY_CODES.UP && activeIndex.value > 0) {\n if (isActive.value) {\n activeIndex.value--;\n }\n\n // Scroll to top when search is available and first element is highlighted (Using 100 to be sure that it'll be sure that search is visible)\n if (optionsWrapperRef.value && isSearchable.value && activeIndex.value === 0) {\n optionsWrapperRef.value.scrollTop -= 100;\n }\n } else if (e.keyCode === KEY_CODES.ENTER && activeIndex.value !== -1 && isActive.value) {\n handleSelect(filteredOptions.value[activeIndex.value]);\n } else {\n return;\n }\n\n e.preventDefault(); // if keyDown did something / we didn't return\n scrollItems(e);\n };\n\n const handleSearchInput = debounce(function () {\n search();\n }, DEBOUNCE.FAST);\n\n const search = async function () {\n if (!props.onSearch) {\n return;\n }\n\n const requestId = uniqueId('search-request-');\n\n try {\n pendingSearchRequests.value[requestId] = true;\n await props.onSearch(searchTerm.value);\n } finally {\n delete pendingSearchRequests.value[requestId];\n }\n };\n\n /**\n * Dynamically sets classes on an `option` in the drawer.\n * @param {Option} option The `option` for which to generate classes\n * @param {number} index The index of the `option` in the list of `options`\n */\n const optionClass = (option: Option, index: number) => {\n return [\n {\n 'stash-select__option': true,\n 'stash-select__option--disabled': isOptionDisabled(option),\n 'stash-select__option--highlighted': activeIndex.value === index,\n 'stash-select__option--selected': isSelected(option),\n },\n\n // @deprecated\n // backwards compatibility\n {\n 'is-selected': activeIndex.value === index,\n 'bg-ice-200': activeIndex.value === index,\n 'bg-blue-100 text-ice-700': isSelected(option),\n },\n ];\n };\n\n /**\n * Finds out if the given item is already present in the selected options\n * @param {Option} option The item to check\n * @returns {boolean} Returns true if element is selected\n */\n const isSelected = (option?: Option | null): boolean => {\n if (option === undefined || option === null) {\n return false;\n }\n\n return internalValue.value.some((value) => value?.[props.trackBy] === option[props.trackBy]);\n };\n\n /**\n * Checks if the element is within the Select menu by checking both the content and the teleportTo element\n */\n function isElementWithinSelectMenu(element: Node | null) {\n if (!element) {\n return false;\n }\n\n const isElementInMenu = contentRef.value?.contains(element);\n const isElementInTeleportedMenu = optionsWrapperRef.value?.contains(element);\n\n return isElementInMenu || isElementInTeleportedMenu;\n }\n\n function handleFocusOut(focusEvent: FocusEvent) {\n const nextFocusedElement = focusEvent.relatedTarget as Node | null;\n const isFocusedElementInMenu = isElementWithinSelectMenu(nextFocusedElement);\n\n if (!isFocusedElementInMenu && nextFocusedElement) {\n dismiss();\n }\n }\n\n function handleOutsideClick(event: MouseEvent) {\n const target = event.target as Node | null;\n const isFocusedElementInMenu = isElementWithinSelectMenu(target);\n\n if (!isFocusedElementInMenu) {\n dismiss();\n }\n }\n\n /**\n * Handles the mouse leave of the select and clears item highlight\n */\n const handleMouseLeave = () => {\n setIndex(-1);\n };\n\n /**\n * Update height of chips container\n */\n const updateChipsHeight = async () => {\n // Wait for the DOM to update so we can get an accurate value for chips' clientHeight\n await nextTick();\n\n if (chipsRef.value && contentRef.value) {\n contentHeight.value = contentHeight.value === 0 ? contentRef.value.clientHeight : contentHeight.value;\n chipsHeight.value = chipsRef.value.clientHeight;\n }\n };\n\n onMounted(() => {\n if (attrs.onInput) {\n throw new Error('ll-select: use the @update:model-value event instead of @input.');\n }\n\n if (chipsRef.value) {\n resizeObserverChips.observe(chipsRef.value);\n }\n\n window.addEventListener('resize', debouncedUpdateChipsHeight);\n\n debouncedUpdateChipsHeight();\n });\n\n /**\n * Remove event listener to handle component width when resizing window\n */\n onUnmounted(() => {\n window.removeEventListener('resize', updateChipsHeight);\n resizeObserverChips.disconnect();\n debouncedUpdateChipsHeight.cancel();\n });\n</script>\n\n<template>\n <Field\n v-bind=\"props\"\n class=\"input ll-select stash-select\"\n data-test=\"stash-select\"\n :class=\"attrs.class\"\n :error-text=\"props.errorText || props.error\"\n :hint-text=\"props.hintText || props.hint\"\n >\n <template v-if=\"props.isReadOnly\" #default=\"{ fieldId, labelId }\">\n <div class=\"flex h-input items-center text-sm\">\n <span :id=\"fieldId\" :aria-labelledby=\"labelId\" class=\"show-empty h-min\">\n {{ readOnlyValue }}\n </span>\n </div>\n </template>\n <template v-else #default=\"{ fieldId, fieldErrorId, hasError }\">\n <!-- SCREEN READER ONLY -->\n <select\n :id=\"fieldId\"\n :aria-errormessage=\"fieldErrorId\"\n :aria-invalid=\"hasError\"\n class=\"sr-only\"\n :disabled=\"props.disabled\"\n :multiple=\"!props.single\"\n :name=\"props.name\"\n @change=\"handleSelectChange\"\n >\n <option\n v-for=\"(option, index) in filteredOptions\"\n :key=\"`srOnlyOption-${index}`\"\n :selected=\"isSelected(option)\"\n :value=\"option[props.trackBy]\"\n :disabled=\"isOptionDisabled(option)\"\n >\n {{ option[props.displayBy] || '' }}\n </option>\n\n <!-- Empty option to allow deselecting non-multiselects -->\n <option value=\"\"></option>\n </select>\n\n <!-- SELECT -->\n <div\n ref=\"selectRef\"\n v-clickoutside=\"handleOutsideClick\"\n role=\"listbox\"\n aria-hidden=\"true\"\n class=\"stash-select__content-wrapper\"\n :aria-controls=\"'listbox-' + fieldId\"\n :aria-expanded=\"isActive\"\n :aria-label=\"props.placeholder\"\n :aria-disabled=\"props.disabled || undefined\"\n :class=\"[\n {\n 'stash-select--disabled': !!props.disabled,\n 'stash-select--error': !!(props.errorText || props.error),\n 'stash-select--active': isActive,\n 'stash-select--single': props.single,\n },\n\n // @deprecated\n // backwards compatibility\n {\n 'is-active': isActive,\n 'is-single': props.single,\n 'is-disabled': props.disabled,\n },\n ]\"\n @keydown=\"onKeyDown\"\n @keyup.esc=\"dismiss\"\n >\n <!-- CONTENT -->\n <div\n :id=\"'listbox-' + fieldId\"\n ref=\"contentRef\"\n class=\"input-field stash-select__content\"\n tabindex=\"0\"\n data-test=\"stash-select|dropdown-trigger\"\n @focusin=\"open\"\n @focusin.once=\"shouldUseLazyOptions = true\"\n @focusout=\"handleFocusOut\"\n @keydown=\"open\"\n @mouseleave=\"handleMouseLeave\"\n >\n <!-- CHIPS -->\n <ul ref=\"chipsRef\" class=\"stash-select__chips\">\n <!-- PLACEHOLDER -->\n <li\n v-if=\"!internalValue.length\"\n class=\"stash-select__placeholder mr-0 pl-1.5\"\n :class=\"{ truncate: !props.noTruncate }\"\n >\n {{ props.placeholder }}\n </li>\n\n <template v-if=\"props.single\">\n <li\n v-for=\"option of internalValue\"\n :key=\"`chip-${option[props.trackBy]}`\"\n class=\"stash-select__selected mr-0 pl-1.5\"\n :class=\"{ truncate: !props.noTruncate }\"\n >\n <slot\n name=\"selected\"\n :option=\"option\"\n :on-remove=\"() => remove(option)\"\n chip-selected-class=\"stash-select__chip\"\n >\n {{ option[props.displayBy] || option }}\n <button\n tabindex=\"-1\"\n class=\"stash-select__remove\"\n @keypress.enter.prevent=\"remove(option)\"\n @mousedown.prevent.stop=\"remove(option)\"\n >\n <Icon icon=\"close\" name=\"close\" size=\"small\" />\n </button>\n </slot>\n </li>\n </template>\n\n <template v-else>\n <li v-for=\"option of internalValue\" :key=\"`chip-${option[props.trackBy]}`\" class=\"inline-block\">\n <slot\n name=\"selected\"\n :option=\"option\"\n :on-remove=\"() => remove(option)\"\n chip-selected-class=\"stash-select__chip\"\n >\n <Chip\n bg-color=\"blue-500\"\n is-removable\n text-color=\"white\"\n class=\"stash-select__chip\"\n @remove=\"remove(option)\"\n >\n {{ option[props.displayBy] || option }}\n </Chip>\n </slot>\n </li>\n </template>\n </ul>\n\n <!-- TOTAL -->\n <div v-if=\"shouldShowTotal\" class=\"stash-select__total\">\n <Chip bg-color=\"blue-500\" is-removable text-color=\"white\" class=\"stash-select__chip\" @remove=\"clear\">\n {{ clearSelectionText }}\n </Chip>\n </div>\n\n <!-- TOGGLE -->\n <Icon\n v-if=\"props.icon\"\n class=\"stash-select__icon\"\n data-test=\"stash-select|toggle-icon\"\n :name=\"props.icon as IconName\"\n :class=\"{ 'text-ice-500': props.disabled }\"\n @mousedown.prevent=\"isActive && dismiss()\"\n />\n <!-- DRAWER -->\n <Teleport :to=\"props.teleportTo\" :disabled=\"!props.enableTeleport\">\n <transition name=\"fade\">\n <div\n v-show=\"isActive && !props.disabled\"\n ref=\"optionsWrapperRef\"\n class=\"stash-select__border-selector w-full shadow-2xl\"\n :style=\"floatingStyles\"\n @click.stop\n >\n <!-- SEARCH -->\n <div v-if=\"isSearchable\" class=\"flex items-center border-b border-blue-500 pr-1.5\">\n <input\n ref=\"searchRef\"\n v-model=\"searchTerm\"\n type=\"text\"\n autocomplete=\"off\"\n class=\"stash-select__search\"\n :data-test=\"attrs['data-test'] ? attrs['data-test'] + '-search' : 'stash-select|search'\"\n :placeholder=\"props.searchPlaceholder\"\n :spellcheck=\"false\"\n @input=\"handleSearchInput\"\n />\n <Icon name=\"search\" class=\"text-ice-500\" />\n </div>\n\n <!-- OPTIONS -->\n <ul class=\"stash-select__options options my-1.5 w-full border-white bg-white\">\n <li\n v-for=\"(option, index) in filteredOptions\"\n :key=\"`option-${option[props.trackBy]}`\"\n :data-test=\"option[props.trackBy]\"\n :class=\"optionClass(option, index)\"\n @click=\"handleSelect(option)\"\n @mouseenter.self=\"setIndex(index)\"\n >\n <slot name=\"option\" :option=\"option\">\n {{ option[props.displayBy] || option }}\n </slot>\n\n <span v-if=\"isSelected(option) && !props.hideCheck\" class=\"ml-auto pl-1.5\">\n <Icon class=\"text-blue-500\" name=\"check\" />\n </span>\n </li>\n <li v-show=\"props.loading || isSearching\" class=\"m-1.5 cursor-default p-1.5\">\n <Icon data-test=\"stash-select|options-loading\" name=\"working\" class=\"animate-spin text-ice-500\" />\n </li>\n <li\n v-show=\"!props.loading && !isSearching && !filteredOptions.length\"\n class=\"m-1.5 cursor-default p-1.5\"\n data-test=\"stash-select|no-options\"\n >\n <slot name=\"no-options\"> No options </slot>\n </li>\n </ul>\n </div>\n </transition>\n </Teleport>\n </div>\n </div>\n </template>\n <template v-if=\"slots.hint?.()\" #hint>\n <slot name=\"hint\"></slot>\n </template>\n </Field>\n</template>\n\n<style scoped>\n @reference \"../../../styles/main.css\";\n\n @layer utilities {\n .stash-select {\n position: relative;\n }\n\n .stash-select__content,\n .stash-select__content-wrapper {\n height: var(--height-input);\n }\n\n .stash-select__content {\n cursor: pointer;\n overflow: hidden;\n position: absolute;\n background: var(--color-white);\n border: 1px solid var(--color-ice-500);\n border-radius: var(--radius-sm);\n color: var(--color-ice-700);\n display: block;\n font-family: var(--font-family-sofia);\n font-size: var(--text-sm);\n font-weight: normal;\n outline: none;\n padding: 0 !important;\n width: 100%;\n\n &:hover {\n border-color: var(--color-ice-500);\n }\n\n &:focus-within {\n border-color: var(--color-blue-500) !important;\n }\n }\n\n .stash-select--disabled .stash-select__content {\n background-color: var(--color-ice-100);\n border-color: var(--color-ice-500);\n color: var(--color-ice-500);\n pointer-events: none !important;\n }\n\n .stash-select--active .stash-select__content {\n cursor: default;\n height: auto;\n min-width: var(--width-select-menu);\n overflow: visible;\n z-index: var(--z-index-control);\n box-shadow: var(--shadow);\n }\n\n .stash-select--error :where(.stash-select__content, .stash-select__content:hover:not(:focus)) {\n border-color: var(--color-red-500) !important;\n }\n\n .stash-select__placeholder {\n color: var(--color-ice-500);\n }\n\n .stash-select--active .stash-select__placeholder {\n cursor: default;\n }\n\n .stash-select__icon {\n color: var(--color-ice-700);\n cursor: pointer;\n pointer-events: none;\n position: absolute;\n right: --spacing(1.5);\n top: --spacing(1.5);\n }\n\n .stash-select--disabled .stash-select__icon {\n color: var(--color-ice-500);\n }\n\n .stash-select--active .stash-select__icon {\n pointer-events: auto;\n }\n\n .stash-select__chips {\n /* distribute (or split?) the margin between chip and chips container. TODO? */\n --half-space: calc(--spacing(1.5) / 2);\n\n padding: var(--half-space);\n padding-right: --spacing(9);\n }\n\n /* chip AND placeholder AND selected: */\n .stash-select__chips > li {\n cursor: pointer;\n height: var(--height-chip);\n margin: var(--half-space);\n max-width: 100%;\n }\n\n .stash-select--disabled .stash-select__chips > li {\n cursor: default;\n }\n\n .stash-select__content-wrapper:deep(.stash-select__chip) {\n border-radius: var(--radius-sm);\n font-size: var(--text-sm);\n font-weight: normal;\n height: var(--height-chip);\n max-width: inherit;\n text-transform: none;\n }\n\n .stash-select--disabled:deep(.stash-select__chip) {\n background: var(--color-ice-500);\n color: var(--color-white);\n }\n\n /* Base total chip styles moved above the more specific descendant rule to satisfy no-descending-specificity */\n .stash-select__total {\n display: block;\n font-weight: var(--font-weight-semibold);\n line-height: var(--line-height-button);\n position: absolute;\n right: --spacing(6);\n white-space: nowrap;\n z-index: var(--z-index-content);\n\n /* -- design TBD --- */\n top: 0;\n left: 0;\n bottom: 0;\n color: var(--color-white);\n background: white;\n padding: --spacing(1.5);\n }\n\n .stash-select__content-wrapper .stash-select__total:deep(.stash-select__chip) {\n font-weight: var(--font-weight-normal);\n }\n\n .stash-select__selected {\n color: var(--color-ice-700);\n }\n\n .stash-select--disabled .stash-select__selected {\n color: var(--color-ice-500);\n }\n\n .stash-select__selected :where(.stash-select__remove) {\n display: none;\n }\n\n .stash-select__search {\n position: relative;\n width: 100%;\n height: var(--height-input);\n background: var(--color-white);\n border-radius: var(--radius-sm);\n color: var(--color-ice-700);\n display: block;\n font-family: var(--font-family-sofia);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-normal);\n outline: none;\n padding: 0 --spacing(3);\n border: 0 !important;\n }\n\n .stash-select__border-selector {\n background: white;\n border-radius: var(--radius-sm);\n border: 1px solid var(--color-blue-500);\n max-height: var(--max-height-select-menu);\n overflow: auto;\n z-index: var(--z-index-dialog);\n }\n\n .stash-select__option {\n align-items: center;\n border-radius: var(--radius-sm);\n cursor: pointer;\n display: flex;\n line-height: --spacing(6);\n margin: --spacing(1.5);\n padding: --spacing(1.5);\n }\n\n .stash-select__option--disabled {\n color: var(--color-ice-500);\n cursor: not-allowed;\n }\n\n .stash-select__option--highlighted {\n background-color: var(--color-ice-200);\n }\n\n .stash-select__option--selected {\n background-color: var(--color-blue-100);\n color: var(--color-ice-700);\n }\n }\n</style>\n"],"names":["props","__props","emit","__emit","attrs","useAttrs","slots","_useSlots","selectRef","ref","contentRef","optionsWrapperRef","chipsRef","searchRef","chipsHeight","contentHeight","internalValue","searchFor","searchTerm","activeIndex","isActive","pendingSearchRequests","shouldUseLazyOptions","floatingStyles","updateFloating","useFloating","autoUpdate","flip","offset","rects","placement","size","availableHeight","elements","resizeObserverChips","debouncedUpdateChipsHeight","isSearchable","computed","internalOptions","OPTIONS","isPlainObject","name","id","readOnlyValue","option","isSearching","filteredOptions","isChipsOneLine","shouldShowTotal","clearSelectionText","shouldPreventEmpty","watch","currentValue","oldValue","setIndex","value","newValue","isEqual","isEmpty","searchForFn","useSearch","debouncedUpdateFloating","debounce","updateChipsHeight","isOptionDisabled","clear","handleSelect","isSelected","remove","dismiss","handleSelectChange","evt","selectedOption","opt","_a","selectedOptions","internalOption","index","logger","open","nextTick","scrollItems","KEY_CODES","el","elBCR","parentBCR","onKeyDown","handleSearchInput","search","DEBOUNCE","requestId","uniqueId","optionClass","isElementWithinSelectMenu","element","isElementInMenu","isElementInTeleportedMenu","_b","handleFocusOut","focusEvent","nextFocusedElement","handleOutsideClick","event","target","handleMouseLeave","onMounted","onUnmounted"],"mappings":"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"}
1
+ {"version":3,"file":"Select.js","sources":["../src/components/Select/Select.vue"],"sourcesContent":["<script lang=\"ts\">\n import { FieldProps } from '../Field/Field.types';\n\n // Note: the `trackBy` prop, the `single` prop, and the fact that options can be strings prevents us from knowing the type of the selected options\n\n type Option = any;\n\n type SelectedOptions = Option | Option[];\n\n export interface SelectProps extends FieldProps {\n /**\n * If there is only 1 option selected, it prevents that option from being de-selected\n */\n preventEmpty?: boolean;\n\n /**\n * @deprecated The `allowEmpty` prop is no longer supported; use `preventEmpty` instead.\n */\n allowEmpty?: string | boolean | null;\n\n /**\n * If `options` are an object, this is what prop to use for display.\n */\n displayBy?: string;\n\n /**\n * List of one or more fields to search on. When empty, displayBy will be used\n */\n searchBy?: string[];\n\n /**\n * Default field to track selected options by.\n */\n trackBy?: string;\n\n /**\n * Placeholder text.\n */\n placeholder?: string;\n\n /**\n * @deprecated Use the `error-text` prop instead\n */\n error?: string;\n\n /**\n * @deprecated Use the `hint-text` prop instead\n */\n hint?: string;\n\n /**\n * Sets a custom icon.\n */\n icon?: string | boolean;\n\n /**\n * Sets a name attribute on the native (hidden) select element\n *\n * Setting the default value to `undefined` so the attribute isn't rendered if not explicitly provided to ensure backwards compatibility with MP django forms\n */\n name?: string;\n\n /**\n * The list of all options to select from.\n */\n options?: Option[] | readonly Option[];\n\n /**\n * Lazily evaluate and set component `options`. Will execute upon user mouseover.\n */\n lazy?: () => Option[];\n\n /**\n * Sets the currently-selected value(s) for the component.\n * Accepts an array of Objects, or a single Object (if `single` is true), or a string\n */\n modelValue?: SelectedOptions;\n\n /**\n * @deprecated Use :model-value or v-model instead of :value.\n */\n value?: SelectedOptions | null;\n\n /**\n * Hides the search input\n */\n hideSearch?: boolean;\n\n /**\n * @deprecated The `searchable` prop is no longer supported; Use `hideSearch` instead.\n */\n searchable?: string | boolean | null;\n\n /**\n * If true, prevents the search term from being cleared when the drawer is dismissed.\n */\n preserveSearchTerm?: boolean;\n\n /**\n * Prevents the options from being filtered when the search term changes.\n * Allows the parent to filter the options.\n */\n disableFiltering?: boolean;\n\n /**\n * Functions as a single select, if true\n */\n single?: boolean;\n\n /**\n * Prevents the Selected Option from being truncated, if true\n */\n noTruncate?: boolean;\n\n /**\n * On an ajaxed request when the dropdown is open, we may be experiencing slower load times.\n * This flag will drop a loader into the dropdown. Note: This has never been design reviewed.\n */\n loading?: boolean;\n\n /**\n * @deprecated Instead, use the `@search` event (the onSearch prop) and ensure its event handler returns a Promise that resolves after the search is complete.\n */\n searchLoading?: boolean;\n\n /**\n * In the selection text we use this to give more visual distinction to what type of item(s) are selected\n * E.g. 2 customers selected. An empty string will result in `2 selected`.\n */\n selectItemType?: string;\n\n /**\n * Hides the \"check\" icon if truthy\n */\n hideCheck?: boolean;\n\n searchPlaceholder?: string;\n\n /**\n * Equivalent to emitting a \"search\" event, except this prop can be used to populate the list of `options` using an HTTP request because a prop's return value can be received and awaited, unlike an event.\n *\n * **Tip:** to show a loading indicator while searching, return a Promise that resolves after the search is complete.\n *\n * **Warning:** the search input is debounced so there is no need to debounce this function.\n */\n onSearch?: (searchTerm: string) => Promise<void> | void;\n\n /**\n * Uses the \"fuzzy search\" algorithm when searching, if true\n */\n useFuzzySearch?: boolean;\n\n /**\n * Sets the placement of the dropdown\n * @default 'bottom-start'\n */\n menuPlacement?: Placement;\n\n /**\n * Enables teleporting the dropdown\n * @default false\n */\n enableTeleport?: boolean;\n\n /**\n * The selector or element to which the dropdown should be teleported\n * @default `'#stash-menus-mount-node'`\n */\n teleportTo?: string | HTMLElement;\n }\n</script>\n\n<script setup lang=\"ts\">\n import { autoUpdate, flip, offset, type Placement, type Side, size, useFloating } from '@floating-ui/vue';\n import logger from '@leaflink/snitch';\n import debounce from 'lodash-es/debounce';\n import isEmpty from 'lodash-es/isEmpty';\n import isEqual from 'lodash-es/isEqual';\n import isPlainObject from 'lodash-es/isPlainObject';\n import uniqueId from 'lodash-es/uniqueId';\n import { computed, nextTick, onMounted, onUnmounted, type Ref, ref, useAttrs, watch } from 'vue';\n\n import useSearch from '../../composables/useSearch/useSearch';\n import { DEBOUNCE, KEY_CODES } from '../../constants';\n import vClickoutside from '../../directives/clickoutside/clickoutside';\n import { DEFAULT_MENUS_PLUGIN_NODE_ID } from '../../plugins/MenusPlugin';\n import Chip from '../Chip/Chip.vue';\n import Field from '../Field/Field.vue';\n import Icon, { IconName } from '../Icon/Icon.vue';\n\n const GAP_HEIGHT = 6;\n\n const MENU_MAX_HEIGHT_PX = 300;\n\n defineOptions({\n name: 'll-select',\n });\n\n const props = withDefaults(defineProps<SelectProps>(), {\n preventEmpty: false,\n allowEmpty: null,\n displayBy: 'name',\n searchBy: () => [],\n trackBy: 'id',\n placeholder: 'Select option',\n error: '',\n hint: '',\n icon: 'caret-down',\n name: undefined,\n options: () => [],\n lazy: undefined,\n modelValue: () => [],\n value: null,\n hideSearch: false,\n searchable: null,\n preserveSearchTerm: false,\n disableFiltering: false,\n single: false,\n noTruncate: false,\n loading: false,\n searchLoading: false,\n selectItemType: '',\n hideCheck: false,\n searchPlaceholder: 'Search',\n onSearch: undefined,\n useFuzzySearch: false,\n menuPlacement: 'bottom-start',\n enableTeleport: false,\n teleportTo: `#${DEFAULT_MENUS_PLUGIN_NODE_ID}`,\n });\n\n const emit = defineEmits<{\n /**\n * Emitted when the model value changes.\n */\n (e: 'update:model-value', selectedOptions: SelectedOptions): void;\n /**\n * Emitted the selected value(s) a cleared.\n */\n (e: 'clear'): void;\n /**\n * Emitted when an option is added.\n */\n (e: 'add', optionAdded: Option): void;\n /**\n * Emitted when an option is removed.\n */\n (e: 'remove', optionRemoved: Option, index: number): void;\n /**\n * Emitted when the select is opened.\n */\n (e: 'opened'): void;\n /**\n * Emitted when the select is closed.\n */\n (e: 'closed', selectedOptions: SelectedOptions): void;\n }>();\n\n const attrs = useAttrs();\n const slots = defineSlots<{\n /**\n * Selected value(s) custom text. Exposes the option object and remove function.\n */\n selected(props: {\n /**\n * The selected option object.\n */\n option: Option;\n /**\n * Function to remove the selected option from the selection.\n */\n onRemove: () => void;\n /**\n * Class to apply to the selected chip.\n */\n chipSelectedClass: string;\n }): unknown;\n /**\n * Select custom option text. Exposes the option object\n */\n option(props: {\n /**\n * Option The option instance for this list item.\n */\n option: Option;\n }): unknown;\n /**\n * Slot for when there are no options available.\n */\n 'no-options'(): unknown;\n /**\n * Slot to display a helpful hint message below the select field.\n */\n hint(): unknown;\n }>();\n\n const selectRef = ref<HTMLDivElement | null>(null);\n const contentRef = ref<HTMLDivElement | null>(null);\n const optionsWrapperRef = ref<HTMLDivElement | null>(null);\n const chipsRef = ref<HTMLUListElement | null>(null);\n const searchRef = ref<HTMLInputElement | null>(null);\n\n const chipsHeight = ref(0);\n const contentHeight = ref(0);\n const internalValue = ref<Option[]>([]) as Ref<Option[]>;\n const searchFor = ref<(searchTerm: string) => Option[]>(() => []); // initialized in the watch handler for options\n const searchTerm = ref('');\n const activeIndex = ref(-1);\n const isActive = ref(false);\n const pendingSearchRequests = ref({});\n const shouldUseLazyOptions = ref(false);\n\n const { floatingStyles, update: updateFloating } = useFloating(selectRef, optionsWrapperRef, {\n whileElementsMounted: autoUpdate,\n placement: props.menuPlacement,\n middleware: [\n flip(),\n offset(({ rects, placement }) => {\n // gets the current placement side, especially after flip() is run\n const placementSide = placement.split('-')[0] as Side;\n const isBottom = placementSide === ('bottom' satisfies Side);\n\n // if the placement is at the bottom, we need to adjust the offset\n if (isBottom && chipsHeight.value > contentHeight.value) {\n return chipsHeight.value - rects.reference.height + GAP_HEIGHT;\n } else {\n return GAP_HEIGHT;\n }\n }),\n size({\n apply({ availableHeight, elements, rects }) {\n if (props.enableTeleport) {\n Object.assign(elements.floating.style, {\n maxWidth: `${rects.reference.width}px`,\n maxHeight: `${Math.min(availableHeight, MENU_MAX_HEIGHT_PX)}px`,\n });\n }\n },\n }),\n ],\n });\n const resizeObserverChips = new ResizeObserver(() => debouncedUpdateChipsHeight());\n\n const isSearchable = computed(() => !props.hideSearch && props.searchable !== 'false' && props.searchable !== false);\n\n const internalOptions = computed(() => {\n let OPTIONS = (shouldUseLazyOptions.value && props.lazy ? props.lazy() : props.options).filter(Boolean); // prevent [undefined]\n\n // if `options` is an array... but not an array of objects\n if (!isPlainObject(OPTIONS[0])) {\n OPTIONS = OPTIONS.map((name, id) => ({ name, id }) as unknown as Option);\n }\n\n return OPTIONS;\n });\n\n const readOnlyValue = computed(() => {\n if (!internalValue.value.length) {\n return '';\n }\n\n return internalValue.value.map((option) => option[props.displayBy] || option).join(', ');\n });\n\n const isSearching = computed(() => props.searchLoading || Object.keys(pendingSearchRequests.value).length > 0);\n\n const filteredOptions = computed(() => {\n if (props.disableFiltering || !(searchTerm.value || '').trim()) {\n return internalOptions.value;\n }\n\n return searchFor.value(searchTerm.value);\n });\n\n const isChipsOneLine = computed(() => chipsHeight.value <= contentHeight.value);\n\n const shouldShowTotal = computed(\n () => !isActive.value && !props.single && !isChipsOneLine.value && internalValue.value.length,\n );\n\n const clearSelectionText = computed(() => {\n return [internalValue.value.length, props.selectItemType, 'selected'].filter(Boolean).join(' ');\n });\n\n const shouldPreventEmpty = computed(() => {\n return props.preventEmpty || props.allowEmpty === 'false' || props.allowEmpty === false;\n });\n\n watch(searchTerm, (currentValue, oldValue) => oldValue !== currentValue && setIndex(-1));\n\n watch(internalValue, () => debouncedUpdateChipsHeight(), { deep: true });\n\n watch(\n () => props.modelValue,\n (value) => {\n // multiple\n if (Array.isArray(value)) {\n const newValue = value.filter(Boolean); // sometimes modelValue is [undefined]\n\n if (!isEqual(newValue, internalValue.value)) {\n internalValue.value = newValue;\n }\n\n return;\n }\n\n // single; if value is a non-empty string or a non-empty object\n if (!isEmpty(value)) {\n if (!isEqual(value, internalValue.value[0])) {\n internalValue.value[0] = value;\n }\n\n return;\n }\n\n // if value is an empty object or falsy\n internalValue.value = [];\n },\n { immediate: true },\n );\n\n watch(\n () => props.options,\n () => {\n const { searchFor: searchForFn } = useSearch<Option>({\n items: computed(() => internalOptions.value),\n fieldNames: props.searchBy.length ? props.searchBy : [props.displayBy],\n trackBy: props.trackBy,\n });\n\n searchFor.value = (searchTerm) => searchForFn(searchTerm, { fuzzy: props.useFuzzySearch });\n },\n { immediate: true },\n );\n\n watch(chipsHeight, () => debouncedUpdateFloating());\n\n const debouncedUpdateFloating = debounce(() => updateFloating(), 50);\n const debouncedUpdateChipsHeight = debounce(() => updateChipsHeight(), 50);\n\n const isOptionDisabled = (option: Option) => {\n return option && typeof option === 'object' && 'disabled' in option && option?.disabled;\n };\n\n const clear = () => {\n internalValue.value = [];\n emit('update:model-value', props.single ? undefined : internalValue.value);\n emit('clear');\n };\n\n /**\n * Adds (or selects) an option.\n * @param {Option} option The selected option\n */\n const handleSelect = (option?: Option) => {\n if (props.disabled || !option || isOptionDisabled(option)) {\n return;\n }\n\n if (!isSelected(option)) {\n if (props.single) {\n internalValue.value = [];\n }\n\n internalValue.value.push(option);\n emit('update:model-value', props.single ? internalValue.value[0] : internalValue.value);\n emit('add', option);\n } else {\n remove(option);\n }\n\n if (props.single) {\n dismiss();\n }\n };\n\n const handleSelectChange = (evt) => {\n // If this is a single select, just pass along the option\n // This short-circuit is purely for readability, as the iterations for multi would also work for single selects\n if (props.single) {\n const selectedOption = internalOptions.value.find((opt) => opt[props.trackBy]?.toString() === evt.target.value);\n handleSelect(selectedOption);\n return;\n }\n\n // Spread the HTMLCollection (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection) to array so we can invoke `find`\n const selectedOptions = [...evt.target.selectedOptions];\n\n // If it's a multiselect, the HTML select used for the screen reader can have multiple items\n // deselected and a new one selected in one change event.\n // We need to reconcile the new selected options with our internal state\n\n // 1. remove all the items in our internal state that are no longer present\n // (Spread this array so we can remove items from it while iterating)\n for (const internalOption of [...internalValue.value]) {\n const selectedOption = selectedOptions.find((opt) => opt.value === internalOption[props.trackBy]?.toString());\n\n if (!selectedOption) {\n remove(internalOption);\n }\n }\n\n // 2. add all the items that are now selected if needed\n for (const selectedOption of selectedOptions) {\n const internalOption = internalOptions.value.find(\n (opt) => opt[props.trackBy]?.toString() === selectedOption.value,\n );\n\n if (isSelected(internalOption)) {\n continue;\n } else {\n handleSelect(internalOption);\n }\n }\n };\n\n /**\n * Removes (or un-selects) an option.\n * @param {Option} option The option to be removed\n */\n const remove = (option: Option) => {\n if (props.disabled || (shouldPreventEmpty.value && internalValue.value.length === 1)) {\n return;\n }\n\n const index = internalValue.value.findIndex((opt) => opt[props.trackBy] === option[props.trackBy]);\n\n if (index === -1) {\n logger.warn('ll-select: could not find option to remove', option);\n return;\n }\n\n internalValue.value.splice(index, 1);\n emit('update:model-value', props.single ? internalValue.value[0] : internalValue.value);\n emit('remove', option, index);\n };\n\n /**\n * Opens the `options` drawer and provides focus.\n */\n const open = () => {\n if (isActive.value) {\n return;\n }\n\n debouncedUpdateChipsHeight();\n\n isActive.value = true;\n emit('opened');\n\n if (isSearchable.value) {\n nextTick(() => {\n searchRef.value?.focus({ preventScroll: true });\n });\n }\n };\n\n /**\n * Closes the drawer and cleans up a few settings.\n */\n const dismiss = () => {\n if (isActive.value) {\n emit('closed', internalValue.value);\n }\n\n setIndex(-1);\n isActive.value = false;\n\n if (!props.preserveSearchTerm) {\n searchTerm.value = '';\n }\n\n // The optional chaining here is required.\n // There are cases when the component is removed from the DOM and the ref is lost.\n // .ie when the component is used in a modal and the modal is dismissed.\n // Tracked in this ticket: F-1893\n contentRef.value?.blur(); // if the component remains in focus, clicking it again will not open the options list\n };\n\n /**\n * Scroll list items when up and down keys are pressed\n * @param {KeyboardEvent} e The native keydown event.\n */\n const scrollItems = async (e: KeyboardEvent) => {\n await nextTick();\n\n if (!contentRef.value) {\n return;\n }\n\n const allowedKeyCodes = [KEY_CODES.ENTER, KEY_CODES.ESCAPE] as number[];\n\n // Wait for highlighted item update after using keyboard (`onKeyDown`)\n if (!allowedKeyCodes.includes(e.keyCode)) {\n const el = contentRef.value.querySelector('.stash-select__option--highlighted');\n const elBCR = el?.getBoundingClientRect();\n const parentBCR = contentRef.value.getBoundingClientRect();\n\n if (!elBCR || !parentBCR) {\n return;\n }\n\n if (elBCR.bottom >= parentBCR.bottom || elBCR.top <= parentBCR.top + elBCR.height) {\n el?.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n }\n };\n\n /**\n * Sets the `activeIndex` on hover, so that the user may then fine-tune\n * their selection with the arrow keys.\n */\n const setIndex = (index: number) => {\n activeIndex.value = index;\n };\n\n /**\n * Handles all user keyboard input on the Dropdown.\n * @param {KeyboardEvent} e The native keydown event.\n */\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.keyCode === KEY_CODES.ESCAPE) {\n dismiss();\n } else if (e.keyCode === KEY_CODES.DOWN && activeIndex.value < filteredOptions.value.length - 1 && isActive.value) {\n activeIndex.value++;\n } else if (e.keyCode === KEY_CODES.UP && activeIndex.value > 0) {\n if (isActive.value) {\n activeIndex.value--;\n }\n\n // Scroll to top when search is available and first element is highlighted (Using 100 to be sure that it'll be sure that search is visible)\n if (optionsWrapperRef.value && isSearchable.value && activeIndex.value === 0) {\n optionsWrapperRef.value.scrollTop -= 100;\n }\n } else if (e.keyCode === KEY_CODES.ENTER && activeIndex.value !== -1 && isActive.value) {\n handleSelect(filteredOptions.value[activeIndex.value]);\n } else {\n return;\n }\n\n e.preventDefault(); // if keyDown did something / we didn't return\n scrollItems(e);\n };\n\n const handleSearchInput = debounce(function () {\n search();\n }, DEBOUNCE.FAST);\n\n const search = async function () {\n if (!props.onSearch) {\n return;\n }\n\n const requestId = uniqueId('search-request-');\n\n try {\n pendingSearchRequests.value[requestId] = true;\n await props.onSearch(searchTerm.value);\n } finally {\n delete pendingSearchRequests.value[requestId];\n }\n };\n\n /**\n * Dynamically sets classes on an `option` in the drawer.\n * @param {Option} option The `option` for which to generate classes\n * @param {number} index The index of the `option` in the list of `options`\n */\n const optionClass = (option: Option, index: number) => {\n return [\n {\n 'stash-select__option': true,\n 'stash-select__option--disabled': isOptionDisabled(option),\n 'stash-select__option--highlighted': activeIndex.value === index,\n 'stash-select__option--selected': isSelected(option),\n },\n\n // @deprecated\n // backwards compatibility\n {\n 'is-selected': activeIndex.value === index,\n 'bg-ice-200': activeIndex.value === index,\n 'bg-blue-100 text-ice-700': isSelected(option),\n },\n ];\n };\n\n /**\n * Finds out if the given item is already present in the selected options\n * @param {Option} option The item to check\n * @returns {boolean} Returns true if element is selected\n */\n const isSelected = (option?: Option | null): boolean => {\n if (option === undefined || option === null) {\n return false;\n }\n\n return internalValue.value.some((value) => value?.[props.trackBy] === option[props.trackBy]);\n };\n\n /**\n * Checks if the element is within the Select menu by checking both the content and the teleportTo element\n */\n function isElementWithinSelectMenu(element: Node | null) {\n if (!element) {\n return false;\n }\n\n const isElementInMenu = contentRef.value?.contains(element);\n const isElementInTeleportedMenu = optionsWrapperRef.value?.contains(element);\n\n return isElementInMenu || isElementInTeleportedMenu;\n }\n\n function handleFocusOut(focusEvent: FocusEvent) {\n const nextFocusedElement = focusEvent.relatedTarget as Node | null;\n const isFocusedElementInMenu = isElementWithinSelectMenu(nextFocusedElement);\n\n if (!isFocusedElementInMenu && nextFocusedElement) {\n dismiss();\n }\n }\n\n function handleOutsideClick(event: MouseEvent) {\n const target = event.target as Node | null;\n const isFocusedElementInMenu = isElementWithinSelectMenu(target);\n\n if (!isFocusedElementInMenu) {\n dismiss();\n }\n }\n\n /**\n * Handles the mouse leave of the select and clears item highlight\n */\n const handleMouseLeave = () => {\n setIndex(-1);\n };\n\n /**\n * Update height of chips container\n */\n const updateChipsHeight = async () => {\n // Wait for the DOM to update so we can get an accurate value for chips' clientHeight\n await nextTick();\n\n if (chipsRef.value && contentRef.value) {\n contentHeight.value = contentHeight.value === 0 ? contentRef.value.clientHeight : contentHeight.value;\n chipsHeight.value = chipsRef.value.clientHeight;\n }\n };\n\n onMounted(() => {\n if (attrs.onInput) {\n throw new Error('ll-select: use the @update:model-value event instead of @input.');\n }\n\n if (chipsRef.value) {\n resizeObserverChips.observe(chipsRef.value);\n }\n\n window.addEventListener('resize', debouncedUpdateChipsHeight);\n\n debouncedUpdateChipsHeight();\n });\n\n /**\n * Remove event listener to handle component width when resizing window\n */\n onUnmounted(() => {\n window.removeEventListener('resize', updateChipsHeight);\n resizeObserverChips.disconnect();\n debouncedUpdateChipsHeight.cancel();\n });\n</script>\n\n<template>\n <Field\n v-bind=\"props\"\n class=\"input ll-select stash-select\"\n data-test=\"stash-select\"\n :class=\"attrs.class\"\n :error-text=\"props.errorText || props.error\"\n :hint-text=\"props.hintText || props.hint\"\n >\n <template v-if=\"props.isReadOnly\" #default=\"{ fieldId, labelId }\">\n <div class=\"flex h-input items-center text-sm\">\n <span :id=\"fieldId\" :aria-labelledby=\"labelId\" class=\"show-empty h-min\">\n {{ readOnlyValue }}\n </span>\n </div>\n </template>\n <template v-else #default=\"{ fieldId, fieldErrorId, hasError }\">\n <!-- SCREEN READER ONLY -->\n <select\n :id=\"fieldId\"\n :aria-errormessage=\"fieldErrorId\"\n :aria-invalid=\"hasError\"\n class=\"sr-only\"\n :disabled=\"props.disabled\"\n :multiple=\"!props.single\"\n :name=\"props.name\"\n @change=\"handleSelectChange\"\n >\n <option\n v-for=\"(option, index) in filteredOptions\"\n :key=\"`srOnlyOption-${index}`\"\n :selected=\"isSelected(option)\"\n :value=\"option[props.trackBy]\"\n :disabled=\"isOptionDisabled(option)\"\n >\n {{ option[props.displayBy] || '' }}\n </option>\n\n <!-- Empty option to allow deselecting non-multiselects -->\n <option value=\"\"></option>\n </select>\n\n <!-- SELECT -->\n <div\n ref=\"selectRef\"\n v-clickoutside=\"handleOutsideClick\"\n role=\"listbox\"\n aria-hidden=\"true\"\n class=\"stash-select__content-wrapper\"\n :aria-controls=\"'listbox-' + fieldId\"\n :aria-expanded=\"isActive\"\n :aria-label=\"props.placeholder\"\n :aria-disabled=\"props.disabled || undefined\"\n :class=\"[\n {\n 'stash-select--disabled': !!props.disabled,\n 'stash-select--error': !!(props.errorText || props.error),\n 'stash-select--active': isActive,\n 'stash-select--single': props.single,\n },\n\n // @deprecated\n // backwards compatibility\n {\n 'is-active': isActive,\n 'is-single': props.single,\n 'is-disabled': props.disabled,\n },\n ]\"\n @keydown=\"onKeyDown\"\n @keyup.esc=\"dismiss\"\n >\n <!-- CONTENT -->\n <div\n :id=\"'listbox-' + fieldId\"\n ref=\"contentRef\"\n class=\"input-field stash-select__content\"\n tabindex=\"0\"\n data-test=\"stash-select|dropdown-trigger\"\n @focusin=\"open\"\n @focusin.once=\"shouldUseLazyOptions = true\"\n @focusout=\"handleFocusOut\"\n @keydown=\"open\"\n @mouseleave=\"handleMouseLeave\"\n >\n <!-- CHIPS -->\n <ul ref=\"chipsRef\" class=\"stash-select__chips\">\n <!-- PLACEHOLDER -->\n <li\n v-if=\"!internalValue.length\"\n class=\"stash-select__placeholder mr-0 pl-1.5\"\n :class=\"{ truncate: !props.noTruncate }\"\n >\n {{ props.placeholder }}\n </li>\n\n <template v-if=\"props.single\">\n <li\n v-for=\"option of internalValue\"\n :key=\"`chip-${option[props.trackBy]}`\"\n class=\"stash-select__selected mr-0 pl-1.5\"\n :class=\"{ truncate: !props.noTruncate }\"\n >\n <slot\n name=\"selected\"\n :option=\"option\"\n :on-remove=\"() => remove(option)\"\n chip-selected-class=\"stash-select__chip\"\n >\n {{ option[props.displayBy] || option }}\n <button\n tabindex=\"-1\"\n class=\"stash-select__remove\"\n @keypress.enter.prevent=\"remove(option)\"\n @mousedown.prevent.stop=\"remove(option)\"\n >\n <Icon icon=\"close\" name=\"close\" size=\"small\" />\n </button>\n </slot>\n </li>\n </template>\n\n <template v-else>\n <li v-for=\"option of internalValue\" :key=\"`chip-${option[props.trackBy]}`\" class=\"inline-block\">\n <slot\n name=\"selected\"\n :option=\"option\"\n :on-remove=\"() => remove(option)\"\n chip-selected-class=\"stash-select__chip\"\n >\n <Chip\n bg-color=\"blue-500\"\n is-removable\n text-color=\"white\"\n class=\"stash-select__chip\"\n @remove=\"remove(option)\"\n >\n {{ option[props.displayBy] || option }}\n </Chip>\n </slot>\n </li>\n </template>\n </ul>\n\n <!-- TOTAL -->\n <div v-if=\"shouldShowTotal\" class=\"stash-select__total\">\n <Chip bg-color=\"blue-500\" is-removable text-color=\"white\" class=\"stash-select__chip\" @remove=\"clear\">\n {{ clearSelectionText }}\n </Chip>\n </div>\n\n <!-- TOGGLE -->\n <Icon\n v-if=\"props.icon\"\n class=\"stash-select__icon\"\n data-test=\"stash-select|toggle-icon\"\n :name=\"props.icon as IconName\"\n :class=\"{ 'text-ice-500': props.disabled }\"\n @mousedown.prevent=\"isActive && dismiss()\"\n />\n <!-- DRAWER -->\n <Teleport :to=\"props.teleportTo\" :disabled=\"!props.enableTeleport\">\n <transition name=\"fade\">\n <div\n v-show=\"isActive && !props.disabled\"\n ref=\"optionsWrapperRef\"\n class=\"stash-select__border-selector w-full shadow-2xl\"\n :style=\"floatingStyles\"\n @click.stop\n >\n <!-- SEARCH -->\n <div v-if=\"isSearchable\" class=\"flex items-center border-b border-blue-500 pr-1.5\">\n <input\n ref=\"searchRef\"\n v-model=\"searchTerm\"\n type=\"text\"\n autocomplete=\"off\"\n class=\"stash-select__search\"\n :data-test=\"attrs['data-test'] ? attrs['data-test'] + '-search' : 'stash-select|search'\"\n :placeholder=\"props.searchPlaceholder\"\n :spellcheck=\"false\"\n @input=\"handleSearchInput\"\n />\n <Icon name=\"search\" class=\"text-ice-500\" />\n </div>\n\n <!-- OPTIONS -->\n <ul class=\"stash-select__options options my-1.5 w-full border-white bg-white\">\n <li\n v-for=\"(option, index) in filteredOptions\"\n :key=\"`option-${option[props.trackBy]}`\"\n :data-test=\"option[props.trackBy]\"\n :class=\"optionClass(option, index)\"\n @click=\"handleSelect(option)\"\n @mouseenter.self=\"setIndex(index)\"\n >\n <slot name=\"option\" :option=\"option\">\n {{ option[props.displayBy] || option }}\n </slot>\n\n <span v-if=\"isSelected(option) && !props.hideCheck\" class=\"ml-auto pl-1.5\">\n <Icon class=\"text-blue-500\" name=\"check\" />\n </span>\n </li>\n <li v-show=\"props.loading || isSearching\" class=\"m-1.5 cursor-default p-1.5\">\n <Icon data-test=\"stash-select|options-loading\" name=\"working\" class=\"animate-spin text-ice-500\" />\n </li>\n <li\n v-show=\"!props.loading && !isSearching && !filteredOptions.length\"\n class=\"m-1.5 cursor-default p-1.5\"\n data-test=\"stash-select|no-options\"\n >\n <slot name=\"no-options\"> No options </slot>\n </li>\n </ul>\n </div>\n </transition>\n </Teleport>\n </div>\n </div>\n </template>\n <template v-if=\"slots.hint?.()\" #hint>\n <slot name=\"hint\"></slot>\n </template>\n </Field>\n</template>\n\n<style scoped>\n @reference \"../../../styles/main.css\";\n\n @layer utilities {\n .stash-select {\n position: relative;\n }\n\n .stash-select__content,\n .stash-select__content-wrapper {\n height: var(--height-input);\n }\n\n .stash-select__content {\n cursor: pointer;\n overflow: hidden;\n position: absolute;\n background: var(--color-white);\n border: 1px solid var(--color-ice-500);\n border-radius: var(--radius-sm);\n color: var(--color-ice-700);\n display: block;\n font-family: var(--font-family-sofia);\n font-size: var(--text-sm);\n font-weight: normal;\n outline: none;\n padding: 0 !important;\n width: 100%;\n\n &:hover {\n border-color: var(--color-ice-500);\n }\n\n &:focus-within {\n border-color: var(--color-blue-500) !important;\n }\n }\n\n .stash-select--disabled .stash-select__content {\n background-color: var(--color-ice-100);\n border-color: var(--color-ice-500);\n color: var(--color-ice-500);\n pointer-events: none !important;\n }\n\n .stash-select--active .stash-select__content {\n cursor: default;\n height: auto;\n min-width: var(--width-select-menu);\n overflow: visible;\n z-index: var(--z-index-control);\n box-shadow: var(--shadow);\n }\n\n .stash-select--error :where(.stash-select__content, .stash-select__content:hover:not(:focus)) {\n border-color: var(--color-red-500) !important;\n }\n\n .stash-select__placeholder {\n color: var(--color-ice-500);\n }\n\n .stash-select--active .stash-select__placeholder {\n cursor: default;\n }\n\n .stash-select__icon {\n color: var(--color-ice-700);\n cursor: pointer;\n pointer-events: none;\n position: absolute;\n right: --spacing(1.5);\n top: --spacing(1.5);\n }\n\n .stash-select--disabled .stash-select__icon {\n color: var(--color-ice-500);\n }\n\n .stash-select--active .stash-select__icon {\n pointer-events: auto;\n }\n\n .stash-select__chips {\n /* distribute (or split?) the margin between chip and chips container. TODO? */\n --half-space: calc(--spacing(1.5) / 2);\n\n padding: var(--half-space);\n padding-right: --spacing(9);\n }\n\n /* chip AND placeholder AND selected: */\n .stash-select__chips > li {\n cursor: pointer;\n height: var(--height-chip);\n margin: var(--half-space);\n max-width: 100%;\n }\n\n .stash-select--disabled .stash-select__chips > li {\n cursor: default;\n }\n\n .stash-select__content-wrapper:deep(.stash-select__chip) {\n border-radius: var(--radius-sm);\n font-size: var(--text-sm);\n font-weight: normal;\n height: var(--height-chip);\n max-width: inherit;\n text-transform: none;\n }\n\n .stash-select--disabled:deep(.stash-select__chip) {\n background: var(--color-ice-500);\n color: var(--color-white);\n }\n\n /* Base total chip styles moved above the more specific descendant rule to satisfy no-descending-specificity */\n .stash-select__total {\n display: block;\n font-weight: var(--font-weight-semibold);\n line-height: var(--line-height-button);\n position: absolute;\n right: --spacing(6);\n white-space: nowrap;\n z-index: var(--z-index-content);\n\n /* -- design TBD --- */\n top: 0;\n left: 0;\n bottom: 0;\n color: var(--color-white);\n background: white;\n padding: --spacing(1.5);\n }\n\n .stash-select__content-wrapper .stash-select__total:deep(.stash-select__chip) {\n font-weight: var(--font-weight-normal);\n }\n\n .stash-select__selected {\n color: var(--color-ice-700);\n }\n\n .stash-select--disabled .stash-select__selected {\n color: var(--color-ice-500);\n }\n\n .stash-select__selected :where(.stash-select__remove) {\n display: none;\n }\n\n .stash-select__search {\n position: relative;\n width: 100%;\n height: var(--height-input);\n background: var(--color-white);\n border-radius: var(--radius-sm);\n color: var(--color-ice-700);\n display: block;\n font-family: var(--font-family-sofia);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-normal);\n outline: none;\n padding: 0 --spacing(3);\n border: 0 !important;\n }\n\n .stash-select__border-selector {\n background: white;\n border-radius: var(--radius-sm);\n border: 1px solid var(--color-blue-500);\n max-height: var(--max-height-select-menu);\n overflow: auto;\n z-index: var(--z-index-dialog);\n }\n\n .stash-select__option {\n align-items: center;\n border-radius: var(--radius-sm);\n cursor: pointer;\n display: flex;\n line-height: --spacing(6);\n margin: --spacing(1.5);\n padding: --spacing(1.5);\n }\n\n .stash-select__option--disabled {\n color: var(--color-ice-500);\n cursor: not-allowed;\n }\n\n .stash-select__option--highlighted {\n background-color: var(--color-ice-200);\n }\n\n .stash-select__option--selected {\n background-color: var(--color-blue-100);\n color: var(--color-ice-700);\n }\n }\n</style>\n"],"names":["props","__props","emit","__emit","attrs","useAttrs","slots","_useSlots","selectRef","ref","contentRef","optionsWrapperRef","chipsRef","searchRef","chipsHeight","contentHeight","internalValue","searchFor","searchTerm","activeIndex","isActive","pendingSearchRequests","shouldUseLazyOptions","floatingStyles","updateFloating","useFloating","autoUpdate","flip","offset","rects","placement","size","availableHeight","elements","resizeObserverChips","debouncedUpdateChipsHeight","isSearchable","computed","internalOptions","OPTIONS","isPlainObject","name","id","readOnlyValue","option","isSearching","filteredOptions","isChipsOneLine","shouldShowTotal","clearSelectionText","shouldPreventEmpty","watch","currentValue","oldValue","setIndex","value","newValue","isEqual","isEmpty","searchForFn","useSearch","debouncedUpdateFloating","debounce","updateChipsHeight","isOptionDisabled","clear","handleSelect","isSelected","remove","dismiss","handleSelectChange","evt","selectedOption","opt","_a","selectedOptions","internalOption","index","logger","open","nextTick","scrollItems","KEY_CODES","el","elBCR","parentBCR","onKeyDown","handleSearchInput","search","DEBOUNCE","requestId","uniqueId","optionClass","isElementWithinSelectMenu","element","isElementInMenu","isElementInTeleportedMenu","_b","handleFocusOut","focusEvent","nextFocusedElement","handleOutsideClick","event","target","handleMouseLeave","onMounted","onUnmounted"],"mappings":"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"}