{"mappings":"AC0QoC;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;;;EAAA;;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAcjB;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAyBQ;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EA+BF;;;;EAwDR;;;;EA6BC;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAkGC;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAUgB;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAGb;;;;EAOA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;EAAA;;;;;AAjRc;EA2JlB;;;;EAAA;;;;EAAA;;;;EAAA;;;;;AA3JkB;EAAA;IAsEX;;;;IAqFP;;;;IAAA;;;;IAAA;;;;IAAA;;;;IAkGC;;;;IAAA;;;;;;AAtNQ;EAAA;;;;;AAAA;EAAA;;;;;AAAA;EAAA;;;;;AAAA;EAAA;;;;;AAAA;EAAA;;;;;AAAA;EAAA;;;;;AAoHT;EAAA;IAAA;;;;IAAA;;;;IAAA;;;;IAAA;;;;IAAA","sources":["b7ca70995960d034","packages/@react-spectrum/s2/src/TagGroup.tsx"],"sourcesContent":["@import \"53ec8c3f8184f13f\";\n@import \"39511c3f261b5ed5\";\n@import \"0adf7c6cbf3a1f42\";\n@import \"05f90dba9e9f901e\";\n@import \"76f74241f914b430\";\n@import \"0ef2aba34cf241d8\";\n@import \"f86d9fb495210c39\";\n@import \"24a7cd4eab7f1ba0\";\n@import \"ce1f7d95b3ccf3fd\";\n@import \"70bbf6109092559b\";\n@import \"07833edfdb3bfcff\";\n@import \"ffbc1ae1f89af4e1\";\n@import \"c340d154a58b1eb0\";\n@import \"27e38777e711f6a4\";\n","/*\n * Copyright 2024 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {ActionButton} from './ActionButton';\nimport AlertIcon from '../s2wf-icons/S2_Icon_AlertTriangle_20_N.svg';\nimport {\n  Tag as AriaTag,\n  TagGroup as AriaTagGroup,\n  TagGroupProps as AriaTagGroupProps,\n  TagProps as AriaTagProps,\n  composeRenderProps,\n  ContextValue,\n  Provider,\n  TextContext as RACTextContext,\n  TagList,\n  TagListProps,\n  TagRenderProps,\n  useLocale,\n  useSlottedContext\n} from 'react-aria-components';\nimport {AvatarContext} from './Avatar';\nimport {baseColor, focusRing, fontRelative, lightDark, style} from '../style' with { type: 'macro' };\nimport {CenterBaseline, centerBaseline} from './CenterBaseline';\nimport {ClearButton} from './ClearButton';\nimport {Collection, CollectionBuilder} from '@react-aria/collections';\nimport {control, field, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};\nimport {createContext, forwardRef, ReactNode, useContext, useEffect, useMemo, useRef, useState} from 'react';\nimport {DOMRef, DOMRefValue, HelpTextProps, Node, SpectrumLabelableProps} from '@react-types/shared';\nimport {FieldLabel, helpTextStyles} from './Field';\nimport {flushSync} from 'react-dom';\nimport {FormContext, useFormProps} from './Form';\nimport {forwardRefType} from './types';\nimport {IconContext} from './Icon';\nimport {ImageContext} from './Image';\nimport {inertValue, useEffectEvent, useId, useLayoutEffect, useResizeObserver} from '@react-aria/utils';\n// @ts-ignore\nimport intlMessages from '../intl/*.json';\nimport {pressScale} from './pressScale';\nimport {Text, TextContext} from './Content';\nimport {useDOMRef} from '@react-spectrum/utils';\nimport {useLocalizedStringFormatter} from '@react-aria/i18n';\nimport {useSpectrumContextProps} from './useSpectrumContextProps';\n\n// Get types from RSP and extend those?\nexport interface TagProps extends Omit<AriaTagProps, 'children' | 'style' | 'className'> {\n  /** The children of the tag. */\n  children: ReactNode\n}\n\nexport interface TagGroupProps<T> extends Omit<AriaTagGroupProps, 'children' | 'style' | 'className'>, Pick<TagListProps<T>, 'items' | 'children' | 'renderEmptyState'>, Omit<SpectrumLabelableProps, 'isRequired' | 'necessityIndicator'>, StyleProps, Omit<HelpTextProps, 'errorMessage'> {\n  /** A description for the tag group. */\n  description?: ReactNode,\n  /**\n   * The size of the tag group.\n   *\n   * @default 'M'\n   */\n  size?: 'S' | 'M' | 'L',\n  /** Whether the tags are displayed in an emphasized style. */\n  isEmphasized?: boolean,\n  /** Provides content to display when there are no items in the tag group. */\n  renderEmptyState?: () => ReactNode,\n  /** Whether the tags are displayed in a error state. */\n  isInvalid?: boolean,\n  /** An error message for the field. */\n  errorMessage?: ReactNode,\n  /** Limit the number of rows initially shown. This will render a button that allows the user to expand to show all tags. */\n  maxRows?: number,\n  /** The label to display on the action button.  */\n  groupActionLabel?: string,\n  /** Handler that is called when the action button is pressed. */\n  onGroupAction?: () => void\n}\n\nexport const TagGroupContext = createContext<ContextValue<Partial<TagGroupProps<any>>, DOMRefValue<HTMLDivElement>>>(null);\n\nconst InternalTagGroupContext = createContext<TagGroupProps<any>>({});\n\n/** Tags allow users to categorize content. They can represent keywords or people, and are grouped to describe an item or a search request. */\nexport const TagGroup = /*#__PURE__*/ (forwardRef as forwardRefType)(function TagGroup<T extends object>(props: TagGroupProps<T>, ref: DOMRef<HTMLDivElement>) {\n  [props, ref] = useSpectrumContextProps(props, ref, TagGroupContext);\n  props = useFormProps(props);\n  let {onRemove} = props;\n  return (\n    <InternalTagGroupContext.Provider value={{onRemove}}>\n      <CollectionBuilder content={<Collection {...props} />}>\n        {collection => <TagGroupInner props={props} forwardedRef={ref} collection={collection} />}\n      </CollectionBuilder>\n    </InternalTagGroupContext.Provider>\n  );\n});\n\nfunction TagGroupInner<T>({\n  props: {\n    label,\n    description,\n    labelPosition = 'top',\n    labelAlign = 'start',\n    isEmphasized,\n    isInvalid,\n    errorMessage,\n    UNSAFE_className = '',\n    UNSAFE_style,\n    size = 'M',\n    ...props\n  },\n  forwardedRef: ref,\n  collection\n}: {props: TagGroupProps<T>, forwardedRef: DOMRef<HTMLDivElement>, collection: any}) {\n  let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');\n  let {\n    maxRows,\n    groupActionLabel,\n    onGroupAction,\n    renderEmptyState = () => stringFormatter.format('tag.noTags'),\n    ...otherProps\n  } = props;\n  let {direction} = useLocale();\n  let containerRef = useRef(null);\n  let tagsRef = useRef<HTMLDivElement | null>(null);\n  let actionsRef = useRef<HTMLDivElement | null>(null);\n  let hiddenTagsRef = useRef<HTMLDivElement | null>(null);\n  let [tagState, setTagState] = useState({visibleTagCount: collection.size, showCollapseButton: false});\n  let [isCollapsed, setIsCollapsed] = useState(maxRows != null);\n  let {onRemove} = useContext(InternalTagGroupContext);\n  let isEmpty = collection.size === 0;\n  let showCollapseToggleButton = tagState.showCollapseButton || tagState.visibleTagCount < collection.size;\n  let formContext = useContext(FormContext);\n  let domRef = useDOMRef(ref);\n\n  let allItems = useMemo(\n    () => Array.from(collection) as Array<Node<T>>,\n    [collection]\n  );\n  let items = useMemo(\n    () => Array.from(collection).slice(0, !isCollapsed ? collection.size : tagState.visibleTagCount) as Array<Node<T>>,\n    [collection, tagState.visibleTagCount, isCollapsed]\n  );\n\n  let updateVisibleTagCount = useEffectEvent(() => {\n    if (maxRows == null) {\n      setTagState({visibleTagCount: collection.size, showCollapseButton: false});\n    }\n\n    if (maxRows != null && maxRows > 0) {\n      let computeVisibleTagCount = () => {\n        let currContainerRef: HTMLDivElement | null = hiddenTagsRef.current;\n        let currTagsRef: HTMLDivElement | null = hiddenTagsRef.current;\n        let currActionsRef: HTMLDivElement | null = actionsRef.current;\n        if (!currContainerRef || !currTagsRef || collection.size === 0 || currContainerRef.parentElement == null) {\n          return {\n            visibleTagCount: 0,\n            showCollapseButton: false\n          };\n        }\n\n        // Count rows and show tags until we hit the maxRows.\n        // I think this is still a safe assumption, and we don't need to queryAll for role=tag\n        let tags = [...currTagsRef.children];\n        let currY = -Infinity;\n        let rowCount = 0;\n        let index = 0;\n        let tagWidths: number[] = [];\n        for (let tag of tags) {\n          let {width, y} = tag.getBoundingClientRect();\n\n          if (y !== currY) {\n            currY = y;\n            rowCount++;\n          }\n\n          if (rowCount > maxRows) {\n            break;\n          }\n          tagWidths.push(width);\n          index++;\n        }\n\n        // Remove tags until there is space for the collapse button and action button (if present) on the last row.\n        let buttons = currActionsRef ? [...currActionsRef.children] : [];\n        if (buttons.length > 0 && rowCount >= maxRows) {\n          let buttonsWidth = buttons.reduce((acc, curr) => acc += curr.getBoundingClientRect().width, 0);\n          let margins = parseFloat(getComputedStyle(buttons[0]).marginInlineStart);\n          buttonsWidth += margins * 2;\n          let end = direction === 'ltr' ? 'right' : 'left';\n          let containerEnd = currContainerRef.parentElement?.getBoundingClientRect()[end] - margins;\n          let lastTagEnd = tags[index - 1]?.getBoundingClientRect()[end];\n          lastTagEnd += margins;\n          let availableWidth = containerEnd - lastTagEnd;\n\n          while (availableWidth <= buttonsWidth && index > 0) {\n            let tagWidth = tagWidths.pop();\n            if (tagWidth != null) {\n              availableWidth += tagWidth;\n            }\n            index--;\n          }\n        }\n\n        return {\n          visibleTagCount: Math.max(index, 1),\n          showCollapseButton: index < collection.size\n        };\n      };\n      let result = computeVisibleTagCount();\n      flushSync(() => {\n        setTagState(result);\n      });\n    }\n  });\n\n  useResizeObserver({ref: maxRows != null ? containerRef : undefined, onResize: updateVisibleTagCount});\n\n  useLayoutEffect(() => {\n    if (collection.size > 0 && (maxRows != null && maxRows > 0)) {\n      queueMicrotask(updateVisibleTagCount);\n    }\n  }, [collection.size, updateVisibleTagCount, maxRows]);\n\n  useEffect(() => {\n    // Recalculate visible tags when fonts are loaded.\n    document.fonts?.ready.then(() => updateVisibleTagCount());\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  let handlePressCollapse = () => {\n    setIsCollapsed(prevCollapsed => !prevCollapsed);\n  };\n\n  let helpText: ReactNode = null;\n  if (!isInvalid && description) {\n    helpText =  (\n      <Text\n        slot=\"description\"\n        styles={helpTextStyles({size})}>\n        {description}\n      </Text>\n    );\n  } else if (isInvalid) {\n    helpText = (\n      <div\n        className={helpTextStyles({size, isInvalid})}>\n        <CenterBaseline>\n          <AlertIcon />\n        </CenterBaseline>\n        <Text slot=\"errorMessage\">\n          {errorMessage}\n        </Text>\n      </div>\n    );\n  }\n\n  return (\n    <AriaTagGroup\n      {...otherProps}\n      ref={domRef}\n      style={UNSAFE_style}\n      className={UNSAFE_className + style(field(), getAllowedOverrides())({\n        size,\n        labelPosition: labelPosition,\n        isInForm: !!formContext\n      }, props.styles)}>\n      <FieldLabel\n        size={size}\n        labelPosition={labelPosition}\n        labelAlign={labelAlign}\n        contextualHelp={props.contextualHelp}>\n        {label}\n      </FieldLabel>\n      <div\n        ref={containerRef}\n        className={style({\n          gridArea: 'input',\n          minWidth: 'full',\n          marginStart: {\n            default: -4,\n            isEmpty: 0\n          },\n          marginEnd: {\n            default: 4,\n            isEmpty: 0\n          },\n          position: 'relative'\n        })({isEmpty})}>\n        <FormContext.Provider value={{...formContext, size}}>\n          <Provider\n            values={[\n              [RACTextContext, undefined],\n              [TagGroupContext, {size, isEmphasized}]\n            ]}>\n            {/* invisible collection for measuring */}\n            {maxRows != null && (\n              <div\n                // @ts-ignore\n                inert={inertValue(true)}\n                ref={hiddenTagsRef}\n                className={style({\n                  display: 'inline',\n                  flexWrap: 'wrap',\n                  fontFamily: 'sans',\n                  position: 'absolute',\n                  top: 0,\n                  bottom: 0,\n                  start: -4,\n                  end: 4,\n                  visibility: 'hidden',\n                  overflow: 'hidden',\n                  opacity: 0\n                })}>\n                {allItems.map(item => {\n                  // pull off individual props as an allow list, don't want refs or other props getting through\n                  return (\n                    <div\n                      style={item.props.UNSAFE_style}\n                      key={item.key}\n                      className={item.props.className({size, allowsRemoving: Boolean(onRemove)})}>\n                      {item.props.children({size, allowsRemoving: Boolean(onRemove), isInCtx: true})}\n                    </div>\n                  );\n                })}\n              </div>\n            )}\n            {/* real tag list */}\n            <TagList\n              ref={tagsRef}\n              items={items}\n              renderEmptyState={renderEmptyState}\n              className={style({\n                display: 'inline',\n                minWidth: 'full',\n                font: 'ui'\n              })}>\n              {item => <Tag {...item.props} id={item.key} textValue={item.textValue} />}\n            </TagList>\n            {!isEmpty && (showCollapseToggleButton || groupActionLabel) &&\n              <ActionGroup\n                collection={collection}\n                aria-label={props['aria-label']}\n                aria-labelledby={props['aria-labelledby']}\n                actionsRef={actionsRef}\n                tagState={tagState}\n                size={size}\n                isCollapsed={isCollapsed}\n                handlePressCollapse={handlePressCollapse}\n                onGroupAction={onGroupAction}\n                groupActionLabel={groupActionLabel} />\n            }\n          </Provider>\n        </FormContext.Provider>\n      </div>\n      {helpText}\n    </AriaTagGroup>\n  );\n}\n\nfunction ActionGroup(props) {\n  let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');\n  let {\n    actionsRef,\n    tagState,\n    size,\n    isCollapsed,\n    handlePressCollapse,\n    onGroupAction,\n    groupActionLabel,\n    collection,\n    // directly use aria-labelling from the TagGroup because we can't use the id from the TagList\n    // and we can't supply an id to the TagList because it'll cause an issue where all the tag ids flip back\n    // and forth with their prefix in an infinite loop\n    'aria-label': ariaLabel,\n    'aria-labelledby': ariaLabelledBy\n  } = props;\n\n  let actionsId = useId();\n  // might need to localize the aria-label which concatenates with this label\n  let actionGroupLabel = stringFormatter.format('tag.actions');\n  return (\n    <div\n      role=\"group\"\n      ref={actionsRef}\n      id={actionsId}\n      aria-label={ariaLabel ? `${ariaLabel} ${actionGroupLabel}` : actionGroupLabel}\n      aria-labelledby={ariaLabelledBy ? ariaLabelledBy : undefined}\n      className={style({\n        display: 'inline'\n      })}>\n      {tagState.showCollapseButton &&\n        <ActionButton\n          isQuiet\n          size={size}\n          styles={style({margin: 4})}\n          UNSAFE_style={{display: 'inline-flex'}}\n          onPress={handlePressCollapse}>\n          {isCollapsed ?\n            stringFormatter.format('tag.showAllButtonLabel', {tagCount: collection.size}) :\n            stringFormatter.format('tag.hideButtonLabel')}\n        </ActionButton>\n      }\n      {groupActionLabel && onGroupAction &&\n        <ActionButton\n          isQuiet\n          size={size}\n          styles={style({margin: 4})}\n          UNSAFE_style={{display: 'inline-flex'}}\n          onPress={() => onGroupAction?.()}>\n          {groupActionLabel}\n        </ActionButton>\n      }\n    </div>\n  );\n}\n\nconst tagStyles = style<TagRenderProps & {size?: 'S' | 'M' | 'L', isEmphasized?: boolean, isLink?: boolean, allowsRemoving?: boolean}>({\n  ...focusRing(),\n  ...control({shape: 'default', icon: true}),\n  display: 'inline-flex',\n  maxWidth: 'full',\n  verticalAlign: 'middle',\n  justifyContent: 'center',\n  transition: 'default',\n  // maxWidth: 'calc(self(height) * 7)', // s2 designs show a max width on tags but we pushed back on this in v3\n  backgroundColor: {\n    default: 'gray-100',\n    isHovered: {\n      default: 'gray-200'\n    },\n    isFocusVisible: {\n      default: 'gray-200'\n    },\n    isSelected: {\n      default: baseColor('neutral'),\n      isEmphasized: {\n        default: lightDark('accent-900', 'accent-700'),\n        isHovered: lightDark('accent-1000', 'accent-600'),\n        isPressed: lightDark('accent-1000', 'accent-600'),\n        isFocusVisible: lightDark('accent-1000', 'accent-600')\n      }\n    },\n    isDisabled: 'disabled',\n    forcedColors: {\n      default: 'ButtonFace',\n      isSelected: 'Highlight'\n    }\n  },\n  color: {\n    default: baseColor('neutral'),\n    isSelected: {\n      default: 'gray-25',\n      isEmphasized: 'white'\n    },\n    isDisabled: 'disabled',\n    forcedColors: {\n      default: 'ButtonText',\n      isSelected: 'HighlightText',\n      isDisabled: 'GrayText'\n    }\n  },\n  borderStyle: 'none',\n  paddingEnd: {\n    default: 'edge-to-text',\n    allowsRemoving: 0\n  },\n  margin: 4,\n  cursor: {\n    default: 'default',\n    isLink: 'pointer'\n  },\n  '--iconPrimary': {\n    type: 'fill',\n    value: 'currentColor'\n  }\n});\n\nconst avatarSize = {\n  S: 16,\n  M: 20,\n  L: 24\n} as const;\n\n/** An individual Tag for TagGroups. */\nexport const Tag = /*#__PURE__*/ (forwardRef as forwardRefType)(function Tag({children, textValue, ...props}: TagProps, ref: DOMRef<HTMLDivElement>) {\n  textValue ||= typeof children === 'string' ? children : undefined;\n  let ctx = useSlottedContext(TagGroupContext);\n  let isInRealDOM = Boolean(ctx?.size);\n  let {size, isEmphasized} = ctx ?? {};\n  let domRef = useDOMRef(ref);\n\n  let backupRef = useRef(null);\n  domRef = domRef || backupRef;\n  let isLink = props.href != null;\n  return (\n    <AriaTag\n      textValue={textValue}\n      {...props}\n      ref={domRef}\n      style={pressScale(domRef)}\n      className={renderProps => tagStyles({size, isEmphasized, isLink, ...renderProps})} >\n      {composeRenderProps(children, (children, renderProps) => (\n        <TagWrapper isInRealDOM={isInRealDOM} isEmphasized={isEmphasized} {...renderProps}>{typeof children === 'string' ? <Text>{children}</Text> : children}</TagWrapper>\n      ))}\n    </AriaTag>\n  );\n});\n\nfunction TagWrapper({children, isDisabled, allowsRemoving, isInRealDOM, isEmphasized, isSelected}) {\n  let {size = 'M'} = useSlottedContext(TagGroupContext) ?? {};\n  return (\n    <>\n      {isInRealDOM && (\n      <div\n        className={style({\n          display: 'flex',\n          minWidth: 0,\n          alignItems: 'center',\n          gap: 'text-to-visual',\n          forcedColorAdjust: 'none',\n          backgroundColor: 'transparent'\n        })}>\n        <Provider\n          values={[\n            [TextContext, {styles: style({order: 1, truncate: true})}],\n            [IconContext, {\n              render: centerBaseline({slot: 'icon', styles: style({order: 0})}),\n              styles: style({size: fontRelative(20), marginStart: '--iconMargin', flexShrink: 0})\n            }],\n            [AvatarContext, {\n              size: avatarSize[size],\n              styles: style({order: 0})\n            }],\n            [ImageContext, {\n              styles: style({\n                size: fontRelative(20),\n                flexShrink: 0,\n                order: 0,\n                aspectRatio: 'square',\n                objectFit: 'contain',\n                borderRadius: 'sm'\n              })\n            }]\n          ]}>\n          {children}\n        </Provider>\n      </div>\n        )}\n      {!isInRealDOM && children}\n      {allowsRemoving && isInRealDOM && (\n        <ClearButton\n          slot=\"remove\"\n          size={size}\n          isStaticColor={isEmphasized && isSelected}\n          isDisabled={isDisabled} />\n      )}\n    </>\n  );\n}\n"],"names":[],"version":3,"file":"TagGroup.css.map"}