@cube-dev/ui-kit 0.0.0-canary-63be1e5 → 0.0.0-canary-dec6fb1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/_internal/hooks/index.js +1 -1
- package/es/_internal/hooks/use-chained-callback.js +1 -1
- package/es/_internal/hooks/use-debounced-value.js +1 -1
- package/es/_internal/hooks/use-deprecation-warning.js +1 -1
- package/es/_internal/hooks/use-effect-once.js +1 -1
- package/es/_internal/hooks/use-event.js +1 -1
- package/es/_internal/hooks/use-is-first-render.js +1 -1
- package/es/_internal/hooks/use-sync-ref.js +1 -1
- package/es/_internal/hooks/use-timer/index.js +1 -1
- package/es/_internal/hooks/use-timer/timer.js +1 -1
- package/es/_internal/hooks/use-timer/use-timer.js +1 -1
- package/es/_internal/hooks/use-update-effect.js +1 -1
- package/es/_internal/hooks/use-warn.js +1 -1
- package/es/_internal/index.js +1 -1
- package/es/components/Block.js +1 -1
- package/es/components/CollectionItem.js +1 -1
- package/es/components/GlobalStyles.js +1 -1
- package/es/components/GridProvider.js +1 -1
- package/es/components/HiddenInput.js +1 -1
- package/es/components/OpenTrasition.js +1 -1
- package/es/components/Root.js +1 -1
- package/es/components/actions/Action/Action.js +1 -1
- package/es/components/actions/Button/Button.js +1 -1
- package/es/components/actions/Button/index.js +1 -1
- package/es/components/actions/ButtonGroup/ButtonGroup.js +1 -1
- package/es/components/actions/CommandMenu/CommandMenu.js +1 -1
- package/es/components/actions/CommandMenu/index.js +1 -1
- package/es/components/actions/CommandMenu/styled.js +1 -1
- package/es/components/actions/ItemAction/ItemAction.js +1 -1
- package/es/components/actions/ItemAction/index.js +1 -1
- package/es/components/actions/ItemActionContext.js +1 -1
- package/es/components/actions/ItemButton/ItemButton.js +1 -1
- package/es/components/actions/ItemButton/index.js +1 -1
- package/es/components/actions/Link/Link.js +1 -1
- package/es/components/actions/Menu/Menu.js +1 -1
- package/es/components/actions/Menu/MenuItem.js +1 -1
- package/es/components/actions/Menu/MenuSection.js +1 -1
- package/es/components/actions/Menu/MenuTrigger.js +1 -1
- package/es/components/actions/Menu/SubMenuTrigger.js +1 -1
- package/es/components/actions/Menu/SubmenuTriggerContext.js +1 -1
- package/es/components/actions/Menu/context.js +1 -1
- package/es/components/actions/Menu/index.js +1 -1
- package/es/components/actions/Menu/styled.js +1 -1
- package/es/components/actions/index.js +1 -1
- package/es/components/actions/use-action.js +1 -1
- package/es/components/actions/use-anchored-menu.js +1 -1
- package/es/components/actions/use-context-menu.js +1 -1
- package/es/components/content/ActiveZone/ActiveZone.js +1 -1
- package/es/components/content/Alert/Alert.js +1 -1
- package/es/components/content/Alert/index.js +1 -1
- package/es/components/content/Alert/types.js +1 -1
- package/es/components/content/Alert/use-alert.js +1 -1
- package/es/components/content/Avatar/Avatar.js +1 -1
- package/es/components/content/Badge/Badge.js +1 -1
- package/es/components/content/Card/Card.js +1 -1
- package/es/components/content/Content.js +1 -1
- package/es/components/content/CopyPasteBlock/CopyPasteBlock.js +1 -1
- package/es/components/content/CopyPasteBlock/index.js +1 -1
- package/es/components/content/CopySnippet/CopySnippet.js +1 -1
- package/es/components/content/CopySnippet/index.js +1 -1
- package/es/components/content/Disclosure/Disclosure.js +1 -1
- package/es/components/content/Disclosure/index.js +1 -1
- package/es/components/content/Divider.js +1 -1
- package/es/components/content/Footer.js +1 -1
- package/es/components/content/Header.js +1 -1
- package/es/components/content/HotKeys/HotKeys.js +1 -1
- package/es/components/content/HotKeys/index.js +1 -1
- package/es/components/content/Item/Item.js +1 -1
- package/es/components/content/Item/index.js +1 -1
- package/es/components/content/ItemBadge/ItemBadge.js +1 -1
- package/es/components/content/ItemBadge/index.js +1 -1
- package/es/components/content/Layout/GridLayout.js +1 -1
- package/es/components/content/Layout/Layout.js +1 -1
- package/es/components/content/Layout/LayoutBlock.js +1 -1
- package/es/components/content/Layout/LayoutCenter.js +1 -1
- package/es/components/content/Layout/LayoutContainer.js +1 -1
- package/es/components/content/Layout/LayoutContent.js +1 -1
- package/es/components/content/Layout/LayoutContext.js +1 -1
- package/es/components/content/Layout/LayoutFlex.js +1 -1
- package/es/components/content/Layout/LayoutFooter.js +1 -1
- package/es/components/content/Layout/LayoutGrid.js +1 -1
- package/es/components/content/Layout/LayoutHeader.js +1 -1
- package/es/components/content/Layout/LayoutPane.js +1 -1
- package/es/components/content/Layout/LayoutPanel.js +1 -1
- package/es/components/content/Layout/LayoutPanelHeader.js +1 -1
- package/es/components/content/Layout/LayoutToolbar.js +1 -1
- package/es/components/content/Layout/hooks/useTinyScrollbar.js +1 -1
- package/es/components/content/Layout/index.js +1 -1
- package/es/components/content/Layout/utils.js +1 -1
- package/es/components/content/List/SectionHeading.js +1 -1
- package/es/components/content/List/index.js +1 -1
- package/es/components/content/Paragraph.js +1 -1
- package/es/components/content/Placeholder/Placeholder.js +1 -1
- package/es/components/content/PrismCode/PrismCode.js +1 -1
- package/es/components/content/PrismCode/prismSetup.js +1 -1
- package/es/components/content/PrismDiffCode/PrismDiffCode.js +1 -1
- package/es/components/content/Result/Result.js +1 -1
- package/es/components/content/Skeleton/Skeleton.js +1 -1
- package/es/components/content/Tag/Tag.js +1 -1
- package/es/components/content/Text.js +1 -1
- package/es/components/content/TextItem/TextItem.js +1 -1
- package/es/components/content/TextItem/index.js +1 -1
- package/es/components/content/Title.js +1 -1
- package/es/components/content/highlightText.js +1 -1
- package/es/components/content/use-auto-tooltip.js +1 -1
- package/es/components/fields/Checkbox/Checkbox.js +1 -1
- package/es/components/fields/Checkbox/CheckboxGroup.js +1 -1
- package/es/components/fields/Checkbox/context.js +1 -1
- package/es/components/fields/Checkbox/index.js +1 -1
- package/es/components/fields/ComboBox/ComboBox.js +1 -1
- package/es/components/fields/ComboBox/index.js +1 -1
- package/es/components/fields/DatePicker/DateInput.js +1 -1
- package/es/components/fields/DatePicker/DateInputBase.js +1 -1
- package/es/components/fields/DatePicker/DatePicker.js +1 -1
- package/es/components/fields/DatePicker/DatePickerButton.js +1 -1
- package/es/components/fields/DatePicker/DatePickerElement.js +1 -1
- package/es/components/fields/DatePicker/DatePickerInput.js +1 -1
- package/es/components/fields/DatePicker/DatePickerSegment.js +1 -1
- package/es/components/fields/DatePicker/DateRangePicker.js +1 -1
- package/es/components/fields/DatePicker/DateRangeSeparatedPicker.js +1 -1
- package/es/components/fields/DatePicker/TimeInput.js +1 -1
- package/es/components/fields/DatePicker/index.js +1 -1
- package/es/components/fields/DatePicker/intl.js +1 -1
- package/es/components/fields/DatePicker/parseDate.js +1 -1
- package/es/components/fields/DatePicker/props.js +1 -1
- package/es/components/fields/DatePicker/types.js +1 -1
- package/es/components/fields/DatePicker/utils.js +1 -1
- package/es/components/fields/FileInput/FileInput.js +1 -1
- package/es/components/fields/FilterListBox/FilterListBox.js +1 -1
- package/es/components/fields/FilterListBox/index.js +1 -1
- package/es/components/fields/FilterPicker/FilterPicker.js +1 -1
- package/es/components/fields/FilterPicker/index.js +1 -1
- package/es/components/fields/Input/Input.js +1 -1
- package/es/components/fields/Input/index.js +1 -1
- package/es/components/fields/ListBox/ListBox.js +1 -1
- package/es/components/fields/ListBox/index.js +1 -1
- package/es/components/fields/NumberInput/NumberInput.js +1 -1
- package/es/components/fields/NumberInput/StepButton.js +1 -1
- package/es/components/fields/PasswordInput/PasswordInput.js +1 -1
- package/es/components/fields/Picker/Picker.js +1 -1
- package/es/components/fields/Picker/index.js +1 -1
- package/es/components/fields/RadioGroup/Radio.js +1 -1
- package/es/components/fields/RadioGroup/RadioGroup.js +1 -1
- package/es/components/fields/RadioGroup/context.js +1 -1
- package/es/components/fields/RadioGroup/index.js +1 -1
- package/es/components/fields/SearchInput/SearchInput.js +1 -1
- package/es/components/fields/SearchInput/index.js +1 -1
- package/es/components/fields/Select/Select.js +1 -1
- package/es/components/fields/Select/index.js +1 -1
- package/es/components/fields/Slider/Gradation.js +1 -1
- package/es/components/fields/Slider/Header.js +1 -1
- package/es/components/fields/Slider/RangeSlider.js +1 -1
- package/es/components/fields/Slider/Slider.js +1 -1
- package/es/components/fields/Slider/SliderBase.js +1 -1
- package/es/components/fields/Slider/SliderInput.js +1 -1
- package/es/components/fields/Slider/SliderThumb.js +1 -1
- package/es/components/fields/Slider/SliderTrack.js +1 -1
- package/es/components/fields/Slider/elements.js +1 -1
- package/es/components/fields/Slider/index.js +1 -1
- package/es/components/fields/Slider/types.js +1 -1
- package/es/components/fields/Switch/Switch.js +1 -1
- package/es/components/fields/Switch/index.js +1 -1
- package/es/components/fields/TextArea/TextArea.js +1 -1
- package/es/components/fields/TextArea/index.js +1 -1
- package/es/components/fields/TextInput/TextInput.js +1 -1
- package/es/components/fields/TextInput/TextInputBase.js +1 -1
- package/es/components/fields/TextInput/index.js +1 -1
- package/es/components/fields/TextInputMapper/TextInputMapper.js +1 -1
- package/es/components/fields/TextInputMapper/index.js +1 -1
- package/es/components/fields/index.js +1 -1
- package/es/components/form/FieldWrapper/FieldWrapper.js +1 -1
- package/es/components/form/FieldWrapper/extract-field-wrapper-props.js +1 -1
- package/es/components/form/FieldWrapper/index.js +1 -1
- package/es/components/form/FieldWrapper/types.js +1 -1
- package/es/components/form/Form/Field.js +1 -1
- package/es/components/form/Form/Form.js +1 -1
- package/es/components/form/Form/ResetButton/ResetButton.js +1 -1
- package/es/components/form/Form/ResetButton/index.js +1 -1
- package/es/components/form/Form/SubmitButton/SubmitButton.js +1 -1
- package/es/components/form/Form/SubmitButton/index.js +1 -1
- package/es/components/form/Form/SubmitError.js +1 -1
- package/es/components/form/Form/index.js +1 -1
- package/es/components/form/Form/types.js +1 -1
- package/es/components/form/Form/use-field/index.js +1 -1
- package/es/components/form/Form/use-field/types.js +1 -1
- package/es/components/form/Form/use-field/use-field-props.js +1 -1
- package/es/components/form/Form/use-field/use-field.js +1 -1
- package/es/components/form/Form/use-form.js +1 -1
- package/es/components/form/Form/validation.js +1 -1
- package/es/components/form/Label.js +1 -1
- package/es/components/form/index.js +1 -1
- package/es/components/form/wrapper.js +1 -1
- package/es/components/helpers/DisplayTransition/DisplayTransition.js +1 -1
- package/es/components/helpers/DisplayTransition/index.js +1 -1
- package/es/components/helpers/IconSwitch/IconSwitch.js +1 -1
- package/es/components/helpers/index.js +1 -1
- package/es/components/layout/Flex.js +1 -1
- package/es/components/layout/Flow.js +1 -1
- package/es/components/layout/Grid.js +1 -1
- package/es/components/layout/Panel.js +1 -1
- package/es/components/layout/Prefix.js +1 -1
- package/es/components/layout/ResizablePanel.js +1 -1
- package/es/components/layout/Space.js +1 -1
- package/es/components/layout/Suffix.js +1 -1
- package/es/components/navigation/LegacyTabs/LegacyTabs.js +1 -1
- package/es/components/organisms/FileTabs/FileTabs.js +1 -1
- package/es/components/organisms/StatsCard/StatsCard.js +1 -1
- package/es/components/other/Base64Upload/Base64Upload.js +1 -1
- package/es/components/other/Calendar/Calendar.js +1 -1
- package/es/components/other/Calendar/CalendarCell.js +1 -1
- package/es/components/other/Calendar/CalendarGrid.js +1 -1
- package/es/components/other/Calendar/RangeCalendar.js +1 -1
- package/es/components/other/CloudLogo/CloudLogo.js +1 -1
- package/es/components/overlays/AlertDialog/AlertDialog.js +1 -1
- package/es/components/overlays/AlertDialog/AlertDialogApiProvider.js +1 -1
- package/es/components/overlays/AlertDialog/AlertDialogZone.js +1 -1
- package/es/components/overlays/AlertDialog/index.js +1 -1
- package/es/components/overlays/AlertDialog/types.js +1 -1
- package/es/components/overlays/Dialog/Dialog.js +1 -1
- package/es/components/overlays/Dialog/DialogContainer.js +1 -1
- package/es/components/overlays/Dialog/DialogForm.js +1 -1
- package/es/components/overlays/Dialog/DialogTrigger.js +1 -1
- package/es/components/overlays/Dialog/context.js +1 -1
- package/es/components/overlays/Dialog/index.js +1 -1
- package/es/components/overlays/Dialog/use-dialog-container.js +1 -1
- package/es/components/overlays/Modal/Modal.js +1 -1
- package/es/components/overlays/Modal/OpenTransition.js +1 -1
- package/es/components/overlays/Modal/Overlay.js +1 -1
- package/es/components/overlays/Modal/Popover.js +1 -1
- package/es/components/overlays/Modal/Tray.js +1 -1
- package/es/components/overlays/Modal/Underlay.js +1 -1
- package/es/components/overlays/Modal/index.js +1 -1
- package/es/components/overlays/Modal/types.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/FloatingNotification.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/NotificationsBar.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/TransitionComponent.js +1 -1
- package/es/components/overlays/NewNotifications/Bar/index.js +1 -1
- package/es/components/overlays/NewNotifications/Dialog/NotificationsDialogContext.js +1 -1
- package/es/components/overlays/NewNotifications/Dialog/NotificationsDialogTrigger.js +1 -1
- package/es/components/overlays/NewNotifications/Dialog/index.js +1 -1
- package/es/components/overlays/NewNotifications/Notification.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationAction.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationCloseButton.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationDescription.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationFooter.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationHeader.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationIcon.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationProvider.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/NotificationView.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/index.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationView/types.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/NotificationsContext.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/NotificationsProvider.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/index.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsContext/use-notifications.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/NotificationsList.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/NotificationsListItem.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/index.js +1 -1
- package/es/components/overlays/NewNotifications/NotificationsList/types.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/index.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/types.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notification-list-item.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notifications-api.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notifications-list.js +1 -1
- package/es/components/overlays/NewNotifications/hooks/use-notifications-observer.js +1 -1
- package/es/components/overlays/NewNotifications/index.js +1 -1
- package/es/components/overlays/NewNotifications/types.js +1 -1
- package/es/components/overlays/Notification/Notification.js +1 -1
- package/es/components/overlays/OverlayWrapper.js +1 -1
- package/es/components/overlays/Toasts/Toast.js +1 -1
- package/es/components/overlays/Toasts/index.js +1 -1
- package/es/components/overlays/Toasts/types.js +1 -1
- package/es/components/overlays/Toasts/use-toasts-api.js +1 -1
- package/es/components/overlays/Tooltip/Tooltip.js +1 -1
- package/es/components/overlays/Tooltip/TooltipProvider.js +1 -1
- package/es/components/overlays/Tooltip/TooltipTrigger.js +1 -1
- package/es/components/overlays/Tooltip/context.js +1 -1
- package/es/components/overlays/Tooltip/index.js +1 -1
- package/es/components/portal/Portal.js +1 -1
- package/es/components/portal/PortalProvider.js +1 -1
- package/es/components/portal/index.js +1 -1
- package/es/components/portal/types.js +1 -1
- package/es/components/portal/usePortal.js +1 -1
- package/es/components/shared/InvalidIcon.js +1 -1
- package/es/components/shared/ValidIcon.js +1 -1
- package/es/components/status/LoadingAnimation/LoadingAnimation.js +1 -1
- package/es/components/status/LoadingAnimation/index.js +1 -1
- package/es/components/status/Spin/Cube.js +1 -1
- package/es/components/status/Spin/InternalSpinner.js +1 -1
- package/es/components/status/Spin/Spin.js +1 -1
- package/es/components/status/Spin/SpinsContainer.js +1 -1
- package/es/components/status/Spin/index.js +1 -1
- package/es/components/status/Spin/types.js +1 -1
- package/es/components/status/index.js +1 -1
- package/es/data/item-themes.js +1 -1
- package/es/data/themes.js +1 -1
- package/es/icons/AdjustmentsHorizontalIcon.js +1 -1
- package/es/icons/AdjustmentsIcon.js +1 -1
- package/es/icons/AiIcon.js +1 -1
- package/es/icons/AreaChartIcon.js +1 -1
- package/es/icons/BackwardIcon.js +1 -1
- package/es/icons/BarChartIcon.js +1 -1
- package/es/icons/BellFilledIcon.js +1 -1
- package/es/icons/BellIcon.js +1 -1
- package/es/icons/BooleanIcon.js +1 -1
- package/es/icons/CalendarEditIcon.js +1 -1
- package/es/icons/CalendarIcon.js +1 -1
- package/es/icons/CaretDownIcon.js +1 -1
- package/es/icons/CaretUpIcon.js +1 -1
- package/es/icons/ChartAreaStackedIcon.js +1 -1
- package/es/icons/ChartAreaStackedPercentageIcon.js +1 -1
- package/es/icons/ChartBarGroupedHorizontalIcon.js +1 -1
- package/es/icons/ChartBarGroupedIcon.js +1 -1
- package/es/icons/ChartBarHorizontalIcon.js +1 -1
- package/es/icons/ChartBarLineIcon.js +1 -1
- package/es/icons/ChartBarStackedHorizontalIcon.js +1 -1
- package/es/icons/ChartBarStackedIcon.js +1 -1
- package/es/icons/ChartBarStackedPercentageHorizontalIcon.js +1 -1
- package/es/icons/ChartBarStackedPercentageIcon.js +1 -1
- package/es/icons/ChartBoxPlot2Icon.js +1 -1
- package/es/icons/ChartBoxPlotIcon.js +1 -1
- package/es/icons/ChartBubbleIcon.js +1 -1
- package/es/icons/ChartDonut2Icon.js +1 -1
- package/es/icons/ChartFunnelIcon.js +1 -1
- package/es/icons/ChartHeatmapIcon.js +1 -1
- package/es/icons/ChartKPIIcon.js +1 -1
- package/es/icons/ChartPie2Icon.js +1 -1
- package/es/icons/ChartScatterIcon.js +1 -1
- package/es/icons/CheckCircleFilledIcon.js +1 -1
- package/es/icons/CheckCircleIcon.js +1 -1
- package/es/icons/CheckIcon.js +1 -1
- package/es/icons/CircleFilledIcon.js +1 -1
- package/es/icons/ClearIcon.js +1 -1
- package/es/icons/CloseCircleFilledIcon.js +1 -1
- package/es/icons/CloseCircleIcon.js +1 -1
- package/es/icons/CloseIcon.js +1 -1
- package/es/icons/CodeIcon.js +1 -1
- package/es/icons/ColumnTotalIcon.js +1 -1
- package/es/icons/CopyIcon.js +1 -1
- package/es/icons/CountIcon.js +1 -1
- package/es/icons/CubeIcon.js +1 -1
- package/es/icons/CubePauseIcon.js +1 -1
- package/es/icons/CubePlayIcon.js +1 -1
- package/es/icons/CurrencyDollarIcon.js +1 -1
- package/es/icons/DangerIcon.js +1 -1
- package/es/icons/DashboardIcon.js +1 -1
- package/es/icons/DatabaseIcon.js +1 -1
- package/es/icons/DecimalDecreaseIcon.js +1 -1
- package/es/icons/DecimalIncreaseIcon.js +1 -1
- package/es/icons/DirectionIcon.js +1 -1
- package/es/icons/DonutIcon.js +1 -1
- package/es/icons/DownIcon.js +1 -1
- package/es/icons/EditIcon.js +1 -1
- package/es/icons/ExclamationCircleFilledIcon.js +1 -1
- package/es/icons/ExclamationCircleIcon.js +1 -1
- package/es/icons/ExclamationIcon.js +1 -1
- package/es/icons/EyeIcon.js +1 -1
- package/es/icons/EyeInvisibleIcon.js +1 -1
- package/es/icons/FilterIcon.js +1 -1
- package/es/icons/FolderFilledIcon.js +1 -1
- package/es/icons/FolderIcon.js +1 -1
- package/es/icons/FolderOpenFilledIcon.js +1 -1
- package/es/icons/FolderOpenIcon.js +1 -1
- package/es/icons/ForwardIcon.js +1 -1
- package/es/icons/HierarchyIcon.js +1 -1
- package/es/icons/HierarchyOpenIcon.js +1 -1
- package/es/icons/Icon.js +1 -1
- package/es/icons/InfoCircleIcon.js +1 -1
- package/es/icons/InfoIcon.js +1 -1
- package/es/icons/KeyIcon.js +1 -1
- package/es/icons/LeftIcon.js +1 -1
- package/es/icons/LineChartIcon.js +1 -1
- package/es/icons/LoadingIcon.js +1 -1
- package/es/icons/LockFilledIcon.js +1 -1
- package/es/icons/LockIcon.js +1 -1
- package/es/icons/MoreIcon.js +1 -1
- package/es/icons/NotAllowedIcon.js +1 -1
- package/es/icons/Number123Icon.js +1 -1
- package/es/icons/NumberIcon.js +1 -1
- package/es/icons/PauseCircleFilledIcon.js +1 -1
- package/es/icons/PauseCircleIcon.js +1 -1
- package/es/icons/PauseIcon.js +1 -1
- package/es/icons/PercentageIcon.js +1 -1
- package/es/icons/PieChartIcon.js +1 -1
- package/es/icons/PlayCircleIcon.js +1 -1
- package/es/icons/PlayIcon.js +1 -1
- package/es/icons/PlusIcon.js +1 -1
- package/es/icons/ProgressBarIcon.js +1 -1
- package/es/icons/ReloadIcon.js +1 -1
- package/es/icons/ReportIcon.js +1 -1
- package/es/icons/ReturnIcon.js +1 -1
- package/es/icons/RightIcon.js +1 -1
- package/es/icons/RowTotalsIcon.js +1 -1
- package/es/icons/SchemeIcon.js +1 -1
- package/es/icons/SearchIcon.js +1 -1
- package/es/icons/SettingsIcon.js +1 -1
- package/es/icons/ShieldFilledIcon.js +1 -1
- package/es/icons/ShieldIcon.js +1 -1
- package/es/icons/SlashIcon.js +1 -1
- package/es/icons/SparklesIcon.js +1 -1
- package/es/icons/SqlIcon.js +1 -1
- package/es/icons/StatsIcon.js +1 -1
- package/es/icons/StopIcon.js +1 -1
- package/es/icons/StringIcon.js +1 -1
- package/es/icons/SubtotalsIcon.js +1 -1
- package/es/icons/SwitchIcon.js +1 -1
- package/es/icons/TableIcon.js +1 -1
- package/es/icons/ThumbsDownIcon.js +1 -1
- package/es/icons/ThumbsUpIcon.js +1 -1
- package/es/icons/ThunderboltCrossedIcon.js +1 -1
- package/es/icons/ThunderboltFilledIcon.js +1 -1
- package/es/icons/ThunderboltIcon.js +1 -1
- package/es/icons/TimeIcon.js +1 -1
- package/es/icons/TrashIcon.js +1 -1
- package/es/icons/UnlockIcon.js +1 -1
- package/es/icons/UpIcon.js +1 -1
- package/es/icons/UserGroupIcon.js +1 -1
- package/es/icons/UserIcon.js +1 -1
- package/es/icons/UserLockIcon.js +1 -1
- package/es/icons/ViewIcon.js +1 -1
- package/es/icons/WarningFilledIcon.js +1 -1
- package/es/icons/WarningIcon.js +1 -1
- package/es/icons/add-new-icon.js +1 -1
- package/es/icons/index.js +1 -1
- package/es/icons/wrap-icon.js +1 -1
- package/es/index.js +1 -1
- package/es/provider.js +1 -1
- package/es/providers/TrackingProvider.js +1 -1
- package/es/providers/navigation.types.js +1 -1
- package/es/providers/navigationAdapter.default.js +1 -1
- package/es/services/notification.js +1 -1
- package/es/shared/form.js +1 -1
- package/es/shared/index.js +1 -1
- package/es/stories/Form.legacy-stories.js +1 -1
- package/es/stories/FormFieldArgs.js +1 -1
- package/es/stories/SimpleLayout.stories.js +1 -1
- package/es/stories/Tasty.stories.js +1 -1
- package/es/stories/components/ConfirmDeletionDialogForm.js +1 -1
- package/es/stories/components/DialogFormApp.js +1 -1
- package/es/stories/components/StyledButton.js +1 -1
- package/es/stories/lists/baseProps.js +1 -1
- package/es/tasty/chunks/cacheKey.js +1 -1
- package/es/tasty/chunks/definitions.js +1 -1
- package/es/tasty/chunks/index.js +1 -1
- package/es/tasty/chunks/renderChunk.js +23 -1
- package/es/tasty/debug.js +1 -1
- package/es/tasty/hooks/index.js +1 -1
- package/es/tasty/hooks/useGlobalStyles.js +10 -2
- package/es/tasty/hooks/useStyles.js +1 -1
- package/es/tasty/index.js +1 -1
- package/es/tasty/injector/index.js +1 -1
- package/es/tasty/injector/injector.js +1 -1
- package/es/tasty/injector/sheet-manager.js +1 -1
- package/es/tasty/injector/types.js +1 -1
- package/es/tasty/parser/classify.js +1 -1
- package/es/tasty/parser/const.js +1 -1
- package/es/tasty/parser/lru.js +1 -1
- package/es/tasty/parser/parser.js +1 -1
- package/es/tasty/parser/tokenizer.js +1 -1
- package/es/tasty/parser/types.js +1 -1
- package/es/tasty/pipeline/conditions.js +1 -1
- package/es/tasty/pipeline/exclusive.js +85 -2
- package/es/tasty/pipeline/index.js +10 -5
- package/es/tasty/pipeline/materialize.js +109 -9
- package/es/tasty/pipeline/parseStateKey.js +12 -2
- package/es/tasty/pipeline/simplify.js +1 -1
- package/es/tasty/states/index.js +5 -10
- package/es/tasty/styles/align.js +1 -1
- package/es/tasty/styles/border.js +1 -1
- package/es/tasty/styles/boxShadow.combinator.js +1 -1
- package/es/tasty/styles/color.js +1 -1
- package/es/tasty/styles/createStyle.js +1 -1
- package/es/tasty/styles/dimension.js +1 -1
- package/es/tasty/styles/display.js +1 -1
- package/es/tasty/styles/fade.js +1 -1
- package/es/tasty/styles/fill.js +1 -1
- package/es/tasty/styles/flow.js +1 -1
- package/es/tasty/styles/font.js +1 -1
- package/es/tasty/styles/fontStyle.js +1 -1
- package/es/tasty/styles/gap.js +1 -1
- package/es/tasty/styles/groupRadius.js +1 -1
- package/es/tasty/styles/height.js +1 -1
- package/es/tasty/styles/index.js +1 -1
- package/es/tasty/styles/inset.js +1 -1
- package/es/tasty/styles/justify.js +1 -1
- package/es/tasty/styles/list.js +1 -1
- package/es/tasty/styles/margin.js +1 -1
- package/es/tasty/styles/outline.js +1 -1
- package/es/tasty/styles/padding.js +1 -1
- package/es/tasty/styles/place.js +1 -1
- package/es/tasty/styles/predefined.js +1 -1
- package/es/tasty/styles/preset.js +1 -1
- package/es/tasty/styles/radius.js +1 -1
- package/es/tasty/styles/reset.js +1 -1
- package/es/tasty/styles/scrollbar.js +1 -1
- package/es/tasty/styles/shadow.js +1 -1
- package/es/tasty/styles/styledScrollbar.js +1 -1
- package/es/tasty/styles/transition.js +1 -1
- package/es/tasty/styles/types.js +1 -1
- package/es/tasty/styles/width.js +1 -1
- package/es/tasty/tasty.js +1 -1
- package/es/tasty/types.js +1 -1
- package/es/tasty/utils/cache-wrapper.js +1 -1
- package/es/tasty/utils/case-converter.js +1 -1
- package/es/tasty/utils/colors.js +1 -1
- package/es/tasty/utils/dotize.js +1 -1
- package/es/tasty/utils/filterBaseProps.js +1 -1
- package/es/tasty/utils/getDisplayName.js +1 -1
- package/es/tasty/utils/isDevEnv.js +1 -1
- package/es/tasty/utils/mergeStyles.js +2 -2
- package/es/tasty/utils/modAttrs.js +1 -1
- package/es/tasty/utils/processTokens.js +1 -1
- package/es/tasty/utils/string.js +1 -1
- package/es/tasty/utils/styles.js +1 -1
- package/es/tasty/utils/warnings.js +1 -1
- package/es/tokens.js +1 -1
- package/es/utils/ResizeSensor.js +1 -1
- package/es/utils/index.js +1 -1
- package/es/utils/modules.js +1 -1
- package/es/utils/promise.js +1 -1
- package/es/utils/raf.js +1 -1
- package/es/utils/random.js +1 -1
- package/es/utils/range.js +1 -1
- package/es/utils/react/RenderCache.js +1 -1
- package/es/utils/react/Slots.js +1 -1
- package/es/utils/react/chain.js +1 -1
- package/es/utils/react/forwardRefWithGenerics.js +1 -1
- package/es/utils/react/index.js +1 -1
- package/es/utils/react/interactions.js +1 -1
- package/es/utils/react/isTextOnly.js +1 -1
- package/es/utils/react/mapProps.js +1 -1
- package/es/utils/react/mergeProps.js +1 -1
- package/es/utils/react/nullableValue.js +1 -1
- package/es/utils/react/resolveIcon.js +1 -1
- package/es/utils/react/sharedStore.js +1 -1
- package/es/utils/react/useCombinedRefs.js +1 -1
- package/es/utils/react/useControlledFocusVisible.js +1 -1
- package/es/utils/react/useEventBus.js +1 -1
- package/es/utils/react/useId.js +1 -1
- package/es/utils/react/useIsDarwin.js +1 -1
- package/es/utils/react/useKeySymbols.js +1 -1
- package/es/utils/react/useLayoutEffect.js +1 -1
- package/es/utils/react/useLocalStorage.js +1 -1
- package/es/utils/react/useQaProps.js +1 -1
- package/es/utils/react/useViewportSize.js +1 -1
- package/es/utils/react/wrapNodeIfPlain.js +1 -1
- package/es/utils/tree.js +1 -1
- package/es/utils/warnings.js +1 -1
- package/es/version.js +2 -2
- package/package.json +1 -1
- package/types/components/HiddenInput.d.ts +1 -1
- package/types/components/actions/CommandMenu/styled.d.ts +5 -5
- package/types/components/actions/Menu/styled.d.ts +8 -8
- package/types/components/content/List/SectionHeading.d.ts +1 -1
- package/types/components/content/Text.d.ts +7 -7
- package/types/components/fields/DatePicker/DatePickerElement.d.ts +1 -1
- package/types/components/fields/Select/Select.d.ts +1 -1
- package/types/components/fields/Slider/elements.d.ts +6 -6
- package/types/tasty/chunks/renderChunk.d.ts +5 -0
- package/types/tasty/injector/types.d.ts +1 -1
- package/types/tasty/pipeline/exclusive.d.ts +15 -0
- package/types/tasty/tasty.d.ts +1 -1
- package/es/tasty/utils/getModCombinations.js +0 -38
- package/es/tasty/utils/mediaRangeOptimizer.js +0 -338
- package/es/tasty/utils/renderStyles.js +0 -1624
- package/types/tasty/utils/getModCombinations.d.ts +0 -9
- package/types/tasty/utils/mediaRangeOptimizer.d.ts +0 -88
- package/types/tasty/utils/renderStyles.d.ts +0 -47
|
@@ -1,1624 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license MIT
|
|
3
|
-
* author: Cube Dev Team
|
|
4
|
-
* @cube-dev/ui-kit v0.0.0-canary-63be1e5
|
|
5
|
-
* Released under the MIT license.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @deprecated This file is superseded by src/tasty/pipeline/index.ts
|
|
10
|
-
* It is kept for reference during the transition period.
|
|
11
|
-
* All imports should use the new pipeline instead.
|
|
12
|
-
*
|
|
13
|
-
* Original description:
|
|
14
|
-
* Style rendering that works with structured style objects.
|
|
15
|
-
* Eliminates CSS string parsing for better performance.
|
|
16
|
-
*
|
|
17
|
-
* Key optimizations:
|
|
18
|
-
* - Early exit checks (hasSameAttributeConflicts) handle common cases without overhead
|
|
19
|
-
* - Logical rule caching (logicalRulesCache) memoizes expensive style processing
|
|
20
|
-
* - Conflict detection prevents invalid CSS selector combinations
|
|
21
|
-
* - Not-selector optimization reduces CSS size
|
|
22
|
-
* - Priority-based filtering handles mod precedence correctly
|
|
23
|
-
* - Consolidated mod processing eliminates code duplication
|
|
24
|
-
*/
|
|
25
|
-
import { Lru } from '../parser/lru';
|
|
26
|
-
import { createStyle, STYLE_HANDLER_MAP } from '../styles';
|
|
27
|
-
import { getModCombinationsIterative } from './getModCombinations';
|
|
28
|
-
import { computeNonOverlappingRanges, parseMediaCondition, rangeToMediaCondition, } from './mediaRangeOptimizer';
|
|
29
|
-
import { computeState, getModSelector, stringifyStyles, styleStateMapToStyleStateDataList, } from './styles';
|
|
30
|
-
// Cache logical rules per styles+breakpoints to avoid recomputation across identical calls
|
|
31
|
-
const logicalRulesCache = new Lru(5000);
|
|
32
|
-
// Normalize selector suffixes coming from `$` in style handler results.
|
|
33
|
-
// Some legacy handlers return suffixes starting with `&` (e.g. '& > *').
|
|
34
|
-
// The renderer expects suffixes without the ampersand because it adds
|
|
35
|
-
// the parent selector during materialization.
|
|
36
|
-
function normalizeDollarSelectorSuffix(suffix) {
|
|
37
|
-
if (!suffix)
|
|
38
|
-
return '';
|
|
39
|
-
return suffix.startsWith('&') ? suffix.slice(1) : suffix;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Check if a key represents a CSS selector
|
|
43
|
-
*/
|
|
44
|
-
export function isSelector(key) {
|
|
45
|
-
return (key.startsWith('&') || key.startsWith('.') || key.match(/^[A-Z]/) !== null);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Transform selector affix by converting capitalized words to sub-element selectors.
|
|
49
|
-
* Returns a selector prefix with leading and trailing spaces.
|
|
50
|
-
*
|
|
51
|
-
* Examples:
|
|
52
|
-
* '> Body > Row' -> ' > [data-element="Body"] > [data-element="Row"] '
|
|
53
|
-
* '> Body > Row >' -> ' > [data-element="Body"] > [data-element="Row"] > '
|
|
54
|
-
* '>' -> ' > '
|
|
55
|
-
*/
|
|
56
|
-
function transformSelectorAffix(affix) {
|
|
57
|
-
const trimmed = affix.trim();
|
|
58
|
-
if (!trimmed)
|
|
59
|
-
return ' ';
|
|
60
|
-
// Validate that combinators have spaces around them
|
|
61
|
-
// Check for capitalized words adjacent to combinators without spaces
|
|
62
|
-
const invalidPattern = /[A-Z][a-z]*[>+~]|[>+~][A-Z][a-z]*/;
|
|
63
|
-
if (invalidPattern.test(trimmed)) {
|
|
64
|
-
console.error(`[Tasty] Invalid selector affix ($) syntax: "${affix}"\n` +
|
|
65
|
-
`Combinators (>, +, ~) must have spaces around them when used with element names.\n` +
|
|
66
|
-
`Example: Use "$: '> Body > Row'" instead of "$: '>Body>Row'"\n` +
|
|
67
|
-
`This is a design choice: the parser uses simple whitespace splitting for performance.`);
|
|
68
|
-
}
|
|
69
|
-
const tokens = trimmed.split(/\s+/);
|
|
70
|
-
const transformed = tokens.map((token) => /^[A-Z]/.test(token) ? `[data-element="${token}"]` : token);
|
|
71
|
-
return ` ${transformed.join(' ')} `;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Get the selector suffix for a key
|
|
75
|
-
*/
|
|
76
|
-
function getSelector(key, styles) {
|
|
77
|
-
if (key.startsWith('&')) {
|
|
78
|
-
return key.slice(1);
|
|
79
|
-
}
|
|
80
|
-
if (key.startsWith('.')) {
|
|
81
|
-
return ` ${key}`;
|
|
82
|
-
}
|
|
83
|
-
if (key.match(/^[A-Z]/)) {
|
|
84
|
-
const affix = styles?.$;
|
|
85
|
-
if (affix !== undefined) {
|
|
86
|
-
const prefix = transformSelectorAffix(String(affix));
|
|
87
|
-
return `${prefix}[data-element="${key}"]`;
|
|
88
|
-
}
|
|
89
|
-
return ` [data-element="${key}"]`;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Parse attribute selector into its components.
|
|
95
|
-
* Simple regex parsing - JS engines optimize repeated patterns internally.
|
|
96
|
-
*/
|
|
97
|
-
function parseAttributeSelector(selector) {
|
|
98
|
-
// Match patterns like [data-size="medium"] or [data-selected]
|
|
99
|
-
const match = selector.match(/^\[([^=\]]+)(?:="([^"]+)")?\]$/);
|
|
100
|
-
return match
|
|
101
|
-
? {
|
|
102
|
-
attribute: match[1],
|
|
103
|
-
value: match[2] || 'true', // Handle boolean attributes
|
|
104
|
-
fullSelector: selector,
|
|
105
|
-
}
|
|
106
|
-
: null;
|
|
107
|
-
}
|
|
108
|
-
function hasConflictingAttributeSelectors(mods, parsedMods) {
|
|
109
|
-
const attributeValues = new Map();
|
|
110
|
-
const attributeBooleans = new Set();
|
|
111
|
-
for (const mod of mods) {
|
|
112
|
-
const parsed = parsedMods?.get(mod) ?? parseAttributeSelector(mod);
|
|
113
|
-
if (!parsed)
|
|
114
|
-
continue;
|
|
115
|
-
if (parsed.value === 'true') {
|
|
116
|
-
// Boolean attribute
|
|
117
|
-
attributeBooleans.add(parsed.attribute);
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// Value attribute
|
|
121
|
-
if (!attributeValues.has(parsed.attribute)) {
|
|
122
|
-
attributeValues.set(parsed.attribute, []);
|
|
123
|
-
}
|
|
124
|
-
attributeValues.get(parsed.attribute).push(parsed.value);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Check for multiple different values for the same attribute
|
|
128
|
-
for (const values of attributeValues.values()) {
|
|
129
|
-
if (values.length > 1)
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Create a conflict checker function that uses precomputed attribute maps
|
|
136
|
-
* for efficient conflict detection during combination generation
|
|
137
|
-
*/
|
|
138
|
-
function createAttributeConflictChecker(parsedMods) {
|
|
139
|
-
return (combination) => hasConflictingAttributeSelectors(combination, parsedMods);
|
|
140
|
-
}
|
|
141
|
-
// Build precomputed attribute maps for efficient not selector optimization
|
|
142
|
-
function buildAttributeMaps(currentMods, allMods, parsedModsCache) {
|
|
143
|
-
const allAttributes = new Map();
|
|
144
|
-
const currentAttributes = new Map();
|
|
145
|
-
const parsedMods = parsedModsCache || new Map();
|
|
146
|
-
// Parse all mods once and cache results (only if cache not provided)
|
|
147
|
-
if (!parsedModsCache) {
|
|
148
|
-
const allModsSet = new Set([...currentMods, ...allMods]);
|
|
149
|
-
for (const mod of allModsSet) {
|
|
150
|
-
if (!parsedMods.has(mod)) {
|
|
151
|
-
parsedMods.set(mod, parseAttributeSelector(mod));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// Build map of all possible values for each attribute
|
|
156
|
-
for (const mod of allMods) {
|
|
157
|
-
const parsed = parsedMods.get(mod);
|
|
158
|
-
if (parsed && parsed.value !== 'true') {
|
|
159
|
-
if (!allAttributes.has(parsed.attribute)) {
|
|
160
|
-
allAttributes.set(parsed.attribute, new Set());
|
|
161
|
-
}
|
|
162
|
-
allAttributes.get(parsed.attribute).add(parsed.value);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// Build map of current mod attribute values
|
|
166
|
-
for (const mod of currentMods) {
|
|
167
|
-
const parsed = parsedMods.get(mod);
|
|
168
|
-
if (parsed && parsed.value !== 'true') {
|
|
169
|
-
currentAttributes.set(parsed.attribute, parsed.value);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return { allAttributes, currentAttributes, parsedMods };
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Check if a combination of positive and negative selectors creates a contradiction
|
|
176
|
-
* Returns true if the combination is INVALID and should be pruned
|
|
177
|
-
*/
|
|
178
|
-
function hasContradiction(currentMods, notMods, parsedMods) {
|
|
179
|
-
// Build maps of positive selector states
|
|
180
|
-
const positiveAttributes = new Map();
|
|
181
|
-
const positiveBooleans = new Set();
|
|
182
|
-
for (const mod of currentMods) {
|
|
183
|
-
const parsed = parsedMods.get(mod);
|
|
184
|
-
if (parsed) {
|
|
185
|
-
if (parsed.value === 'true') {
|
|
186
|
-
// Boolean attribute (e.g., [data-theme])
|
|
187
|
-
positiveBooleans.add(parsed.attribute);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
// Value attribute (e.g., [data-theme="danger"])
|
|
191
|
-
if (!positiveAttributes.has(parsed.attribute)) {
|
|
192
|
-
positiveAttributes.set(parsed.attribute, []);
|
|
193
|
-
}
|
|
194
|
-
positiveAttributes.get(parsed.attribute).push(parsed.value);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// Check negative selectors for contradictions
|
|
199
|
-
for (const mod of notMods) {
|
|
200
|
-
const parsed = parsedMods.get(mod);
|
|
201
|
-
if (parsed) {
|
|
202
|
-
if (parsed.value === 'true') {
|
|
203
|
-
// Negative boolean: !([data-theme])
|
|
204
|
-
// Case 6: Value positive + attribute negative = CONTRADICTION
|
|
205
|
-
if (positiveAttributes.has(parsed.attribute) ||
|
|
206
|
-
positiveBooleans.has(parsed.attribute)) {
|
|
207
|
-
return true; // INVALID: can't have value without attribute
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
// Negative value: !([data-theme="danger"])
|
|
212
|
-
// No contradiction - this is valid
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
function optimizeNotSelectors(currentMods, allMods, precomputedMaps) {
|
|
219
|
-
const maps = precomputedMaps || buildAttributeMaps(currentMods, allMods);
|
|
220
|
-
const notMods = allMods.filter((mod) => !currentMods.includes(mod));
|
|
221
|
-
const optimizedNotMods = [];
|
|
222
|
-
// Precompute presence of negative boolean attributes to avoid repeated scans
|
|
223
|
-
const negativeBooleanByAttr = new Set();
|
|
224
|
-
for (const mod of notMods) {
|
|
225
|
-
const p = maps.parsedMods.get(mod);
|
|
226
|
-
if (p && p.value === 'true') {
|
|
227
|
-
negativeBooleanByAttr.add(p.attribute);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// Build maps of positive selector states for subsumption optimization
|
|
231
|
-
const positiveAttributes = new Map();
|
|
232
|
-
const positiveBooleans = new Set();
|
|
233
|
-
for (const mod of currentMods) {
|
|
234
|
-
const parsed = maps.parsedMods.get(mod);
|
|
235
|
-
if (parsed) {
|
|
236
|
-
if (parsed.value === 'true') {
|
|
237
|
-
positiveBooleans.add(parsed.attribute);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
if (!positiveAttributes.has(parsed.attribute)) {
|
|
241
|
-
positiveAttributes.set(parsed.attribute, []);
|
|
242
|
-
}
|
|
243
|
-
positiveAttributes.get(parsed.attribute).push(parsed.value);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
for (const mod of notMods) {
|
|
248
|
-
const parsed = maps.parsedMods.get(mod);
|
|
249
|
-
if (parsed && parsed.value !== 'true') {
|
|
250
|
-
// Negative value selector
|
|
251
|
-
// If we already have a value for this attribute, skip this not selector
|
|
252
|
-
// because it's already mutually exclusive (optimization)
|
|
253
|
-
if (maps.currentAttributes.has(parsed.attribute)) {
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
// If we have a positive value for this attribute, skip the negative boolean
|
|
258
|
-
// This avoids producing selectors like [data-attr="x"]:not([data-attr])
|
|
259
|
-
if (parsed && parsed.value === 'true') {
|
|
260
|
-
if (maps.currentAttributes.has(parsed.attribute)) {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
// Case 4 subsumption: If we have a value positive and boolean positive for same attribute
|
|
265
|
-
// The value implies the boolean, so we can skip the boolean from positive mods
|
|
266
|
-
// (This is handled elsewhere - the value selector is more specific)
|
|
267
|
-
// Case 7 subsumption: If we have a value negative and boolean negative for same attribute
|
|
268
|
-
// The boolean negative implies value negative, so skip the value negative
|
|
269
|
-
if (parsed && parsed.value !== 'true') {
|
|
270
|
-
// If we also have the boolean attribute in negative mods, skip the value negative
|
|
271
|
-
if (negativeBooleanByAttr.has(parsed.attribute)) {
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
optimizedNotMods.push(mod);
|
|
276
|
-
}
|
|
277
|
-
return optimizedNotMods;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Quick check if there are any same-attribute conflicts (boolean vs value for same attribute).
|
|
281
|
-
* Returns true if conflicts exist, false if filtering can be skipped.
|
|
282
|
-
* This early exit optimization handles the common case efficiently.
|
|
283
|
-
*/
|
|
284
|
-
function hasSameAttributeConflicts(allMods, parsedModsCache) {
|
|
285
|
-
const attributeCounts = new Map();
|
|
286
|
-
for (const mod of allMods) {
|
|
287
|
-
const parsed = parsedModsCache.get(mod);
|
|
288
|
-
if (!parsed)
|
|
289
|
-
continue;
|
|
290
|
-
const isBool = parsed.value === 'true';
|
|
291
|
-
const existing = attributeCounts.get(parsed.attribute);
|
|
292
|
-
if (existing) {
|
|
293
|
-
// Already have both types for this attribute - conflict exists!
|
|
294
|
-
if ((isBool && existing.hasValue) || (!isBool && existing.hasBool)) {
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
// Update the types we've seen
|
|
298
|
-
if (isBool)
|
|
299
|
-
existing.hasBool = true;
|
|
300
|
-
else
|
|
301
|
-
existing.hasValue = true;
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
attributeCounts.set(parsed.attribute, {
|
|
305
|
-
hasBool: isBool,
|
|
306
|
-
hasValue: !isBool,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Filter mods based on priority order for same-attribute conflicts.
|
|
314
|
-
* If a boolean selector has higher priority than value selectors for the same attribute,
|
|
315
|
-
* remove the value selectors (they would be shadowed by the boolean).
|
|
316
|
-
*
|
|
317
|
-
* Priority is determined by order in the styleStates (after reversal - earlier = higher priority)
|
|
318
|
-
*/
|
|
319
|
-
function filterModsByPriority(allMods, styleStates, lookupStyles, parsedModsCache) {
|
|
320
|
-
// Early exit: if no same-attribute conflicts exist, skip all the expensive work
|
|
321
|
-
// This handles the common case efficiently without any cache overhead
|
|
322
|
-
if (!hasSameAttributeConflicts(allMods, parsedModsCache)) {
|
|
323
|
-
return allMods;
|
|
324
|
-
}
|
|
325
|
-
// Build priority map: for each mod, find its earliest appearance in any state list
|
|
326
|
-
const modPriorities = new Map();
|
|
327
|
-
for (const style of lookupStyles) {
|
|
328
|
-
const states = styleStates[style];
|
|
329
|
-
if (!states || states.length === 0)
|
|
330
|
-
continue; // Skip empty states
|
|
331
|
-
// states are already reversed (higher priority = lower index)
|
|
332
|
-
for (let index = 0; index < states.length; index++) {
|
|
333
|
-
const state = states[index];
|
|
334
|
-
if (!state.mods || state.mods.length === 0)
|
|
335
|
-
continue; // Skip empty mods
|
|
336
|
-
for (const mod of state.mods) {
|
|
337
|
-
const currentPriority = modPriorities.get(mod);
|
|
338
|
-
if (currentPriority === undefined || index < currentPriority) {
|
|
339
|
-
modPriorities.set(mod, index);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// Group mods by attribute
|
|
345
|
-
const attributeGroups = new Map();
|
|
346
|
-
for (const mod of allMods) {
|
|
347
|
-
const parsed = parsedModsCache.get(mod);
|
|
348
|
-
if (!parsed)
|
|
349
|
-
continue;
|
|
350
|
-
const priority = modPriorities.get(mod);
|
|
351
|
-
if (priority === undefined)
|
|
352
|
-
continue;
|
|
353
|
-
const isBool = parsed.value === 'true';
|
|
354
|
-
let group = attributeGroups.get(parsed.attribute);
|
|
355
|
-
if (!group) {
|
|
356
|
-
group = [];
|
|
357
|
-
attributeGroups.set(parsed.attribute, group);
|
|
358
|
-
}
|
|
359
|
-
group.push({
|
|
360
|
-
mod,
|
|
361
|
-
isBool,
|
|
362
|
-
priority,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
// Filter: for each attribute, if boolean has higher priority than any value, remove values
|
|
366
|
-
const modsToRemove = new Set();
|
|
367
|
-
for (const [attribute, group] of attributeGroups.entries()) {
|
|
368
|
-
// Only process attributes with more than one mod
|
|
369
|
-
if (group.length <= 1)
|
|
370
|
-
continue;
|
|
371
|
-
const boolMods = group.filter((m) => m.isBool);
|
|
372
|
-
const valueMods = group.filter((m) => !m.isBool);
|
|
373
|
-
// Only check if we have both types
|
|
374
|
-
if (boolMods.length === 0 || valueMods.length === 0)
|
|
375
|
-
continue;
|
|
376
|
-
// Check if any boolean has higher priority (lower index) than all values
|
|
377
|
-
for (const boolMod of boolMods) {
|
|
378
|
-
const hasHigherPriorityThanAllValues = valueMods.every((valueMod) => boolMod.priority < valueMod.priority);
|
|
379
|
-
if (hasHigherPriorityThanAllValues) {
|
|
380
|
-
// This boolean shadows all value mods for this attribute
|
|
381
|
-
valueMods.forEach((valueMod) => modsToRemove.add(valueMod.mod));
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return allMods.filter((mod) => !modsToRemove.has(mod));
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Explode a style handler result into logical rules with proper mapping
|
|
389
|
-
* Phase 1: Handler fan-out ($ selectors)
|
|
390
|
-
* Phase 2: Rule materialization
|
|
391
|
-
*/
|
|
392
|
-
function explodeHandlerResult(result, selectorSuffix = '', atRuleContext, ownMods, negatedOwnMods) {
|
|
393
|
-
if (!result)
|
|
394
|
-
return [];
|
|
395
|
-
// Phase 1: Handler fan-out - normalize to array
|
|
396
|
-
const resultArray = Array.isArray(result) ? result : [result];
|
|
397
|
-
const logicalRules = [];
|
|
398
|
-
for (const item of resultArray) {
|
|
399
|
-
if (!item || typeof item !== 'object')
|
|
400
|
-
continue;
|
|
401
|
-
const { $, ...styleProps } = item;
|
|
402
|
-
// Build declarations from style props
|
|
403
|
-
const declarations = {};
|
|
404
|
-
for (const [prop, value] of Object.entries(styleProps)) {
|
|
405
|
-
if (value != null && value !== '') {
|
|
406
|
-
declarations[prop] = String(value);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (Object.keys(declarations).length === 0)
|
|
410
|
-
continue;
|
|
411
|
-
// Phase 2: Selector fan-out - handle $ suffixes
|
|
412
|
-
// IMPORTANT: If we are already in a pseudo-element context (contains '::'),
|
|
413
|
-
// CSS does not allow further descendant/child selectors (e.g., '>*') after
|
|
414
|
-
// a pseudo-element. In such cases we must ignore the `$`-derived selectors.
|
|
415
|
-
const inPseudoElementContext = selectorSuffix.includes('::');
|
|
416
|
-
if (inPseudoElementContext && $) {
|
|
417
|
-
// Skip this item entirely to avoid producing invalid selectors like
|
|
418
|
-
// `.t0::before>*`.
|
|
419
|
-
continue;
|
|
420
|
-
}
|
|
421
|
-
const suffixes = $
|
|
422
|
-
? (Array.isArray($) ? $ : [$]).map((s) => selectorSuffix + normalizeDollarSelectorSuffix(String(s)))
|
|
423
|
-
: [selectorSuffix];
|
|
424
|
-
// Create logical rules for each suffix
|
|
425
|
-
for (const suffix of suffixes) {
|
|
426
|
-
const rule = {
|
|
427
|
-
selectorSuffix: suffix,
|
|
428
|
-
declarations: { ...declarations },
|
|
429
|
-
};
|
|
430
|
-
if (atRuleContext) {
|
|
431
|
-
rule.atRuleContext = atRuleContext;
|
|
432
|
-
}
|
|
433
|
-
if (ownMods?.length) {
|
|
434
|
-
rule.ownMods = ownMods;
|
|
435
|
-
}
|
|
436
|
-
if (negatedOwnMods?.length) {
|
|
437
|
-
rule.negatedOwnMods = negatedOwnMods;
|
|
438
|
-
}
|
|
439
|
-
logicalRules.push(rule);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return logicalRules;
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Convert handler result (CSSMap) to CSS string for global injection
|
|
446
|
-
*/
|
|
447
|
-
function convertHandlerResultToCSS(result, selectorSuffix = '') {
|
|
448
|
-
if (!result)
|
|
449
|
-
return '';
|
|
450
|
-
if (Array.isArray(result)) {
|
|
451
|
-
const fragments = [];
|
|
452
|
-
for (const item of result) {
|
|
453
|
-
const itemCSS = convertHandlerResultToCSS(item, selectorSuffix);
|
|
454
|
-
if (itemCSS) {
|
|
455
|
-
fragments.push(itemCSS);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return fragments.join('');
|
|
459
|
-
}
|
|
460
|
-
const { $, css, ...styleProps } = result;
|
|
461
|
-
const styleFragments = [];
|
|
462
|
-
// Process style properties using array accumulation
|
|
463
|
-
for (const [styleName, value] of Object.entries(styleProps)) {
|
|
464
|
-
if (Array.isArray(value)) {
|
|
465
|
-
// Handle array values
|
|
466
|
-
for (const val of value) {
|
|
467
|
-
if (val) {
|
|
468
|
-
styleFragments.push(`${styleName}: ${val};\n`);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
else if (value) {
|
|
473
|
-
// Handle single values
|
|
474
|
-
styleFragments.push(`${styleName}: ${value};\n`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
let renderedStyles = styleFragments.join('');
|
|
478
|
-
if (css) {
|
|
479
|
-
renderedStyles = css + '\n' + renderedStyles;
|
|
480
|
-
}
|
|
481
|
-
if (!renderedStyles) {
|
|
482
|
-
return '';
|
|
483
|
-
}
|
|
484
|
-
const finalSelectorSuffix = selectorSuffix || '';
|
|
485
|
-
if (Array.isArray($)) {
|
|
486
|
-
const ruleFragments = [];
|
|
487
|
-
for (const suffix of $) {
|
|
488
|
-
const normalized = suffix
|
|
489
|
-
? normalizeDollarSelectorSuffix(String(suffix))
|
|
490
|
-
: '';
|
|
491
|
-
ruleFragments.push(`&${finalSelectorSuffix}${normalized}{\n${renderedStyles}}\n`);
|
|
492
|
-
}
|
|
493
|
-
return ruleFragments.join('');
|
|
494
|
-
}
|
|
495
|
-
const normalizedSingle = $ ? normalizeDollarSelectorSuffix(String($)) : '';
|
|
496
|
-
return `&${finalSelectorSuffix}${normalizedSingle}{\n${renderedStyles}}\n`;
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Process state maps with mod combinations and generate logical rules.
|
|
500
|
-
* This consolidates the common logic for handling mod combinations, priority filtering,
|
|
501
|
-
* contradiction checking, and selector optimization.
|
|
502
|
-
*
|
|
503
|
-
* Enhanced to support advanced states (media queries, container queries, etc.)
|
|
504
|
-
*/
|
|
505
|
-
function processStateMapsWithModCombinations(styleStates, lookupStyles, handler, parentSuffix, allLogicalRules) {
|
|
506
|
-
// Collect all mods and check for advanced states
|
|
507
|
-
const allMods = [];
|
|
508
|
-
const seenMods = new Set();
|
|
509
|
-
let hasAdvancedStates = false;
|
|
510
|
-
// Collect all unique at-rule contexts from states
|
|
511
|
-
const atRuleContextsByKey = new Map();
|
|
512
|
-
for (const style of lookupStyles) {
|
|
513
|
-
const states = styleStates[style];
|
|
514
|
-
if (!states)
|
|
515
|
-
continue;
|
|
516
|
-
for (const state of states) {
|
|
517
|
-
if (state.mods) {
|
|
518
|
-
for (const mod of state.mods) {
|
|
519
|
-
if (!seenMods.has(mod)) {
|
|
520
|
-
seenMods.add(mod);
|
|
521
|
-
allMods.push(mod);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
// Check for any advanced state context (atRuleContext or ownMods)
|
|
526
|
-
if (state.atRuleContext ||
|
|
527
|
-
state.ownMods?.length ||
|
|
528
|
-
state.negatedOwnMods?.length) {
|
|
529
|
-
hasAdvancedStates = true;
|
|
530
|
-
if (state.atRuleContext) {
|
|
531
|
-
// Create a key for this context to deduplicate
|
|
532
|
-
const ctxKey = JSON.stringify(state.atRuleContext);
|
|
533
|
-
if (!atRuleContextsByKey.has(ctxKey)) {
|
|
534
|
-
atRuleContextsByKey.set(ctxKey, state.atRuleContext);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
// Handle case with no mods but possibly advanced states
|
|
541
|
-
if (allMods.length === 0) {
|
|
542
|
-
if (!hasAdvancedStates) {
|
|
543
|
-
// No mods, no advanced states - just call handler with default state values
|
|
544
|
-
const stateProps = {};
|
|
545
|
-
lookupStyles.forEach((style) => {
|
|
546
|
-
const states = styleStates[style];
|
|
547
|
-
if (states && states.length > 0) {
|
|
548
|
-
stateProps[style] = states[0].value;
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
const result = handler(stateProps);
|
|
552
|
-
if (!result)
|
|
553
|
-
return;
|
|
554
|
-
const logicalRules = explodeHandlerResult(result, parentSuffix);
|
|
555
|
-
allLogicalRules.push(...logicalRules);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
// Has advanced states but no mods - generate rules for each at-rule context
|
|
559
|
-
processAdvancedStatesOnly(styleStates, lookupStyles, handler, parentSuffix, allLogicalRules);
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
// Parse all mods once and share the cache across all operations
|
|
563
|
-
const parsedModsCache = new Map();
|
|
564
|
-
for (const mod of allMods) {
|
|
565
|
-
parsedModsCache.set(mod, parseAttributeSelector(getModSelector(mod)));
|
|
566
|
-
}
|
|
567
|
-
// Apply priority-based filtering for same-attribute boolean vs value conflicts
|
|
568
|
-
const filteredMods = filterModsByPriority(allMods, styleStates, lookupStyles, parsedModsCache);
|
|
569
|
-
// Precompute attribute maps once for all combinations
|
|
570
|
-
const attributeMaps = buildAttributeMaps([], filteredMods, parsedModsCache);
|
|
571
|
-
// Generate combinations with conflict-aware pruning
|
|
572
|
-
const conflictChecker = createAttributeConflictChecker(attributeMaps.parsedMods);
|
|
573
|
-
const combinations = getModCombinationsIterative(filteredMods, true, conflictChecker);
|
|
574
|
-
// Process each mod combination
|
|
575
|
-
combinations.forEach((modCombination) => {
|
|
576
|
-
const stateProps = {};
|
|
577
|
-
// Find matching state for each style
|
|
578
|
-
lookupStyles.forEach((style) => {
|
|
579
|
-
const states = styleStates[style];
|
|
580
|
-
const matchingState = states.find((state) => computeState(state.model, (mod) => modCombination.includes(mod)));
|
|
581
|
-
if (matchingState) {
|
|
582
|
-
stateProps[style] = matchingState.value;
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
// Use precomputed maps for efficient not selector optimization
|
|
586
|
-
const currentMaps = buildAttributeMaps(modCombination, filteredMods, parsedModsCache);
|
|
587
|
-
// Compute raw NOTs for contradiction check (before optimization)
|
|
588
|
-
const rawNotMods = filteredMods.filter((mod) => !modCombination.includes(mod));
|
|
589
|
-
// Check for contradictions between positive and negative selectors
|
|
590
|
-
if (hasContradiction(modCombination, rawNotMods, currentMaps.parsedMods)) {
|
|
591
|
-
return; // Skip this invalid combination
|
|
592
|
-
}
|
|
593
|
-
// Optimize NOT selectors afterwards (pure simplification)
|
|
594
|
-
const optimizedNotMods = optimizeNotSelectors(modCombination, filteredMods, currentMaps);
|
|
595
|
-
// Build the mod selector string
|
|
596
|
-
const modsSelectors = `${modCombination
|
|
597
|
-
.map(getModSelector)
|
|
598
|
-
.join('')}${optimizedNotMods
|
|
599
|
-
.map((mod) => {
|
|
600
|
-
const sel = getModSelector(mod);
|
|
601
|
-
return sel.startsWith(':not(') ? sel.slice(5, -1) : `:not(${sel})`;
|
|
602
|
-
})
|
|
603
|
-
.join('')}`;
|
|
604
|
-
// Simple non-responsive state values
|
|
605
|
-
const result = handler(stateProps);
|
|
606
|
-
if (!result)
|
|
607
|
-
return;
|
|
608
|
-
const logical = explodeHandlerResult(result, `${modsSelectors}${parentSuffix}`);
|
|
609
|
-
allLogicalRules.push(...logical);
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
function processAdvancedStatesOnly(styleStates, lookupStyles, handler, parentSuffix, allLogicalRules) {
|
|
613
|
-
// Group states by their at-rule context AND ownMods
|
|
614
|
-
const contextGroups = new Map();
|
|
615
|
-
for (const style of lookupStyles) {
|
|
616
|
-
const states = styleStates[style];
|
|
617
|
-
if (!states)
|
|
618
|
-
continue;
|
|
619
|
-
for (const state of states) {
|
|
620
|
-
// Create a key that includes both atRuleContext and ownMods
|
|
621
|
-
const ctxKey = state.atRuleContext
|
|
622
|
-
? JSON.stringify(state.atRuleContext)
|
|
623
|
-
: '';
|
|
624
|
-
const ownModsKey = state.ownMods?.join(',') || '';
|
|
625
|
-
const negatedOwnModsKey = state.negatedOwnMods?.join(',') || '';
|
|
626
|
-
const groupKey = `${ctxKey}||${ownModsKey}||${negatedOwnModsKey}`;
|
|
627
|
-
if (!contextGroups.has(groupKey)) {
|
|
628
|
-
contextGroups.set(groupKey, {
|
|
629
|
-
context: state.atRuleContext,
|
|
630
|
-
ownMods: state.ownMods,
|
|
631
|
-
negatedOwnMods: state.negatedOwnMods,
|
|
632
|
-
states: [],
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
contextGroups.get(groupKey).states.push({ style, state });
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
// Generate rules for each context group
|
|
639
|
-
for (const [, group] of contextGroups) {
|
|
640
|
-
const stateProps = {};
|
|
641
|
-
// For each style, find the value for this context
|
|
642
|
-
for (const { style, state } of group.states) {
|
|
643
|
-
stateProps[style] = state.value;
|
|
644
|
-
}
|
|
645
|
-
// Fill in missing styles with their default values
|
|
646
|
-
for (const style of lookupStyles) {
|
|
647
|
-
if (!(style in stateProps)) {
|
|
648
|
-
const states = styleStates[style];
|
|
649
|
-
if (states && states.length > 0) {
|
|
650
|
-
// Use the first state without at-rule context, or the last state
|
|
651
|
-
const defaultState = states.find((s) => !s.atRuleContext) ||
|
|
652
|
-
states[states.length - 1];
|
|
653
|
-
stateProps[style] = defaultState.value;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
const result = handler(stateProps);
|
|
658
|
-
if (!result)
|
|
659
|
-
continue;
|
|
660
|
-
const logical = explodeHandlerResult(result, parentSuffix, group.context, group.ownMods, group.negatedOwnMods);
|
|
661
|
-
allLogicalRules.push(...logical);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Convert logical rules to final StyleResult format
|
|
666
|
-
*/
|
|
667
|
-
function materializeRules(logicalRules, className) {
|
|
668
|
-
return logicalRules.map((rule) => {
|
|
669
|
-
// Generate base selector
|
|
670
|
-
let selector = `.${className}${rule.selectorSuffix}`;
|
|
671
|
-
// Increase specificity for tasty class selectors by duplicating the class
|
|
672
|
-
if (/^t\d+$/.test(className)) {
|
|
673
|
-
selector = `.${className}${selector}`;
|
|
674
|
-
}
|
|
675
|
-
// Handle @own mods by appending to the selector (after sub-element)
|
|
676
|
-
if (rule.ownMods?.length || rule.negatedOwnMods?.length) {
|
|
677
|
-
// Build own mod selectors
|
|
678
|
-
const ownModSelectors = [];
|
|
679
|
-
for (const mod of rule.ownMods || []) {
|
|
680
|
-
ownModSelectors.push(getModSelector(mod));
|
|
681
|
-
}
|
|
682
|
-
for (const mod of rule.negatedOwnMods || []) {
|
|
683
|
-
ownModSelectors.push(`:not(${getModSelector(mod)})`);
|
|
684
|
-
}
|
|
685
|
-
selector = selector + ownModSelectors.join('');
|
|
686
|
-
}
|
|
687
|
-
// Handle root states by prefixing selector
|
|
688
|
-
if (rule.atRuleContext?.rootStates?.length) {
|
|
689
|
-
const rootPrefix = rule.atRuleContext.rootStates
|
|
690
|
-
.map((rs) => `:root${rs}`)
|
|
691
|
-
.join('');
|
|
692
|
-
selector = `${rootPrefix} ${selector}`;
|
|
693
|
-
}
|
|
694
|
-
else if (rule.atRuleContext?.negatedRootStates?.length) {
|
|
695
|
-
// Handle negated root states (for non-overlapping default rules)
|
|
696
|
-
const negatedPrefix = `:root${rule.atRuleContext.negatedRootStates.join('')}`;
|
|
697
|
-
selector = `${negatedPrefix} ${selector}`;
|
|
698
|
-
}
|
|
699
|
-
const declarations = Object.entries(rule.declarations)
|
|
700
|
-
.map(([prop, value]) => `${prop}: ${value};`)
|
|
701
|
-
.join(' ');
|
|
702
|
-
const result = {
|
|
703
|
-
selector,
|
|
704
|
-
declarations,
|
|
705
|
-
};
|
|
706
|
-
// Convert atRuleContext to atRules array for the injector
|
|
707
|
-
const atRules = buildAtRulesArray(rule.atRuleContext);
|
|
708
|
-
if (atRules.length > 0) {
|
|
709
|
-
result.atRules = atRules;
|
|
710
|
-
}
|
|
711
|
-
return result;
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Build an array of at-rule strings from AtRuleContext
|
|
716
|
-
*/
|
|
717
|
-
function buildAtRulesArray(ctx) {
|
|
718
|
-
if (!ctx)
|
|
719
|
-
return [];
|
|
720
|
-
const atRules = [];
|
|
721
|
-
// Add media queries (combined with 'and')
|
|
722
|
-
if (ctx.media?.length) {
|
|
723
|
-
const mediaConditions = ctx.media.join(' and ');
|
|
724
|
-
atRules.push(`@media ${mediaConditions}`);
|
|
725
|
-
}
|
|
726
|
-
// Add container queries (nested)
|
|
727
|
-
if (ctx.container?.length) {
|
|
728
|
-
for (const c of ctx.container) {
|
|
729
|
-
const name = c.name ? `${c.name} ` : '';
|
|
730
|
-
// Don't double-wrap with parentheses if condition already has them
|
|
731
|
-
const condition = c.condition.startsWith('(') && c.condition.endsWith(')')
|
|
732
|
-
? c.condition
|
|
733
|
-
: `(${c.condition})`;
|
|
734
|
-
atRules.push(`@container ${name}${condition}`);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
// Add starting style
|
|
738
|
-
if (ctx.startingStyle) {
|
|
739
|
-
atRules.push('@starting-style');
|
|
740
|
-
}
|
|
741
|
-
return atRules;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Core style processing logic that generates logical rules
|
|
745
|
-
* Used by the unified renderStyles function
|
|
746
|
-
*/
|
|
747
|
-
function generateLogicalRules(styles, parentSuffix = '') {
|
|
748
|
-
const allLogicalRules = [];
|
|
749
|
-
// Process styles recursively, preserving mod selectors and combining with nested selector suffixes
|
|
750
|
-
function processStyles(currentStyles, parentSuffix = '') {
|
|
751
|
-
const keys = Object.keys(currentStyles || {});
|
|
752
|
-
const selectorKeys = keys.filter((key) => isSelector(key));
|
|
753
|
-
// Recurse into nested selectors first to compute proper suffix chaining
|
|
754
|
-
for (const key of selectorKeys) {
|
|
755
|
-
const suffix = getSelector(key, currentStyles[key]);
|
|
756
|
-
if (suffix && currentStyles[key]) {
|
|
757
|
-
// Remove $ key to prevent it from being processed as a style property
|
|
758
|
-
const { $: _omit, ...cleanedStyles } = currentStyles[key];
|
|
759
|
-
processStyles(cleanedStyles, `${parentSuffix}${suffix}`);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
// Build handler queue for style properties at this level
|
|
763
|
-
const handlerQueue = [];
|
|
764
|
-
const seenHandlers = new Set();
|
|
765
|
-
keys.forEach((styleName) => {
|
|
766
|
-
if (isSelector(styleName))
|
|
767
|
-
return;
|
|
768
|
-
let handlers = STYLE_HANDLER_MAP[styleName];
|
|
769
|
-
if (!handlers) {
|
|
770
|
-
handlers = STYLE_HANDLER_MAP[styleName] = [createStyle(styleName)];
|
|
771
|
-
}
|
|
772
|
-
handlers.forEach((handler) => {
|
|
773
|
-
if (seenHandlers.has(handler)) {
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
seenHandlers.add(handler);
|
|
777
|
-
const lookupStyles = handler.__lookupStyles;
|
|
778
|
-
const filteredStyleMap = lookupStyles.reduce((map, name) => {
|
|
779
|
-
const value = currentStyles?.[name];
|
|
780
|
-
if (value !== undefined) {
|
|
781
|
-
map[name] = value;
|
|
782
|
-
}
|
|
783
|
-
return map;
|
|
784
|
-
}, {});
|
|
785
|
-
handlerQueue.push({
|
|
786
|
-
handler,
|
|
787
|
-
styleMap: filteredStyleMap,
|
|
788
|
-
});
|
|
789
|
-
});
|
|
790
|
-
});
|
|
791
|
-
// Process handlers
|
|
792
|
-
handlerQueue.forEach(({ handler, styleMap }) => {
|
|
793
|
-
const lookupStyles = handler.__lookupStyles;
|
|
794
|
-
// Check if any values have state maps
|
|
795
|
-
const hasStateMaps = lookupStyles.some((style) => {
|
|
796
|
-
const value = styleMap[style];
|
|
797
|
-
return value && typeof value === 'object' && !Array.isArray(value);
|
|
798
|
-
});
|
|
799
|
-
if (hasStateMaps) {
|
|
800
|
-
// Build styleStates from styleMap
|
|
801
|
-
const styleStates = {};
|
|
802
|
-
lookupStyles.forEach((style) => {
|
|
803
|
-
const value = styleMap[style];
|
|
804
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
805
|
-
const { states } = styleStateMapToStyleStateDataList(value);
|
|
806
|
-
styleStates[style] = states;
|
|
807
|
-
}
|
|
808
|
-
else {
|
|
809
|
-
// Simple value, create a single state
|
|
810
|
-
styleStates[style] = [{ mods: [], notMods: [], value }];
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
// Use the consolidated helper for mod combination processing
|
|
814
|
-
processStateMapsWithModCombinations(styleStates, lookupStyles, handler, parentSuffix, allLogicalRules);
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
// Simple case: no state maps, call handler directly
|
|
818
|
-
const result = handler(styleMap);
|
|
819
|
-
if (result) {
|
|
820
|
-
const logical = explodeHandlerResult(result, parentSuffix);
|
|
821
|
-
allLogicalRules.push(...logical);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
// Kick off processing from the root styles with empty suffix
|
|
827
|
-
processStyles(styles, parentSuffix);
|
|
828
|
-
return allLogicalRules;
|
|
829
|
-
}
|
|
830
|
-
/**
|
|
831
|
-
* Optimize media ranges in logical rules to generate non-overlapping CSS rules.
|
|
832
|
-
* Groups rules by selector suffix and applies range optimization to media-only rules.
|
|
833
|
-
*/
|
|
834
|
-
function optimizeMediaRangesInRules(rules) {
|
|
835
|
-
// Group rules by selector suffix (ignoring atRuleContext)
|
|
836
|
-
const groups = new Map();
|
|
837
|
-
for (const rule of rules) {
|
|
838
|
-
const key = rule.selectorSuffix;
|
|
839
|
-
if (!groups.has(key)) {
|
|
840
|
-
groups.set(key, []);
|
|
841
|
-
}
|
|
842
|
-
groups.get(key).push(rule);
|
|
843
|
-
}
|
|
844
|
-
const result = [];
|
|
845
|
-
for (const [, groupRules] of groups) {
|
|
846
|
-
// Separate media-only rules from others
|
|
847
|
-
const mediaOnlyRules = [];
|
|
848
|
-
const otherRules = [];
|
|
849
|
-
for (const rule of groupRules) {
|
|
850
|
-
const ctx = rule.atRuleContext;
|
|
851
|
-
// Check if this is a media-only rule (no container, no root states, no starting style)
|
|
852
|
-
if (ctx?.media?.length &&
|
|
853
|
-
!ctx.container?.length &&
|
|
854
|
-
!ctx.rootStates?.length &&
|
|
855
|
-
!ctx.startingStyle) {
|
|
856
|
-
mediaOnlyRules.push(rule);
|
|
857
|
-
}
|
|
858
|
-
else if (!ctx?.media?.length &&
|
|
859
|
-
!ctx?.container?.length &&
|
|
860
|
-
!ctx?.rootStates?.length &&
|
|
861
|
-
!ctx?.startingStyle) {
|
|
862
|
-
// Default rule (no at-rules) - treat as "remaining" media range
|
|
863
|
-
otherRules.push(rule);
|
|
864
|
-
}
|
|
865
|
-
else {
|
|
866
|
-
// Other rules with mixed at-rule contexts
|
|
867
|
-
result.push(rule);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
// If we have media rules, try to optimize them
|
|
871
|
-
if (mediaOnlyRules.length > 0) {
|
|
872
|
-
const defaultRule = otherRules.length > 0 ? otherRules[0] : undefined;
|
|
873
|
-
const optimized = tryOptimizeMediaGroup(mediaOnlyRules, defaultRule);
|
|
874
|
-
result.push(...optimized);
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
// No media rules to optimize, just pass through
|
|
878
|
-
result.push(...otherRules);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
return result;
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Optimize container ranges in logical rules to generate non-overlapping CSS rules.
|
|
885
|
-
* Similar to media query optimization but for @container queries.
|
|
886
|
-
*/
|
|
887
|
-
function optimizeContainerRangesInRules(rules) {
|
|
888
|
-
// Group rules by selector suffix (ignoring atRuleContext)
|
|
889
|
-
const groups = new Map();
|
|
890
|
-
for (const rule of rules) {
|
|
891
|
-
const key = rule.selectorSuffix;
|
|
892
|
-
if (!groups.has(key)) {
|
|
893
|
-
groups.set(key, []);
|
|
894
|
-
}
|
|
895
|
-
groups.get(key).push(rule);
|
|
896
|
-
}
|
|
897
|
-
const result = [];
|
|
898
|
-
for (const [, groupRules] of groups) {
|
|
899
|
-
// Separate container-only rules from others
|
|
900
|
-
const containerOnlyRules = [];
|
|
901
|
-
const otherRules = [];
|
|
902
|
-
for (const rule of groupRules) {
|
|
903
|
-
const ctx = rule.atRuleContext;
|
|
904
|
-
// Check if this is a container-only rule (no media, no root states, no starting style)
|
|
905
|
-
if (ctx?.container?.length &&
|
|
906
|
-
!ctx.media?.length &&
|
|
907
|
-
!ctx.rootStates?.length &&
|
|
908
|
-
!ctx.startingStyle) {
|
|
909
|
-
containerOnlyRules.push(rule);
|
|
910
|
-
}
|
|
911
|
-
else if (!ctx?.media?.length &&
|
|
912
|
-
!ctx?.container?.length &&
|
|
913
|
-
!ctx?.rootStates?.length &&
|
|
914
|
-
!ctx?.startingStyle) {
|
|
915
|
-
// Default rule (no at-rules) - treat as "remaining" range
|
|
916
|
-
otherRules.push(rule);
|
|
917
|
-
}
|
|
918
|
-
else {
|
|
919
|
-
// Other rules with mixed at-rule contexts
|
|
920
|
-
result.push(rule);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
// If we have container rules, try to optimize them
|
|
924
|
-
if (containerOnlyRules.length > 0) {
|
|
925
|
-
const defaultRule = otherRules.length > 0 ? otherRules[0] : undefined;
|
|
926
|
-
const optimized = tryOptimizeContainerGroup(containerOnlyRules, defaultRule);
|
|
927
|
-
result.push(...optimized);
|
|
928
|
-
}
|
|
929
|
-
else {
|
|
930
|
-
// No container rules to optimize, just pass through
|
|
931
|
-
result.push(...otherRules);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
return result;
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* Check if a set of container conditions together cover all possible values.
|
|
938
|
-
* Similar to checkIfConditionsCoverAllRanges but for container queries.
|
|
939
|
-
*/
|
|
940
|
-
function checkIfContainerConditionsCoverAllRanges(containerRules) {
|
|
941
|
-
let hasUnboundedLower = false;
|
|
942
|
-
let hasUnboundedUpper = false;
|
|
943
|
-
let conditionCount = 0;
|
|
944
|
-
for (const rule of containerRules) {
|
|
945
|
-
const container = rule.atRuleContext?.container?.[0];
|
|
946
|
-
if (!container)
|
|
947
|
-
continue;
|
|
948
|
-
let conditionStr = container.condition;
|
|
949
|
-
// Strip surrounding parentheses
|
|
950
|
-
if (conditionStr.startsWith('(') && conditionStr.endsWith(')')) {
|
|
951
|
-
conditionStr = conditionStr.slice(1, -1);
|
|
952
|
-
}
|
|
953
|
-
const parsed = parseMediaCondition(conditionStr);
|
|
954
|
-
if (!parsed)
|
|
955
|
-
continue;
|
|
956
|
-
conditionCount++;
|
|
957
|
-
if (parsed.type === 'simple') {
|
|
958
|
-
const cond = parsed.condition;
|
|
959
|
-
if ((cond.operator === '<' || cond.operator === '<=') &&
|
|
960
|
-
cond.valueNumeric !== null) {
|
|
961
|
-
hasUnboundedLower = true;
|
|
962
|
-
}
|
|
963
|
-
else if ((cond.operator === '>' || cond.operator === '>=') &&
|
|
964
|
-
cond.valueNumeric !== null) {
|
|
965
|
-
hasUnboundedUpper = true;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
else if (parsed.type === 'range') {
|
|
969
|
-
// Range conditions don't contribute to unbounded ends
|
|
970
|
-
// but do count towards condition count
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
// If we have both ends covered and enough conditions, assume full coverage
|
|
974
|
-
if (hasUnboundedLower && hasUnboundedUpper && conditionCount >= 3) {
|
|
975
|
-
return true;
|
|
976
|
-
}
|
|
977
|
-
return false;
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Try to optimize a group of container-only rules with an optional default rule.
|
|
981
|
-
* Returns optimized rules with non-overlapping container queries.
|
|
982
|
-
*/
|
|
983
|
-
function tryOptimizeContainerGroup(containerRules, defaultRule) {
|
|
984
|
-
// Parse container conditions and separate by type
|
|
985
|
-
const parsedSimple = [];
|
|
986
|
-
const otherRules = [];
|
|
987
|
-
for (const rule of containerRules) {
|
|
988
|
-
const container = rule.atRuleContext?.container?.[0];
|
|
989
|
-
if (!container) {
|
|
990
|
-
otherRules.push(rule);
|
|
991
|
-
continue;
|
|
992
|
-
}
|
|
993
|
-
let conditionStr = container.condition;
|
|
994
|
-
// Strip surrounding parentheses if present
|
|
995
|
-
if (conditionStr.startsWith('(') && conditionStr.endsWith(')')) {
|
|
996
|
-
conditionStr = conditionStr.slice(1, -1);
|
|
997
|
-
}
|
|
998
|
-
const parsed = parseMediaCondition(conditionStr);
|
|
999
|
-
if (parsed && parsed.type === 'simple') {
|
|
1000
|
-
parsedSimple.push({
|
|
1001
|
-
rule,
|
|
1002
|
-
condition: parsed.condition,
|
|
1003
|
-
containerName: container.name,
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
else {
|
|
1007
|
-
// Range or unparseable - pass through
|
|
1008
|
-
otherRules.push(rule);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
// Check if all simple conditions are on the same dimension and same container name
|
|
1012
|
-
// AND there are no other rules (ranges)
|
|
1013
|
-
if (parsedSimple.length > 0 && otherRules.length === 0) {
|
|
1014
|
-
const dimension = parsedSimple[0].condition.dimension;
|
|
1015
|
-
const containerName = parsedSimple[0].containerName;
|
|
1016
|
-
const allSameDimensionAndName = parsedSimple.every((c) => c.condition.dimension === dimension &&
|
|
1017
|
-
c.containerName === containerName);
|
|
1018
|
-
if (allSameDimensionAndName) {
|
|
1019
|
-
// All conditions are simple conditions on the same dimension
|
|
1020
|
-
// Build a map for computeNonOverlappingRanges
|
|
1021
|
-
const mediaConditionsMap = new Map();
|
|
1022
|
-
const rulesByKey = new Map();
|
|
1023
|
-
for (let i = 0; i < parsedSimple.length; i++) {
|
|
1024
|
-
const key = `rule_${i}`;
|
|
1025
|
-
mediaConditionsMap.set(key, parsedSimple[i].condition);
|
|
1026
|
-
rulesByKey.set(key, parsedSimple[i].rule);
|
|
1027
|
-
}
|
|
1028
|
-
// Compute non-overlapping ranges
|
|
1029
|
-
const defaultKey = defaultRule ? 'default' : null;
|
|
1030
|
-
const optimizedRanges = computeNonOverlappingRanges(mediaConditionsMap, defaultKey);
|
|
1031
|
-
// If optimization succeeded
|
|
1032
|
-
if (optimizedRanges.length > 0) {
|
|
1033
|
-
const result = [];
|
|
1034
|
-
for (const range of optimizedRanges) {
|
|
1035
|
-
const rangeCondition = rangeToMediaCondition(range, dimension);
|
|
1036
|
-
const sourceRule = range.stateKey === 'default'
|
|
1037
|
-
? defaultRule
|
|
1038
|
-
: rulesByKey.get(range.stateKey);
|
|
1039
|
-
if (sourceRule) {
|
|
1040
|
-
result.push({
|
|
1041
|
-
selectorSuffix: sourceRule.selectorSuffix,
|
|
1042
|
-
declarations: { ...sourceRule.declarations },
|
|
1043
|
-
ownMods: sourceRule.ownMods,
|
|
1044
|
-
negatedOwnMods: sourceRule.negatedOwnMods,
|
|
1045
|
-
atRuleContext: {
|
|
1046
|
-
container: [{ name: containerName, condition: rangeCondition }],
|
|
1047
|
-
},
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
// For non-optimizable conditions, use NOT negation for the default
|
|
1056
|
-
// But first check if the explicit conditions already cover all ranges
|
|
1057
|
-
if (containerRules.length > 0 && defaultRule) {
|
|
1058
|
-
// Check if explicit conditions cover all possible ranges
|
|
1059
|
-
const coversAllRanges = checkIfContainerConditionsCoverAllRanges(containerRules);
|
|
1060
|
-
if (coversAllRanges) {
|
|
1061
|
-
// No need for default rule - explicit conditions cover everything
|
|
1062
|
-
return [...containerRules];
|
|
1063
|
-
}
|
|
1064
|
-
const result = [...containerRules];
|
|
1065
|
-
// Create negated conditions for the default rule
|
|
1066
|
-
const negations = [];
|
|
1067
|
-
for (const rule of containerRules) {
|
|
1068
|
-
const container = rule.atRuleContext?.container?.[0];
|
|
1069
|
-
if (!container)
|
|
1070
|
-
continue;
|
|
1071
|
-
// Use 'not (...)' for all conditions
|
|
1072
|
-
const cond = container.condition;
|
|
1073
|
-
negations.push(`not (${cond})`);
|
|
1074
|
-
}
|
|
1075
|
-
const combinedNegation = negations.filter(Boolean).join(' and ');
|
|
1076
|
-
if (combinedNegation) {
|
|
1077
|
-
// Get the container name from the first rule (they should all be the same)
|
|
1078
|
-
const containerName = containerRules[0].atRuleContext?.container?.[0]?.name;
|
|
1079
|
-
result.push({
|
|
1080
|
-
...defaultRule,
|
|
1081
|
-
atRuleContext: {
|
|
1082
|
-
container: [{ name: containerName, condition: combinedNegation }],
|
|
1083
|
-
},
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
return result;
|
|
1087
|
-
}
|
|
1088
|
-
// No optimization possible, return as-is
|
|
1089
|
-
return [
|
|
1090
|
-
...containerRules,
|
|
1091
|
-
...otherRules,
|
|
1092
|
-
...(defaultRule ? [defaultRule] : []),
|
|
1093
|
-
];
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* Check if a set of media conditions together cover all possible values.
|
|
1097
|
-
* If so, there's no need to generate a default rule.
|
|
1098
|
-
*/
|
|
1099
|
-
function checkIfConditionsCoverAllRanges(mediaRules) {
|
|
1100
|
-
// Parse all conditions
|
|
1101
|
-
const conditions = [];
|
|
1102
|
-
let hasUnboundedLower = false; // e.g., width < 768px (covers 0 to 768)
|
|
1103
|
-
let hasUnboundedUpper = false; // e.g., width >= 1024px (covers 1024 to ∞)
|
|
1104
|
-
for (const rule of mediaRules) {
|
|
1105
|
-
let mediaStr = rule.atRuleContext?.media?.[0];
|
|
1106
|
-
if (!mediaStr)
|
|
1107
|
-
continue;
|
|
1108
|
-
// Strip surrounding parentheses
|
|
1109
|
-
if (mediaStr.startsWith('(') && mediaStr.endsWith(')')) {
|
|
1110
|
-
mediaStr = mediaStr.slice(1, -1);
|
|
1111
|
-
}
|
|
1112
|
-
const parsed = parseMediaCondition(mediaStr);
|
|
1113
|
-
if (!parsed)
|
|
1114
|
-
continue;
|
|
1115
|
-
if (parsed.type === 'simple') {
|
|
1116
|
-
const cond = parsed.condition;
|
|
1117
|
-
if ((cond.operator === '<' || cond.operator === '<=') &&
|
|
1118
|
-
cond.valueNumeric !== null) {
|
|
1119
|
-
// Upper bound only: width < 768px covers 0 to 768
|
|
1120
|
-
hasUnboundedLower = true;
|
|
1121
|
-
conditions.push({
|
|
1122
|
-
hasLowerBound: false,
|
|
1123
|
-
hasUpperBound: true,
|
|
1124
|
-
upperValue: cond.valueNumeric,
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
else if ((cond.operator === '>' || cond.operator === '>=') &&
|
|
1128
|
-
cond.valueNumeric !== null) {
|
|
1129
|
-
// Lower bound only: width >= 1024px covers 1024 to ∞
|
|
1130
|
-
hasUnboundedUpper = true;
|
|
1131
|
-
conditions.push({
|
|
1132
|
-
hasLowerBound: true,
|
|
1133
|
-
hasUpperBound: false,
|
|
1134
|
-
lowerValue: cond.valueNumeric,
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
else if (parsed.type === 'range') {
|
|
1139
|
-
const cond = parsed.condition;
|
|
1140
|
-
if (cond.lower.valueNumeric !== null &&
|
|
1141
|
-
cond.upper.valueNumeric !== null) {
|
|
1142
|
-
conditions.push({
|
|
1143
|
-
hasLowerBound: true,
|
|
1144
|
-
hasUpperBound: true,
|
|
1145
|
-
lowerValue: cond.lower.valueNumeric,
|
|
1146
|
-
upperValue: cond.upper.valueNumeric,
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
// If we have both an unbounded lower (covers 0 to X) and unbounded upper (covers Y to ∞),
|
|
1152
|
-
// and intermediate ranges fill the gap, then all values are covered.
|
|
1153
|
-
if (hasUnboundedLower && hasUnboundedUpper && conditions.length >= 2) {
|
|
1154
|
-
// Simple heuristic: if we have 3+ conditions with both unbounded ends,
|
|
1155
|
-
// assume they cover all values
|
|
1156
|
-
// A more rigorous check would verify no gaps exist
|
|
1157
|
-
return conditions.length >= 3;
|
|
1158
|
-
}
|
|
1159
|
-
return false;
|
|
1160
|
-
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Try to optimize a group of media-only rules with an optional default rule.
|
|
1163
|
-
* Returns optimized rules with non-overlapping media queries.
|
|
1164
|
-
*/
|
|
1165
|
-
function tryOptimizeMediaGroup(mediaRules, defaultRule) {
|
|
1166
|
-
// Parse media conditions and separate by type
|
|
1167
|
-
const parsedSimple = [];
|
|
1168
|
-
const otherRules = [];
|
|
1169
|
-
for (const rule of mediaRules) {
|
|
1170
|
-
let mediaStr = rule.atRuleContext?.media?.[0];
|
|
1171
|
-
if (!mediaStr) {
|
|
1172
|
-
otherRules.push(rule);
|
|
1173
|
-
continue;
|
|
1174
|
-
}
|
|
1175
|
-
// Strip surrounding parentheses if present (buildAtRuleContext adds them)
|
|
1176
|
-
if (mediaStr.startsWith('(') && mediaStr.endsWith(')')) {
|
|
1177
|
-
mediaStr = mediaStr.slice(1, -1);
|
|
1178
|
-
}
|
|
1179
|
-
const parsed = parseMediaCondition(mediaStr);
|
|
1180
|
-
if (parsed && parsed.type === 'simple') {
|
|
1181
|
-
parsedSimple.push({ rule, condition: parsed.condition });
|
|
1182
|
-
}
|
|
1183
|
-
else {
|
|
1184
|
-
// Range or unparseable - pass through
|
|
1185
|
-
otherRules.push(rule);
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
// Check if all simple conditions are on the same dimension AND there are no other rules (ranges)
|
|
1189
|
-
// We can only use the range optimizer when ALL conditions are simple (no pre-existing ranges)
|
|
1190
|
-
if (parsedSimple.length > 0 && otherRules.length === 0) {
|
|
1191
|
-
const dimension = parsedSimple[0].condition.dimension;
|
|
1192
|
-
const allSameDimension = parsedSimple.every((c) => c.condition.dimension === dimension);
|
|
1193
|
-
if (allSameDimension) {
|
|
1194
|
-
// All conditions are simple conditions on the same dimension
|
|
1195
|
-
// Build a map for computeNonOverlappingRanges
|
|
1196
|
-
const mediaConditionsMap = new Map();
|
|
1197
|
-
const rulesByKey = new Map();
|
|
1198
|
-
for (let i = 0; i < parsedSimple.length; i++) {
|
|
1199
|
-
const key = `rule_${i}`;
|
|
1200
|
-
mediaConditionsMap.set(key, parsedSimple[i].condition);
|
|
1201
|
-
rulesByKey.set(key, parsedSimple[i].rule);
|
|
1202
|
-
}
|
|
1203
|
-
// Compute non-overlapping ranges
|
|
1204
|
-
const defaultKey = defaultRule ? 'default' : null;
|
|
1205
|
-
const optimizedRanges = computeNonOverlappingRanges(mediaConditionsMap, defaultKey);
|
|
1206
|
-
// If optimization succeeded
|
|
1207
|
-
if (optimizedRanges.length > 0) {
|
|
1208
|
-
const result = [];
|
|
1209
|
-
for (const range of optimizedRanges) {
|
|
1210
|
-
const mediaCondition = rangeToMediaCondition(range, dimension);
|
|
1211
|
-
const isFromDefault = range.stateKey === 'default';
|
|
1212
|
-
const sourceRule = isFromDefault
|
|
1213
|
-
? defaultRule
|
|
1214
|
-
: rulesByKey.get(range.stateKey);
|
|
1215
|
-
if (sourceRule) {
|
|
1216
|
-
result.push({
|
|
1217
|
-
selectorSuffix: sourceRule.selectorSuffix,
|
|
1218
|
-
declarations: { ...sourceRule.declarations },
|
|
1219
|
-
ownMods: sourceRule.ownMods,
|
|
1220
|
-
negatedOwnMods: sourceRule.negatedOwnMods,
|
|
1221
|
-
atRuleContext: { media: [mediaCondition] },
|
|
1222
|
-
// Mark explicit media rules (not from default) - they should not be split by root states
|
|
1223
|
-
isExplicitMedia: !isFromDefault,
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
return result;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
// For non-optimizable conditions, use NOT negation for the default
|
|
1232
|
-
// But first check if the explicit conditions already cover all ranges
|
|
1233
|
-
if (mediaRules.length > 0 && defaultRule) {
|
|
1234
|
-
// Check if explicit conditions cover all possible ranges
|
|
1235
|
-
const coversAllRanges = checkIfConditionsCoverAllRanges(mediaRules);
|
|
1236
|
-
// Mark all explicit media rules
|
|
1237
|
-
const markedMediaRules = mediaRules.map((rule) => ({
|
|
1238
|
-
...rule,
|
|
1239
|
-
isExplicitMedia: true,
|
|
1240
|
-
}));
|
|
1241
|
-
if (coversAllRanges) {
|
|
1242
|
-
// No need for default rule - explicit conditions cover everything
|
|
1243
|
-
return markedMediaRules;
|
|
1244
|
-
}
|
|
1245
|
-
const result = [...markedMediaRules];
|
|
1246
|
-
// Create negated conditions for the default rule
|
|
1247
|
-
const negations = [];
|
|
1248
|
-
for (const rule of mediaRules) {
|
|
1249
|
-
const cond = rule.atRuleContext?.media?.[0];
|
|
1250
|
-
if (!cond)
|
|
1251
|
-
continue;
|
|
1252
|
-
// Use 'not (...)' for all conditions
|
|
1253
|
-
negations.push(`not (${cond})`);
|
|
1254
|
-
}
|
|
1255
|
-
const combinedNegation = negations.filter(Boolean).join(' and ');
|
|
1256
|
-
if (combinedNegation) {
|
|
1257
|
-
result.push({
|
|
1258
|
-
...defaultRule,
|
|
1259
|
-
atRuleContext: { media: [combinedNegation] },
|
|
1260
|
-
// Default-derived rule, can be split by root states
|
|
1261
|
-
isExplicitMedia: false,
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
|
-
return result;
|
|
1265
|
-
}
|
|
1266
|
-
// No optimization possible, return as-is
|
|
1267
|
-
// Mark media rules as explicit
|
|
1268
|
-
const markedMediaRules = mediaRules.map((rule) => ({
|
|
1269
|
-
...rule,
|
|
1270
|
-
isExplicitMedia: true,
|
|
1271
|
-
}));
|
|
1272
|
-
return [
|
|
1273
|
-
...markedMediaRules,
|
|
1274
|
-
...otherRules,
|
|
1275
|
-
...(defaultRule ? [defaultRule] : []),
|
|
1276
|
-
];
|
|
1277
|
-
}
|
|
1278
|
-
/**
|
|
1279
|
-
* Optimize root states in logical rules to generate non-overlapping CSS rules.
|
|
1280
|
-
* For default rules, adds negated root states to make them non-overlapping with explicit root rules.
|
|
1281
|
-
*/
|
|
1282
|
-
function optimizeRootStatesInRules(rules) {
|
|
1283
|
-
// Group rules by selector suffix and media context
|
|
1284
|
-
const groups = new Map();
|
|
1285
|
-
for (const rule of rules) {
|
|
1286
|
-
const mediaKey = rule.atRuleContext?.media?.join(',') || '';
|
|
1287
|
-
const key = `${rule.selectorSuffix}||${mediaKey}`;
|
|
1288
|
-
if (!groups.has(key)) {
|
|
1289
|
-
groups.set(key, []);
|
|
1290
|
-
}
|
|
1291
|
-
groups.get(key).push(rule);
|
|
1292
|
-
}
|
|
1293
|
-
const result = [];
|
|
1294
|
-
for (const [, groupRules] of groups) {
|
|
1295
|
-
// Separate root state rules from default rules
|
|
1296
|
-
const rootStateRules = [];
|
|
1297
|
-
const defaultRules = [];
|
|
1298
|
-
for (const rule of groupRules) {
|
|
1299
|
-
if (rule.atRuleContext?.rootStates?.length) {
|
|
1300
|
-
rootStateRules.push(rule);
|
|
1301
|
-
}
|
|
1302
|
-
else if (!rule.atRuleContext?.container?.length &&
|
|
1303
|
-
!rule.atRuleContext?.startingStyle) {
|
|
1304
|
-
// Default rule (may have media but no root/container/starting)
|
|
1305
|
-
defaultRules.push(rule);
|
|
1306
|
-
}
|
|
1307
|
-
else {
|
|
1308
|
-
result.push(rule);
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
// Collect all unique root state selectors
|
|
1312
|
-
const allRootSelectors = new Set();
|
|
1313
|
-
for (const rule of rootStateRules) {
|
|
1314
|
-
for (const rs of rule.atRuleContext?.rootStates || []) {
|
|
1315
|
-
allRootSelectors.add(rs);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
// Add root state rules as-is
|
|
1319
|
-
result.push(...rootStateRules);
|
|
1320
|
-
// For default rules, add negated root states
|
|
1321
|
-
for (const defaultRule of defaultRules) {
|
|
1322
|
-
if (allRootSelectors.size > 0) {
|
|
1323
|
-
const negatedRootStates = Array.from(allRootSelectors).map((rs) => `:not(${rs})`);
|
|
1324
|
-
result.push({
|
|
1325
|
-
...defaultRule,
|
|
1326
|
-
atRuleContext: {
|
|
1327
|
-
...defaultRule.atRuleContext,
|
|
1328
|
-
negatedRootStates,
|
|
1329
|
-
},
|
|
1330
|
-
});
|
|
1331
|
-
}
|
|
1332
|
-
else {
|
|
1333
|
-
result.push(defaultRule);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
return result;
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* Cross-multiply media queries with root states to avoid overlapping.
|
|
1341
|
-
* When we have both:
|
|
1342
|
-
* - Media rules: @media (width < 600px) { ... }
|
|
1343
|
-
* - Root rules: :root[data-theme="dark"] { ... }
|
|
1344
|
-
* They overlap! This function wraps root-only rules with the same media ranges
|
|
1345
|
-
* to make them non-overlapping.
|
|
1346
|
-
*
|
|
1347
|
-
* IMPORTANT: Only default-derived media rules (isExplicitMedia=false) should be
|
|
1348
|
-
* split by root states. Explicit @media() rules apply to all themes.
|
|
1349
|
-
*/
|
|
1350
|
-
function crossMultiplyMediaAndRootStates(rules) {
|
|
1351
|
-
// Group rules by selectorSuffix
|
|
1352
|
-
const groups = new Map();
|
|
1353
|
-
for (const rule of rules) {
|
|
1354
|
-
const key = rule.selectorSuffix;
|
|
1355
|
-
if (!groups.has(key)) {
|
|
1356
|
-
groups.set(key, []);
|
|
1357
|
-
}
|
|
1358
|
-
groups.get(key).push(rule);
|
|
1359
|
-
}
|
|
1360
|
-
const result = [];
|
|
1361
|
-
for (const [, groupRules] of groups) {
|
|
1362
|
-
// Categorize rules
|
|
1363
|
-
const explicitMediaRules = []; // Explicit @media rules - apply to all themes
|
|
1364
|
-
const defaultDerivedMediaRules = []; // Default-derived media rules - can be split by root
|
|
1365
|
-
const rootOnlyRules = []; // Has root, no media
|
|
1366
|
-
const combinedRules = []; // Has both
|
|
1367
|
-
const otherRules = []; // Neither (or has container/starting)
|
|
1368
|
-
for (const rule of groupRules) {
|
|
1369
|
-
const hasMedia = !!rule.atRuleContext?.media?.length;
|
|
1370
|
-
const hasRoot = !!rule.atRuleContext?.rootStates?.length;
|
|
1371
|
-
const hasContainer = !!rule.atRuleContext?.container?.length;
|
|
1372
|
-
const hasStarting = !!rule.atRuleContext?.startingStyle;
|
|
1373
|
-
if (hasContainer || hasStarting) {
|
|
1374
|
-
// Don't touch container or starting rules
|
|
1375
|
-
otherRules.push(rule);
|
|
1376
|
-
}
|
|
1377
|
-
else if (hasMedia && hasRoot) {
|
|
1378
|
-
combinedRules.push(rule);
|
|
1379
|
-
}
|
|
1380
|
-
else if (hasMedia && !hasRoot) {
|
|
1381
|
-
// Separate explicit vs default-derived media rules
|
|
1382
|
-
if (rule.isExplicitMedia) {
|
|
1383
|
-
explicitMediaRules.push(rule);
|
|
1384
|
-
}
|
|
1385
|
-
else {
|
|
1386
|
-
defaultDerivedMediaRules.push(rule);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
else if (hasRoot && !hasMedia) {
|
|
1390
|
-
rootOnlyRules.push(rule);
|
|
1391
|
-
}
|
|
1392
|
-
else {
|
|
1393
|
-
otherRules.push(rule);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
// Explicit media rules go straight through - they apply to ALL themes
|
|
1397
|
-
result.push(...explicitMediaRules);
|
|
1398
|
-
// If we have both default-derived media rules and root-only rules, cross-multiply
|
|
1399
|
-
if (defaultDerivedMediaRules.length > 0 && rootOnlyRules.length > 0) {
|
|
1400
|
-
// Collect all unique media conditions from DEFAULT-DERIVED rules only
|
|
1401
|
-
const defaultDerivedMediaConditions = new Set();
|
|
1402
|
-
for (const rule of defaultDerivedMediaRules) {
|
|
1403
|
-
for (const mc of rule.atRuleContext?.media || []) {
|
|
1404
|
-
defaultDerivedMediaConditions.add(mc);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
// For each root-only rule, create copies for each DEFAULT-DERIVED media range
|
|
1408
|
-
for (const rootRule of rootOnlyRules) {
|
|
1409
|
-
for (const mediaCondition of defaultDerivedMediaConditions) {
|
|
1410
|
-
result.push({
|
|
1411
|
-
...rootRule,
|
|
1412
|
-
atRuleContext: {
|
|
1413
|
-
...rootRule.atRuleContext,
|
|
1414
|
-
media: [mediaCondition],
|
|
1415
|
-
},
|
|
1416
|
-
});
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
// Keep default-derived media rules - they will get root negation in the next step
|
|
1420
|
-
result.push(...defaultDerivedMediaRules);
|
|
1421
|
-
}
|
|
1422
|
-
else {
|
|
1423
|
-
// No cross-multiplication needed, pass through
|
|
1424
|
-
result.push(...defaultDerivedMediaRules);
|
|
1425
|
-
result.push(...rootOnlyRules);
|
|
1426
|
-
}
|
|
1427
|
-
// Pass through combined and other rules
|
|
1428
|
-
result.push(...combinedRules);
|
|
1429
|
-
result.push(...otherRules);
|
|
1430
|
-
}
|
|
1431
|
-
return result;
|
|
1432
|
-
}
|
|
1433
|
-
/**
|
|
1434
|
-
* Optimize @own mods in logical rules.
|
|
1435
|
-
* For default rules with no ownMods, adds negated ownMods to make them non-overlapping.
|
|
1436
|
-
*/
|
|
1437
|
-
function optimizeOwnModsInRules(rules) {
|
|
1438
|
-
// Group rules by selector suffix (for sub-element context)
|
|
1439
|
-
const groups = new Map();
|
|
1440
|
-
for (const rule of rules) {
|
|
1441
|
-
const key = `${rule.selectorSuffix}||${JSON.stringify(rule.atRuleContext || {})}`;
|
|
1442
|
-
if (!groups.has(key)) {
|
|
1443
|
-
groups.set(key, []);
|
|
1444
|
-
}
|
|
1445
|
-
groups.get(key).push(rule);
|
|
1446
|
-
}
|
|
1447
|
-
const result = [];
|
|
1448
|
-
for (const [, groupRules] of groups) {
|
|
1449
|
-
// Separate rules with ownMods from default rules
|
|
1450
|
-
const ownModRules = [];
|
|
1451
|
-
const defaultRules = [];
|
|
1452
|
-
for (const rule of groupRules) {
|
|
1453
|
-
if (rule.ownMods?.length) {
|
|
1454
|
-
ownModRules.push(rule);
|
|
1455
|
-
}
|
|
1456
|
-
else if (!rule.negatedOwnMods?.length) {
|
|
1457
|
-
defaultRules.push(rule);
|
|
1458
|
-
}
|
|
1459
|
-
else {
|
|
1460
|
-
result.push(rule);
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
// Collect all unique ownMods
|
|
1464
|
-
const allOwnMods = new Set();
|
|
1465
|
-
for (const rule of ownModRules) {
|
|
1466
|
-
for (const mod of rule.ownMods || []) {
|
|
1467
|
-
allOwnMods.add(mod);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
// Add ownMod rules as-is
|
|
1471
|
-
result.push(...ownModRules);
|
|
1472
|
-
// For default rules, add negated ownMods
|
|
1473
|
-
for (const defaultRule of defaultRules) {
|
|
1474
|
-
if (allOwnMods.size > 0) {
|
|
1475
|
-
result.push({
|
|
1476
|
-
...defaultRule,
|
|
1477
|
-
negatedOwnMods: Array.from(allOwnMods),
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
else {
|
|
1481
|
-
result.push(defaultRule);
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
return result;
|
|
1486
|
-
}
|
|
1487
|
-
export function renderStyles(styles, classNameOrSelector) {
|
|
1488
|
-
const directSelector = !!classNameOrSelector;
|
|
1489
|
-
if (!styles) {
|
|
1490
|
-
return directSelector ? [] : { rules: [] };
|
|
1491
|
-
}
|
|
1492
|
-
// Generate logical rules using shared pipeline (memoized per styles)
|
|
1493
|
-
const stylesKey = stringifyStyles(styles);
|
|
1494
|
-
let allLogicalRules = logicalRulesCache.get(stylesKey);
|
|
1495
|
-
if (!allLogicalRules) {
|
|
1496
|
-
let rules = generateLogicalRules(styles);
|
|
1497
|
-
// Apply optimizations in order: media ranges -> container ranges -> cross-multiply -> root states -> own mods
|
|
1498
|
-
rules = optimizeMediaRangesInRules(rules);
|
|
1499
|
-
rules = optimizeContainerRangesInRules(rules);
|
|
1500
|
-
rules = crossMultiplyMediaAndRootStates(rules);
|
|
1501
|
-
rules = optimizeRootStatesInRules(rules);
|
|
1502
|
-
rules = optimizeOwnModsInRules(rules);
|
|
1503
|
-
allLogicalRules = rules;
|
|
1504
|
-
logicalRulesCache.set(stylesKey, allLogicalRules);
|
|
1505
|
-
}
|
|
1506
|
-
if (directSelector) {
|
|
1507
|
-
// Direct selector mode: convert logical rules directly to StyleResult format
|
|
1508
|
-
if (!classNameOrSelector) {
|
|
1509
|
-
throw new Error('directSelector mode requires classNameOrSelector');
|
|
1510
|
-
}
|
|
1511
|
-
return allLogicalRules.map((rule) => {
|
|
1512
|
-
// Replace & with the actual selector or append suffix to selector
|
|
1513
|
-
let finalSelector = rule.selectorSuffix
|
|
1514
|
-
? `${classNameOrSelector}${rule.selectorSuffix}`
|
|
1515
|
-
: classNameOrSelector;
|
|
1516
|
-
// Increase specificity for tasty class selectors by duplicating the class
|
|
1517
|
-
if (finalSelector.startsWith('.') && /^\.t\d+/.test(finalSelector)) {
|
|
1518
|
-
const classMatch = finalSelector.match(/^\.t\d+/);
|
|
1519
|
-
if (classMatch) {
|
|
1520
|
-
const baseClass = classMatch[0];
|
|
1521
|
-
finalSelector = baseClass + finalSelector;
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
const declarations = Object.entries(rule.declarations)
|
|
1525
|
-
.map(([prop, value]) => `${prop}: ${value};`)
|
|
1526
|
-
.join(' ');
|
|
1527
|
-
return {
|
|
1528
|
-
selector: finalSelector,
|
|
1529
|
-
declarations,
|
|
1530
|
-
};
|
|
1531
|
-
});
|
|
1532
|
-
}
|
|
1533
|
-
// Standard mode: use accumulation and materialization with className
|
|
1534
|
-
const accumulatedRules = new Map();
|
|
1535
|
-
for (const rule of allLogicalRules) {
|
|
1536
|
-
// Create a key based on selectorSuffix AND atRuleContext (to avoid merging rules with different at-rules)
|
|
1537
|
-
const atRuleKey = rule.atRuleContext
|
|
1538
|
-
? JSON.stringify(rule.atRuleContext)
|
|
1539
|
-
: '';
|
|
1540
|
-
const ownModsKey = rule.ownMods?.join(',') || '';
|
|
1541
|
-
const negatedOwnModsKey = rule.negatedOwnMods?.join(',') || '';
|
|
1542
|
-
const ruleKey = `${rule.selectorSuffix}||${atRuleKey}||${ownModsKey}||${negatedOwnModsKey}`;
|
|
1543
|
-
const existing = accumulatedRules.get(ruleKey);
|
|
1544
|
-
if (existing) {
|
|
1545
|
-
// Merge declarations from this rule into the existing one
|
|
1546
|
-
Object.assign(existing.declarations, rule.declarations);
|
|
1547
|
-
}
|
|
1548
|
-
else {
|
|
1549
|
-
// Create a new accumulated rule - preserve atRuleContext, ownMods, negatedOwnMods
|
|
1550
|
-
accumulatedRules.set(ruleKey, {
|
|
1551
|
-
selectorSuffix: rule.selectorSuffix,
|
|
1552
|
-
declarations: { ...rule.declarations },
|
|
1553
|
-
atRuleContext: rule.atRuleContext,
|
|
1554
|
-
ownMods: rule.ownMods,
|
|
1555
|
-
negatedOwnMods: rule.negatedOwnMods,
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
// If no className provided, return rules with needsClassName flag
|
|
1560
|
-
if (!classNameOrSelector) {
|
|
1561
|
-
const materializedRules = Array.from(accumulatedRules.values()).map((rule) => {
|
|
1562
|
-
const declarations = Object.entries(rule.declarations)
|
|
1563
|
-
.map(([prop, value]) => `${prop}: ${value};`)
|
|
1564
|
-
.join(' ');
|
|
1565
|
-
// Build selector with @own mods if present
|
|
1566
|
-
let selector = rule.selectorSuffix || '';
|
|
1567
|
-
if (rule.ownMods?.length || rule.negatedOwnMods?.length) {
|
|
1568
|
-
const ownModSelectors = [];
|
|
1569
|
-
for (const mod of rule.ownMods || []) {
|
|
1570
|
-
ownModSelectors.push(getModSelector(mod));
|
|
1571
|
-
}
|
|
1572
|
-
for (const mod of rule.negatedOwnMods || []) {
|
|
1573
|
-
ownModSelectors.push(`:not(${getModSelector(mod)})`);
|
|
1574
|
-
}
|
|
1575
|
-
selector = selector + ownModSelectors.join('');
|
|
1576
|
-
}
|
|
1577
|
-
// Compute rootPrefix for root states
|
|
1578
|
-
let rootPrefix;
|
|
1579
|
-
if (rule.atRuleContext?.rootStates?.length) {
|
|
1580
|
-
rootPrefix = rule.atRuleContext.rootStates
|
|
1581
|
-
.map((rs) => `:root${rs}`)
|
|
1582
|
-
.join('');
|
|
1583
|
-
}
|
|
1584
|
-
else if (rule.atRuleContext?.negatedRootStates?.length) {
|
|
1585
|
-
rootPrefix = `:root${rule.atRuleContext.negatedRootStates.join('')}`;
|
|
1586
|
-
}
|
|
1587
|
-
// Build at-rules array
|
|
1588
|
-
const atRules = buildAtRulesArray(rule.atRuleContext);
|
|
1589
|
-
const result = {
|
|
1590
|
-
selector,
|
|
1591
|
-
declarations,
|
|
1592
|
-
needsClassName: true, // Flag for injector to prepend className
|
|
1593
|
-
};
|
|
1594
|
-
if (rootPrefix) {
|
|
1595
|
-
result.rootPrefix = rootPrefix;
|
|
1596
|
-
}
|
|
1597
|
-
if (atRules.length > 0) {
|
|
1598
|
-
result.atRules = atRules;
|
|
1599
|
-
}
|
|
1600
|
-
return result;
|
|
1601
|
-
});
|
|
1602
|
-
return {
|
|
1603
|
-
rules: materializedRules,
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
// Materialize the accumulated logical rules into final format
|
|
1607
|
-
const finalRulesRaw = materializeRules(Array.from(accumulatedRules.values()), classNameOrSelector);
|
|
1608
|
-
// Simplified deduplication (should be much less work now)
|
|
1609
|
-
const seen = new Set();
|
|
1610
|
-
const finalRules = finalRulesRaw.filter((rule) => {
|
|
1611
|
-
const at = rule.atRules && rule.atRules.length ? `@${rule.atRules.join('|')}` : '';
|
|
1612
|
-
const key = `${rule.selector}|${rule.declarations}|${at}`;
|
|
1613
|
-
if (seen.has(key))
|
|
1614
|
-
return false;
|
|
1615
|
-
seen.add(key);
|
|
1616
|
-
return true;
|
|
1617
|
-
});
|
|
1618
|
-
return {
|
|
1619
|
-
rules: finalRules,
|
|
1620
|
-
className: classNameOrSelector,
|
|
1621
|
-
};
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
|