From 4c2fca87c8a94445226aedf44cbb01473ef99ac1 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Thu, 7 May 2026 13:25:10 +0800 Subject: [PATCH 01/18] feat: add elementor integration --- src/editor/app.js | 400 +++++++++++++++ .../add-interaction-popover/index.js | 27 +- .../components/import-export-modal/index.js | 13 +- src/editor/components/location-rules/index.js | 13 +- .../components/target-selector/index.js | 38 +- .../components/timeline/class-runner.js | 8 +- src/editor/components/timeline/runner.js | 3 +- .../timeline/use-initial-style-tag.js | 11 +- src/editor/editor.js | 454 +----------------- src/editor/editor.php | 34 +- src/editor/editor.scss | 62 ++- src/editor/editors/abstract.js | 87 ++++ src/editor/editors/elementor.js | 282 +++++++++++ src/editor/editors/gutenberg.js | 129 +++++ src/editor/editors/index.js | 42 ++ src/editor/hooks/use-interactions.js | 29 +- src/editor/plugins/index.js | 12 +- src/editor/util/index.js | 25 +- 18 files changed, 1147 insertions(+), 522 deletions(-) create mode 100644 src/editor/app.js create mode 100644 src/editor/editors/abstract.js create mode 100644 src/editor/editors/elementor.js create mode 100644 src/editor/editors/gutenberg.js create mode 100644 src/editor/editors/index.js diff --git a/src/editor/app.js b/src/editor/app.js new file mode 100644 index 0000000..e0cba9e --- /dev/null +++ b/src/editor/app.js @@ -0,0 +1,400 @@ +import ElementSVG from './assets/element.svg' +import PageSVG from './assets/page.svg' +import { + AddInteractionButton, + InteractionButton, + InteractionPanel, + ImportExportModal, +} from './components' +import { createNewInteraction, createNewAction } from './util' +import { useInteractions } from './hooks' +import { interactions as interactionsConfig, manageInteractionsUrl } from 'interactions' +import { InteractionLibrary } from './interaction-library' +import { isGutenbergEditor } from '~interact/editor/editors' + +import { __ } from '@wordpress/i18n' +import { upload } from '@wordpress/icons' +import { + PanelBody, + Button, + BaseControl, + Notice, +} from '@wordpress/components' +import { + useState, + useCallback, + useRef, + useEffect, + createInterpolateElement, +} from '@wordpress/element' +import { useSelect, useDispatch } from '@wordpress/data' + +import useOnPostPreview from './use-on-post-save' + +const InteractionsApp = ( { + selectedBlockAnchor = null, + enablePostPreviewGuard = true, +} ) => { + const isGutenberg = isGutenbergEditor() + const interactionLibraryMode = useSelect( select => + select( 'interact/interaction-library-modal' ).getMode(), + [] ) + // Interaction library open modal and set target function. + const { + setMode: setInteractionLibraryMode, + } = useDispatch( 'interact/interaction-library-modal' ) + + const [ selectedInteraction, setSelectedInteraction ] = useState( null ) + const [ editPropsPassed, setEditPropsPassed ] = useState( {} ) + const [ editMode, setEditMode ] = useState( 'edit' ) + const [ isShowingError, setIsShowingError ] = useState( true ) + const [ importExportModalProps, setImportExportModalProps ] = useState( null ) + + const sidebarRef = useRef() + // This gets updated when the current interaction being edited is dirty. + const isDirtyRef = useRef( false ) + + const { + interactions: allInteractions, + interactionsFiltered: interactions, + loadingError, + updateInteraction, + deleteInteraction, + } = useInteractions() + + // This listens to the post preview button and publish button and asks the + // user to save the interaction if it's dirty. + const onPostSaveCallback = proceedSaveCallback => { + if ( isDirtyRef.current ) { + // eslint-disable-next-line no-alert + if ( confirm( __( 'You have unsaved changes in your interaction. Do you want to save it before continuing?', 'interactions' ) ) ) { + window?.dispatchEvent( new CustomEvent( 'interact/save-interaction', { + detail: { + // This callback will be called after the interaction is saved. + callback: proceedSaveCallback, + }, + } ) ) + return true + } + } + } + + useOnPostPreview( enablePostPreviewGuard ? onPostSaveCallback : () => {} ) + + const getInteractionFromKey = useCallback( key => { + return allInteractions.find( interaction => interaction.key === key ) + }, [ allInteractions ] ) + + const onAddInteractionHandler = useCallback( ( interactionType, target = null, props = {} ) => { + if ( selectedInteraction && isDirtyRef.current ) { + alert( __( 'You are currently editing an interaction, please save or discard your changes first.', 'interactions' ) )// eslint-disable-line no-alert + return + } + setEditMode( 'new' ) + const newInteraction = createNewInteraction( interactionType, target, props ) + setSelectedInteraction( newInteraction ) + }, [ selectedInteraction ] ) + + const onEditInteractionHandler = useCallback( ( keyOrInteraction, editProps ) => { + if ( selectedInteraction && isDirtyRef.current ) { + alert( __( 'You are currently editing an interaction, please save or discard your changes first.', 'interactions' ) )// eslint-disable-line no-alert + return + } + // If editMode is provided (e.g. when duplicating), set the editMode state accordingly. + if ( typeof editProps.editMode !== 'undefined' ) { + setEditMode( editProps.editMode ) + } + setEditPropsPassed( editProps ) + setSelectedInteraction( typeof keyOrInteraction === 'string' ? getInteractionFromKey( keyOrInteraction ) : keyOrInteraction ) + }, [ getInteractionFromKey, selectedInteraction ] ) + + // Listen to external adds of interactions from the main toolbar button. + useEffect( () => { + const onAddInteractionEventHandler = event => { + onAddInteractionHandler( event.detail.type, event.detail.target, event.detail.props ) + } + const onEditInteractionEventHandler = event => { + const { + key, interaction, ...editProps + } = event.detail + onEditInteractionHandler( key || interaction, editProps ) + } + + window?.addEventListener( 'interact/add-interaction', onAddInteractionEventHandler ) + window?.addEventListener( 'interact/edit-interaction', onEditInteractionEventHandler ) + + return () => { + window?.removeEventListener( 'interact/add-interaction', onAddInteractionEventHandler ) + window?.removeEventListener( 'interact/edit-interaction', onEditInteractionEventHandler ) + } + }, [ onAddInteractionHandler, onEditInteractionHandler ] ) + + useEffect( () => { + if ( ! selectedInteraction ) { + setEditPropsPassed( {} ) + } + }, [ selectedInteraction ] ) + + useEffect( () => { + const dismissedErrors = JSON.parse( localStorage.getItem( 'interact-dismissed-errors' ) || '[]' ) + const errorKey = loadingError?.interactionKey + + if ( ! loadingError?.interactionKey ) { + return + } + + if ( dismissedErrors.includes( errorKey ) ) { + setIsShowingError( false ) + } else { + setIsShowingError( true ) + } + }, [ loadingError ] ) + + // Interaction library can only be opened if the current interaction is not dirty. + useEffect( () => { + if ( ! isGutenberg ) { + return + } + + if ( selectedInteraction && isDirtyRef.current && interactionLibraryMode ) { + setInteractionLibraryMode( null ) + alert( __( 'You are currently editing an interaction, please save or discard your changes first.', 'interactions' ) )// eslint-disable-line no-alert + } + }, [ isGutenberg, selectedInteraction, isDirtyRef, interactionLibraryMode, setInteractionLibraryMode ] ) + + const { elementInteractions, pageInteractions } = interactions.reduce( ( acc, interaction ) => { + const interactionConfig = interactionsConfig[ interaction.type ] + if ( interactionConfig?.type === 'element' ) { + acc.elementInteractions.push( interaction ) + } else if ( interactionConfig?.type === 'page' ) { + acc.pageInteractions.push( interaction ) + } + return acc + }, { elementInteractions: [], pageInteractions: [] } ) + + const onOpenImportExportModal = props => { + setImportExportModalProps( props ) + } + + const onCloseImportExportModal = () => { + setImportExportModalProps( null ) + } + + return <> + { selectedInteraction === null && loadingError && isShowingError && + + setIsShowingError( false ) } + isDismissible={ false } + > +

{ loadingError.message }

+

{ __( 'Check the browser console for more details.', 'interactions' ) }

+
+ +
+
+
+ } + { allInteractions.length > 0 && selectedInteraction === null && + + { interactions.length > 0 &&

{ __( 'These interactions are on this page because of their location rules.', 'interactions' ) }

} + { interactions.length === 0 &&

{ __( 'There are no interactions on this page because no matches were found in the location rules.', 'interactions' ) }

} + +
+ } + { selectedInteraction === null && +
+ +

+ { __( 'Animate or trigger actions on any button, image, text or widget.', 'interactions' ) } +   + + { __( 'Learn more', 'interactions' ) } + +

+ +
+
+
+ { elementInteractions.map( interaction => { + return ( + { + setSelectedInteraction( interaction ) + } } + onDelete={ () => { + deleteInteraction( interaction.key ) + } } + /> + ) + } ) } + { ! elementInteractions.length && ( +
+ +

{ __( 'Define actions that occur when user interacts with elements on your page', 'interactions' ) }

+
+ ) } +
+
+
+ +

+ { __( 'Launch page-wide transitions, backgrounds or state-based effects.', 'interactions' ) } +   + + { __( 'Learn more', 'interactions' ) } + +

+ +
+
+
+ { pageInteractions.map( interaction => { + return ( + { + setSelectedInteraction( interaction ) + } } + onDelete={ () => { + deleteInteraction( interaction.key ) + } } + /> + ) + } ) } + { ! pageInteractions.length && ( +
+ +

{ __( 'Define actions that occur when there\'s a change in your page\'s state', 'interactions' ) }

+
+ ) } +
+
+ +
+ } + { selectedInteraction !== null && + { + return updateInteraction( newInteraction ).then( () => { + setEditMode( 'edit' ) + } ) + } } + onClose={ ( focusOnInteractionButton = false ) => { + if ( focusOnInteractionButton ) { + setTimeout( () => { + sidebarRef.current?.querySelector( `.interact-list__item-button--${ selectedInteraction.key }` )?.focus() + } ) + } + setSelectedInteraction( null ) + setEditMode( 'edit' ) + } } + onDelete={ () => deleteInteraction( selectedInteraction.key ) } + onDirtyChange={ isDirty => isDirtyRef.current = isDirty } + onOpenImportExportModal={ onOpenImportExportModal } + /> + } + { importExportModalProps && + + } + { isGutenberg && interactionLibraryMode && } + +} + +export default InteractionsApp diff --git a/src/editor/components/add-interaction-popover/index.js b/src/editor/components/add-interaction-popover/index.js index 0301258..38e1210 100644 --- a/src/editor/components/add-interaction-popover/index.js +++ b/src/editor/components/add-interaction-popover/index.js @@ -16,6 +16,10 @@ import { setBlockAnchorIfPossible, openInteractionsSidebar, } from '~interact/editor/util' +import { + getCurrentSelectedTarget, + isElementorEditor, +} from '~interact/editor/editors' import { cloneDeep, first } from 'lodash' import { @@ -56,21 +60,24 @@ const AddInteractionPopover = props => { const [ selected, setSelected ] = useState( initialSelected ) const [ showDescription, setShowDescription ] = useState( null ) const [ hidden, setHidden ] = useState( false ) + const isElementor = isElementorEditor() const { getBlockNamesByClientId, getSelectedBlockClientId, } = useSelect( select => { + const blockEditorStore = select( 'core/block-editor' ) return { - getBlockNamesByClientId: select( 'core/block-editor' ).getBlockNamesByClientId, - getSelectedBlockClientId: select( 'core/block-editor' ).getSelectedBlockClientId, + getBlockNamesByClientId: blockEditorStore?.getBlockNamesByClientId || ( () => [] ), + getSelectedBlockClientId: blockEditorStore?.getSelectedBlockClientId || ( () => null ), } } ) const [ target, setTarget ] = useState( { - type: 'block', - value: getOrGenerateBlockAnchor( getSelectedBlockClientId(), false ) || '', - blockName: first( getBlockNamesByClientId( getSelectedBlockClientId() ) ) || '', + type: getCurrentSelectedTarget()?.type || ( isElementor ? 'selector' : 'block' ), + value: getCurrentSelectedTarget()?.value || getOrGenerateBlockAnchor( getSelectedBlockClientId(), false ) || '', + blockName: getCurrentSelectedTarget()?.blockName || first( getBlockNamesByClientId( getSelectedBlockClientId() ) ) || '', + options: getCurrentSelectedTarget()?.options || '', } ) const libraryTitle = ! showElementOption && showPageOption ? __( 'My Page Interactions', 'interactions' ) @@ -94,7 +101,7 @@ const AddInteractionPopover = props => { return acc }, { elementInteractions: [], pageInteractions: [] } ) - if ( hidden ) { + if ( hidden && ! isElementor ) { return ( { setHidden( true ) } + hasPickerPopover={ ! isElementor } + onBlockSelectClick={ () => { + if ( ! isElementor ) { + setHidden( true ) + } + } } /> ) } diff --git a/src/editor/components/import-export-modal/index.js b/src/editor/components/import-export-modal/index.js index e092f85..44727f7 100644 --- a/src/editor/components/import-export-modal/index.js +++ b/src/editor/components/import-export-modal/index.js @@ -6,6 +6,7 @@ * External deprendencies */ import { getOrGenerateBlockAnchor } from '~interact/editor/util' +import { getCurrentSelectedTarget } from '~interact/editor/editors' import { first } from 'lodash' /** @@ -52,9 +53,10 @@ const ImportExportModal = props => { getBlockNamesByClientId, getSelectedBlockClientId, } = useSelect( select => { + const blockEditorStore = select( 'core/block-editor' ) return { - getBlockNamesByClientId: select( 'core/block-editor' ).getBlockNamesByClientId, - getSelectedBlockClientId: select( 'core/block-editor' ).getSelectedBlockClientId, + getBlockNamesByClientId: blockEditorStore?.getBlockNamesByClientId || ( () => [] ), + getSelectedBlockClientId: blockEditorStore?.getSelectedBlockClientId || ( () => null ), } } ) @@ -83,9 +85,14 @@ const ImportExportModal = props => { target = null, } = data + const selectedTarget = getCurrentSelectedTarget() + if ( selectedTarget ) { + target = selectedTarget + } + // If the currently selected block is valid, overwrite the interaction trigger. const clientId = getSelectedBlockClientId() - if ( clientId ) { + if ( clientId && ! selectedTarget ) { target = { type: 'block', value: getOrGenerateBlockAnchor( clientId, true ) || '', diff --git a/src/editor/components/location-rules/index.js b/src/editor/components/location-rules/index.js index f9bb975..3bf05ba 100644 --- a/src/editor/components/location-rules/index.js +++ b/src/editor/components/location-rules/index.js @@ -9,12 +9,15 @@ import { import { Fragment, useEffect, useState, } from '@wordpress/element' -import { select } from '@wordpress/data' import { __, sprintf } from '@wordpress/i18n' import apiFetch from '@wordpress/api-fetch' +import { getCurrentEditorPostContext } from '~interact/editor/editors' const NOOP = () => {} +const getCurrentPostId = () => getCurrentEditorPostContext().postId +const getCurrentPostType = () => getCurrentEditorPostContext().postType + const updateLocation = ( locations, index1, index2, newLocation ) => { const newLocations = cloneDeep( locations ) newLocations[ index1 ][ index2 ] = newLocation @@ -94,7 +97,7 @@ const LocationRules = props => { onChange( removeLocation( locations, i, k ) ) } } onClickAnd={ () => { - const value = locations.length === 0 ? select( 'core/editor' ).getCurrentPostId() : '' + const value = locations.length === 0 ? getCurrentPostId() : '' onChange( addLocation( locations, i, k + 1, { param: 'post', operator: '==', @@ -111,7 +114,7 @@ const LocationRules = props => { label={ __( 'Add rule group', 'interactions' ) } variant="secondary" onClick={ () => { - const value = locations.length === 0 ? select( 'core/editor' ).getCurrentPostId() : '' + const value = locations.length === 0 ? getCurrentPostId() : '' onChange( addLocation( locations, locations.length, 0, { param: 'post', operator: '==', @@ -151,10 +154,10 @@ const LocationRule = props => { // If param is a post/page, then the post_id doesn't exist yet, then we need to add it near the top as "Current Post" or "Current Page" if ( param === 'post' || param === 'page' ) { - const postType = select( 'core/editor' ).getCurrentPostType() + const postType = getCurrentPostType() options.some( ( { post_type, options } ) => { if ( post_type === postType ) { - const currentPostId = select( 'core/editor' ).getCurrentPostId() + const currentPostId = getCurrentPostId() // Check if the current post is already in the list const exists = options.some( ( { value } ) => { return value === currentPostId diff --git a/src/editor/components/target-selector/index.js b/src/editor/components/target-selector/index.js index ca37d6b..7f4ba10 100644 --- a/src/editor/components/target-selector/index.js +++ b/src/editor/components/target-selector/index.js @@ -1,6 +1,11 @@ import TargetSVG from '~interact/editor/assets/target.svg' import { GridLayout, FlexLayout } from '~interact/editor/components' +import { + getSelectedBlockAnchor, + isElementorEditor, + startElementorElementPicker, +} from '~interact/editor/editors' import { getOrGenerateBlockAnchor, getOrGenerateBlockClass } from '~interact/editor/util' import { SelectControl, @@ -40,6 +45,9 @@ const TargetSelector = props => { const [ isPopoverOpen, setIsPopoverOpen ] = useState( false ) const [ buttonRef, setButtonRef ] = useState( null ) const prevValueRef = useRef( {} ) + const elementPickerStopRef = useRef( null ) + const isElementor = isElementorEditor() + const hasBlockEditor = !! select( 'core/block-editor' )?.getSelectedBlockClientId const targetButton = ( <> @@ -50,14 +58,24 @@ const TargetSelector = props => { ref={ setButtonRef } onClick={ () => { onBlockSelectClick() - if ( hasPickerPopover && ! isPopoverOpen ) { + if ( isElementor ) { + elementPickerStopRef.current?.() + elementPickerStopRef.current = startElementorElementPicker( { + targetType: value.type === 'class' ? 'class' : 'selector', + onPick: target => { + onChange( target ) + onBlockSelectDone() + }, + onCancel: onBlockSelectDone, + } ) + } else if ( hasPickerPopover && ! isPopoverOpen ) { setIsPopoverOpen( true ) } else if ( hasPickerPopover && isPopoverOpen ) { setIsPopoverOpen( false ) } } } /> - { hasPickerPopover && isPopoverOpen && ( + { hasPickerPopover && isPopoverOpen && ! isElementor && ( { targetOptions = targetOptions.filter( target => target.value !== 'trigger' ) } + if ( isElementor ) { + targetOptions = targetOptions.filter( target => + [ 'trigger', 'class', 'selector' ].includes( target.value ) + ) + } + + useEffect( () => { + return () => { + elementPickerStopRef.current?.() + } + }, [] ) + // Watch for warnings, we need to throttle this because we are subscribed to // the editor and changes can be fast. const [ targetWarning, setTargetWarning ] = useState( getTargetSelectorWarning( value.type, value.value ) ) @@ -176,7 +206,7 @@ const TargetSelector = props => { const newTarget = { ...value, type } // Use any previous values we may have already entered. - if ( type === 'block-name' ) { + if ( type === 'block-name' && hasBlockEditor ) { if ( prevValueRef.current[ type ] ) { newTarget.value = prevValueRef.current[ type ] } else { @@ -207,7 +237,7 @@ const TargetSelector = props => { className="interact-target-block-input" id="interact-target-block-input" label={ __( 'Block Anchor / ID', 'interactions' ) } - value={ value.value } + value={ value.value || ( isElementor ? '' : getSelectedBlockAnchor() || '' ) } // When typing, the previous blockName should be invalid onChange={ targetValue => onChange( { ...value, blockName: '', value: targetValue, diff --git a/src/editor/components/timeline/class-runner.js b/src/editor/components/timeline/class-runner.js index a06d812..d197467 100644 --- a/src/editor/components/timeline/class-runner.js +++ b/src/editor/components/timeline/class-runner.js @@ -1,6 +1,7 @@ import InteractRunner from '../../../frontend/scripts/class-runner' import { actions as actionsConfig, interactions as interactionsConfig } from 'interactions' import { getBlockClientId } from './with-tracked-anchors' +import { getEditorCanvasElement } from '~interact/editor/editors' const NOOP = () => {} @@ -48,12 +49,7 @@ class InteractEditorRunner extends InteractRunner { * @return {DOMElement} The document where the interactions are being previewed. */ getDocument() { - const iframe = document.querySelector( 'iframe[name="editor-canvas"]' ) - let editorEl = document.querySelector( '.editor-styles-wrapper' ) - if ( iframe ) { - editorEl = iframe.contentDocument.querySelector( '.editor-styles-wrapper' ) - } - return editorEl + return getEditorCanvasElement() } getTimelineType( interaction ) { diff --git a/src/editor/components/timeline/runner.js b/src/editor/components/timeline/runner.js index b2a23ac..39f4523 100644 --- a/src/editor/components/timeline/runner.js +++ b/src/editor/components/timeline/runner.js @@ -9,6 +9,7 @@ import { cloneDeep } from 'lodash' import { getBlockClientId } from './with-tracked-anchors' import { doAction } from '@wordpress/hooks' import { select } from '@wordpress/data' +import { getEditorMode } from '~interact/editor/editors' // Create the runner. const runner = new InteractEditorRunner() @@ -41,7 +42,7 @@ export const useTimelineRunnerRef = ( interaction, actions, timelineIndex ) => { const runnerRef = useRef( null ) const [ initialStyles, setInitialStyles ] = useState( '' ) - const renderingMode = select( 'core/editor' ).getRenderingMode() + const renderingMode = select( 'core/editor' )?.getRenderingMode?.() || getEditorMode() const prevRenderingMode = useRef( renderingMode ) // Initialize the runner diff --git a/src/editor/components/timeline/use-initial-style-tag.js b/src/editor/components/timeline/use-initial-style-tag.js index d5ce48f..19e6aa1 100644 --- a/src/editor/components/timeline/use-initial-style-tag.js +++ b/src/editor/components/timeline/use-initial-style-tag.js @@ -4,16 +4,9 @@ import { useEffect, useRef } from '@wordpress/element' import { debounce } from 'lodash' +import { getEditorCanvasElement } from '~interact/editor/editors' -// Gets the main editor element, either the iframe or the main document. -const getEditorEl = () => { - const iframe = document.querySelector( 'iframe[name="editor-canvas"]' ) - let editorEl = document.querySelector( '.editor-styles-wrapper' ) - if ( iframe ) { - editorEl = iframe.contentDocument.querySelector( '.editor-styles-wrapper' ) - } - return editorEl -} +const getEditorEl = () => getEditorCanvasElement() // Creates a style tag inside the editor to hold the initial interaction styles export const useInitialStyleTag = style => { diff --git a/src/editor/editor.js b/src/editor/editor.js index ede9876..8b9741c 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -1,453 +1,7 @@ -import IconSVG from './assets/icon.svg' -import ElementSVG from './assets/element.svg' -import PageSVG from './assets/page.svg' -import { - AddInteractionButton, - InteractionButton, - InteractionPanel, - ImportExportModal, -} from './components' -import { createNewInteraction, createNewAction } from './util' -import { useInteractions } from './hooks' -import { interactions as interactionsConfig, manageInteractionsUrl } from 'interactions' -import { InteractionLibrary } from './interaction-library' - -import { registerPlugin } from '@wordpress/plugins' -import { __ } from '@wordpress/i18n' -import { upload } from '@wordpress/icons' -import { - PanelBody, - Button, - BaseControl, - Notice, -} from '@wordpress/components' -import { - useState, - useCallback, - useRef, - useEffect, - createInterpolateElement, -} from '@wordpress/element' -import { useSelect, useDispatch } from '@wordpress/data' - import './plugins' -import useOnPostPreview from './use-on-post-save' - -const InteractionsEditor = () => { - // We need to to this for both, because one might be disabled. E.g. in - // WooCommerce, editSite is loaded and stops the sidebar from showing up. - const SideEditorPluginSidebar = window.wp.editSite?.PluginSidebar - const PostEditorPluginSidebar = window.wp.editPost?.PluginSidebar - - const SideBar = SideEditorPluginSidebar ? SideEditorPluginSidebar - : PostEditorPluginSidebar ? PostEditorPluginSidebar : null - - const selectedBlockAnchor = useSelect( select => { - const clientId = select( 'core/block-editor' ).getSelectedBlockClientId() - return clientId ? select( 'core/block-editor' ).getBlockAttributes( clientId )?.anchor : null - } ) - - const interactionLibraryMode = useSelect( select => - select( 'interact/interaction-library-modal' ).getMode(), - [] ) - // Interaction library open modal and set target function - const { - setMode: setInteractionLibraryMode, - } = useDispatch( 'interact/interaction-library-modal' ) - - const [ selectedInteraction, setSelectedInteraction ] = useState( null ) - const [ editPropsPassed, setEditPropsPassed ] = useState( {} ) - const [ editMode, setEditMode ] = useState( 'edit' ) - const [ isShowingError, setIsShowingError ] = useState( true ) - const [ importExportModalProps, setImportExportModalProps ] = useState( null ) - - const sidebarRef = useRef() - const isDirtyRef = useRef( false ) // This gets updated when the current interaction being edited is dirty. - - const { - interactions: allInteractions, - interactionsFiltered: interactions, - loadingError, - updateInteraction, - deleteInteraction, - } = useInteractions() - - // This listens to the post preview button and publish button and asks the - // user to save the interaction if it's dirty. - const onPostSaveCallback = proceedSaveCallback => { - if ( isDirtyRef.current ) { - // eslint-disable-next-line no-alert - if ( confirm( __( 'You have unsaved changes in your interaction. Do you want to save it before continuing?', 'interactions' ) ) ) { - // Save the interaction, give the callback as the detail. - window?.dispatchEvent( new CustomEvent( 'interact/save-interaction', { - detail: { - // This callback will be called after the interaction is saved. - callback: proceedSaveCallback, - }, - } ) ) - // Return true to stop the preview. - return true - } - } - } - useOnPostPreview( onPostSaveCallback ) - - const getInteractionFromKey = useCallback( key => { - return allInteractions.find( interaction => interaction.key === key ) - }, [ allInteractions ] ) - - const onAddInteractionHandler = useCallback( ( interactionType, target = null, props = {} ) => { - if ( selectedInteraction && isDirtyRef.current ) { - alert( __( 'You are currently editing an interaction, please save or discard your changes first.', 'interactions' ) )// eslint-disable-line no-alert - return - } - setEditMode( 'new' ) - const newInteraction = createNewInteraction( interactionType, target, props ) - setSelectedInteraction( newInteraction ) - }, [ selectedInteraction ] ) - - const onEditInteractionHandler = useCallback( ( keyOrInteraction, editProps ) => { - if ( selectedInteraction && isDirtyRef.current ) { - alert( __( 'You are currently editing an interaction, please save or discard your changes first.', 'interactions' ) )// eslint-disable-line no-alert - return - } - // If editMode is provided (e.g. when duplicating), set the editMode state accordingly - if ( typeof editProps.editMode !== 'undefined' ) { - setEditMode( editProps.editMode ) - } - setEditPropsPassed( editProps ) - setSelectedInteraction( typeof keyOrInteraction === 'string' ? getInteractionFromKey( keyOrInteraction ) : keyOrInteraction ) - }, [ getInteractionFromKey, selectedInteraction ] ) - - // Listen to external adds of interactions from the main toolbar button. - useEffect( () => { - const onAddInteractionEventHandler = event => { - onAddInteractionHandler( event.detail.type, event.detail.target, event.detail.props ) - } - const onEditInteractionEventHandler = event => { - const { - key, interaction, ...editProps - } = event.detail - onEditInteractionHandler( key || interaction, editProps ) - } - - window?.addEventListener( 'interact/add-interaction', onAddInteractionEventHandler ) - window?.addEventListener( 'interact/edit-interaction', onEditInteractionEventHandler ) - - return () => { - window?.removeEventListener( 'interact/add-interaction', onAddInteractionEventHandler ) - window?.removeEventListener( 'interact/edit-interaction', onEditInteractionEventHandler ) - } - }, [ onAddInteractionHandler, onEditInteractionHandler ] ) - - useEffect( () => { - if ( ! selectedInteraction ) { - setEditPropsPassed( {} ) - } - }, [ selectedInteraction ] ) - - useEffect( () => { - const dismissedErrors = JSON.parse( localStorage.getItem( 'interact-dismissed-errors' ) || '[]' ) - const errorKey = loadingError?.interactionKey - - if ( ! loadingError?.interactionKey ) { - return - } - - if ( dismissedErrors.includes( errorKey ) ) { - setIsShowingError( false ) - } else { - setIsShowingError( true ) - } - }, [ loadingError ] ) - - // Interaction library can only be opened if the current interaction is not dirty. - useEffect( () => { - if ( selectedInteraction && isDirtyRef.current && interactionLibraryMode ) { - setInteractionLibraryMode( null ) - alert( __( 'You are currently editing an interaction, please save or discard your changes first.', 'interactions' ) )// eslint-disable-line no-alert - } - }, [ selectedInteraction, isDirtyRef, interactionLibraryMode, setInteractionLibraryMode ] ) - - const { elementInteractions, pageInteractions } = interactions - // Sort alphabetically by title - // .sort( ( a, b ) => { - // if ( a.title < b.title ) { - // return -1 - // } - // if ( a.title > b.title ) { - // return 1 - // } - // return 0 - // } ) - .reduce( ( acc, interaction ) => { - const interactionConfig = interactionsConfig[ interaction.type ] - if ( interactionConfig?.type === 'element' ) { - acc.elementInteractions.push( interaction ) - } else if ( interactionConfig?.type === 'page' ) { - acc.pageInteractions.push( interaction ) - } - return acc - }, { elementInteractions: [], pageInteractions: [] } ) - - const onOpenImportExportModal = props => { - setImportExportModalProps( props ) - } - - const onCloseImportExportModal = () => { - setImportExportModalProps( null ) - } - - // If the sidebar is not available (like in the Widgets editor), then do nothing. - if ( ! SideBar ) { - return null - } - - return <> - } - > - { selectedInteraction === null && loadingError && isShowingError && - - setIsShowingError( false ) } - isDismissible={ false } - > -

{ loadingError.message }

-

{ __( 'Check the browser console for more details.', 'interactions' ) }

-
- -
-
-
- } - { allInteractions.length > 0 && selectedInteraction === null && - - { interactions.length > 0 &&

{ __( 'These interactions are on this page because of their location rules.', 'interactions' ) }

} - { interactions.length === 0 &&

{ __( 'There are no interactions on this page because no matches were found in the location rules.', 'interactions' ) }

} - -
- } - { selectedInteraction === null && -
- -

- { __( 'Animate or trigger actions on any button, image, text or widget.', 'interactions' ) } -   - - { __( 'Learn more', 'interactions' ) } - -

- -
-
-
- { elementInteractions - .map( interaction => { - return ( - { - setSelectedInteraction( interaction ) - } } - onDelete={ () => { - deleteInteraction( interaction.key ) - } } - /> - ) - } ) } - { ! elementInteractions.length && ( -
- -

{ __( 'Define actions that occur when user interacts with elements on your page', 'interactions' ) }

-
- ) } -
-
- - - -

- { __( 'Launch page‑wide transitions, backgrounds or state‑based effects.', 'interactions' ) } -   - - { __( 'Learn more', 'interactions' ) } - -

- -
-
-
- { pageInteractions - .map( interaction => { - return ( - { - setSelectedInteraction( interaction ) - } } - onDelete={ () => { - deleteInteraction( interaction.key ) - } } - /> - ) - } ) } - { ! pageInteractions.length && ( -
- -

{ __( 'Define actions that occur when there\'s a change in your page\'s state', 'interactions' ) }

-
- ) } -
-
- - { /* - - */ } -
- } - { selectedInteraction !== null && - { - return updateInteraction( newInteraction ).then( () => { - setEditMode( 'edit' ) - } ) - } } - onClose={ ( focusOnInteractionButton = false ) => { - if ( focusOnInteractionButton ) { - // Focus on the previous interaction button so we can go back to it. - setTimeout( () => { - sidebarRef.current?.querySelector( `.interact-list__item-button--${ selectedInteraction.key }` )?.focus() - } ) - } - setSelectedInteraction( null ) - setEditMode( 'edit' ) - } } - onDelete={ () => deleteInteraction( selectedInteraction.key ) } - onDirtyChange={ isDirty => isDirtyRef.current = isDirty } - onOpenImportExportModal={ onOpenImportExportModal } - /> - } - { importExportModalProps && - - } - - { /* Render the Interaction Library modal in the root editor component */ - interactionLibraryMode && - } - -} +import { getInteractionsEditor } from './editors' +import { domReady } from '~interact/shared/dom-ready.js' -registerPlugin( 'interact-editor', { - render: InteractionsEditor, +domReady( () => { + getInteractionsEditor().init() } ) diff --git a/src/editor/editor.php b/src/editor/editor.php index a964602..3c9df00 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -18,17 +18,38 @@ class Interact_Editor { */ function __construct() { if ( is_admin() ) { - add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor' ) ); + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_gutenberg_editor' ) ); + add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue_elementor_editor' ) ); add_action( 'enqueue_block_assets', array( $this, 'enqueue_assets' ) ); } } + /** + * Loads the editor script inside the Gutenberg editor. + * + * @return void + */ + public function enqueue_gutenberg_editor() { + $this->enqueue_editor( 'gutenberg' ); + } + + /** + * Loads the editor script inside the Elementor editor. + * + * @return void + */ + public function enqueue_elementor_editor() { + $this->enqueue_editor( 'elementor' ); + } + /** * Loads the editor script. * + * @param string $editor_mode Current editor mode. + * * @return void */ - public function enqueue_editor() { + public function enqueue_editor( $editor_mode = 'gutenberg' ) { // Load the required interaciton and action types. interact_require_types(); @@ -63,6 +84,7 @@ public function enqueue_editor() { [ $actions, $action_categories ] = $this->get_action_types_config(); global $wp_version; + $post = get_post(); $args = apply_filters( 'interact/localize_script', array( 'interactions' => $interactions, 'interactionCategories' => $interaction_categories, @@ -77,6 +99,14 @@ public function enqueue_editor() { 'restNonce' => wp_create_nonce( 'wp_rest' ), // This needs to be 'wp_rest' to use the built-in nonce verification. 'srcUrl' => untrailingslashit( plugins_url( '/', INTERACT_FILE ) ), 'currentUserCanUnfilteredHtml' => current_user_can( 'unfiltered_html' ), + // Keep the current editor and document context available to the + // shared editor app so integrations can resolve targets and + // location rules correctly. + 'editorMode' => $editor_mode, + 'currentPostId' => $post ? (int) $post->ID : 0, + 'currentPostType' => $post ? $post->post_type : '', + 'currentPostTemplate' => $post ? get_page_template_slug( $post->ID ) : '', + 'currentPostParent' => $post ? (int) $post->post_parent : 0, ) ); wp_localize_script( 'interact-editor', 'interactions', $args ); } diff --git a/src/editor/editor.scss b/src/editor/editor.scss index a07a736..e2e3c17 100644 --- a/src/editor/editor.scss +++ b/src/editor/editor.scss @@ -139,6 +139,66 @@ --wp-components-color-accent: #05f; } +.interact-elementor-launcher { + position: fixed !important; + right: 32px; + bottom: 32px; + z-index: 100000; + border-radius: 999px !important; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18); +} + +.interact-elementor-backdrop { + position: fixed; + inset: 0; + background: rgba(17, 24, 39, 0.18); + z-index: 99998; +} + +.interact-elementor-panel { + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: min(420px, calc(100vw - 48px)); + background: #fff; + box-shadow: -24px 0 48px rgba(15, 23, 42, 0.18); + transform: translateX(100%); + transition: transform 180ms ease; + z-index: 99999; + display: flex; + flex-direction: column; +} + +.interact-elementor-panel.is-open { + transform: translateX(0); +} + +.interact-elementor-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 18px; + border-bottom: 1px solid #e5e7eb; + background: #f8fafc; +} + +.interact-elementor-panel__title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; +} + +.interact-elementor-panel__body { + flex: 1; + overflow: auto; +} + +.interact-elementor-sidebar { + min-height: 100%; +} + /* Wordpress 7.0 compatibility */ .interact-sidebar, .interact-popover { @@ -160,4 +220,4 @@ .interact-property-control .components-base-control__field { margin-bottom: 8px; } -} \ No newline at end of file +} diff --git a/src/editor/editors/abstract.js b/src/editor/editors/abstract.js new file mode 100644 index 0000000..e8e5052 --- /dev/null +++ b/src/editor/editors/abstract.js @@ -0,0 +1,87 @@ +import { + currentPostId, + currentPostParent, + currentPostTemplate, + currentPostType, +} from 'interactions' +import { select } from '@wordpress/data' + +const NOOP = () => {} + +// Base editor adapter that defines the shared editor contract. +class InteractionsEditorAbstract { + constructor() { + this.initialized = false + } + + // Boot the current editor integration. + init() { + this.initialized = true + return this + } + + // Return the current editor mode. + getEditorMode() { + throw new Error( 'InteractionsEditorAbstract#getEditorMode must be implemented.' ) + } + + isElementor() { + return this.getEditorMode() === 'elementor' + } + + isGutenberg() { + return ! this.isElementor() + } + + // Return the current document context for location rule matching. + getCurrentPostContext() { + const editorStore = select( 'core/editor' ) + return { + postId: editorStore?.getCurrentPostId?.() || currentPostId || 0, + postType: editorStore?.getCurrentPostType?.() || currentPostType || '', + postTemplate: editorStore?.getCurrentPost?.()?.template || currentPostTemplate || '', + postParent: editorStore?.getCurrentPost?.()?.parent || currentPostParent || 0, + } + } + + getCanvasDocument() { + return document + } + + getCanvasElement() { + const canvasDocument = this.getCanvasDocument() + return canvasDocument?.querySelector( '.editor-styles-wrapper' ) || canvasDocument?.body || document.body + } + + // Open the editor panel when supported by the current integration. + openPanel() { + return null + } + + openInteractionsPanel() { + return this.openPanel() + } + + getSelectedBlockAnchor() { + return null + } + + getCurrentSelectedTarget() { + return null + } + + registerSelectionTracking() { + return NOOP + } + + // Start an editor-specific target picker. + startElementPicker( args = {} ) { + const { + onCancel = NOOP, + } = args + onCancel() + return NOOP + } +} + +export default InteractionsEditorAbstract diff --git a/src/editor/editors/elementor.js b/src/editor/editors/elementor.js new file mode 100644 index 0000000..45cbf50 --- /dev/null +++ b/src/editor/editors/elementor.js @@ -0,0 +1,282 @@ +import IconSVG from '../assets/icon.svg' +import InteractionsApp from '../app' +import InteractionsEditorAbstract from './abstract' + +import { __ } from '@wordpress/i18n' +import { Button } from '@wordpress/components' +import { + useEffect, + useState, + createRoot, +} from '@wordpress/element' + +const NOOP = () => {} + +// Elementor editor adapter. +class ElementorInteractionsEditor extends InteractionsEditorAbstract { + constructor() { + super() + this.selectedElement = null + } + + getEditorMode() { + return 'elementor' + } + + // Mount the Elementor launcher and side panel shell. + init() { + if ( this.initialized ) { + return this + } + + const mountNodeId = 'interact-elementor-root' + if ( document.getElementById( mountNodeId ) ) { + return super.init() + } + + const ElementorInteractionsEditorComponent = () => { + const [ isOpen, setIsOpen ] = useState( false ) + + useEffect( () => { + const openHandler = () => setIsOpen( true ) + window.addEventListener( 'interact/open-elementor-sidebar', openHandler ) + return () => window.removeEventListener( 'interact/open-elementor-sidebar', openHandler ) + }, [] ) + + const Wrapper = ( { + children, + } ) => ( +
+ { children } +
+ ) + + return ( + <> + + { /* { isOpen && ( +
setIsOpen( false ) } + aria-hidden="true" + /> + ) } */ } +
+
+
+ + { __( 'Interactions', 'interactions' ) } +
+
+
+ + + +
+
+ + ) + } + + const mountNode = document.createElement( 'div' ) + mountNode.id = mountNodeId + document.body.appendChild( mountNode ) + this.registerSelectionTracking() + createRoot( mountNode ).render( ) + + return super.init() + } + + // Return the Elementor preview canvas document. + getCanvasDocument() { + const iframe = document.querySelector( '#elementor-preview-iframe' ) + return iframe?.contentDocument || document + } + + // Open the Interactions sidebar in Elementor. + openPanel() { + window.dispatchEvent( new CustomEvent( 'interact/open-elementor-sidebar' ) ) + return null + } + + // Build an interaction target from a selected Elementor element. + buildTargetFromElement( element, targetType = 'selector' ) { + if ( ! element ) { + return null + } + + const targetElement = element.closest( '.elementor-element[data-id]' ) + if ( ! targetElement ) { + return null + } + + const elementId = targetElement.getAttribute( 'data-id' ) + if ( ! elementId ) { + return null + } + + const elementType = targetElement.getAttribute( 'data-element_type' ) || '' + const widgetType = targetElement.getAttribute( 'data-widget_type' ) || '' + const label = widgetType || elementType || 'elementor-element' + const wrapperSelector = `.elementor-element.elementor-element-${ elementId }` + const classValue = `elementor-element-${ elementId }` + const targetValue = targetType === 'class' + ? classValue + : elementType === 'widget' + ? `${ wrapperSelector } > *` + : wrapperSelector + + return { + type: targetType, + value: targetValue, + blockName: label, + } + } + + getHighlightElement( element ) { + if ( ! element ) { + return null + } + + const targetElement = element.closest( '.elementor-element[data-id]' ) + if ( ! targetElement ) { + return null + } + + const elementType = targetElement.getAttribute( 'data-element_type' ) || '' + if ( elementType !== 'widget' ) { + return targetElement + } + + return targetElement.firstElementChild || targetElement + } + + // Return the currently selected Elementor target. + getCurrentSelectedTarget() { + return this.buildTargetFromElement( this.selectedElement?.element || null ) + } + + // Track the current Elementor selection from the editor panel. + registerSelectionTracking() { + if ( ! window.elementor?.hooks?.addAction ) { + return NOOP + } + + const register = action => { + window.elementor.hooks.addAction( action, ( panel, model, view ) => { + this.selectedElement = { + model, + view, + element: view?.$el?.get?.( 0 ) || null, + } + } ) + } + + [ + 'panel/open_editor/section', + 'panel/open_editor/column', + 'panel/open_editor/container', + 'panel/open_editor/widget', + ].forEach( register ) + + return NOOP + } + + // Start an Elementor preview picker for selector or class targets. + startElementPicker( { + targetType = 'selector', + onPick = NOOP, + onCancel = NOOP, + } = {} ) { + const previewDocument = this.getCanvasDocument() + if ( ! previewDocument ) { + onCancel() + return NOOP + } + + let highlightedElement = null + + // Restore the previously highlighted element back to its original outline. + const clearHighlight = () => { + if ( highlightedElement ) { + highlightedElement.style.outline = highlightedElement.dataset.interactPrevOutline || '' + highlightedElement.style.outlineOffset = highlightedElement.dataset.interactPrevOutlineOffset || '' + delete highlightedElement.dataset.interactPrevOutline + delete highlightedElement.dataset.interactPrevOutlineOffset + } + highlightedElement = null + } + + // Follow the pointer inside the preview and visually mark the current pick candidate. + const mouseMoveHandler = event => { + const candidate = event.target.closest( '.elementor-element[data-id]' ) + const highlightCandidate = this.getHighlightElement( candidate ) + if ( highlightCandidate === highlightedElement ) { + return + } + clearHighlight() + if ( highlightCandidate ) { + highlightedElement = highlightCandidate + highlightedElement.dataset.interactPrevOutline = highlightedElement.style.outline || '' + highlightedElement.dataset.interactPrevOutlineOffset = highlightedElement.style.outlineOffset || '' + highlightedElement.style.outline = '2px solid #05f' + highlightedElement.style.outlineOffset = '2px' + } + } + + // Convert the clicked Elementor element into an interaction target and stop pick mode. + const clickHandler = event => { + const candidate = event.target.closest( '.elementor-element[data-id]' ) + if ( ! candidate ) { + return + } + event.preventDefault() + event.stopPropagation() + const target = this.buildTargetFromElement( candidate, targetType ) + stop() + if ( target ) { + onPick( target ) + } else { + onCancel() + } + } + + // Allow canceling the picker with Escape. + const keyHandler = event => { + if ( event.key === 'Escape' ) { + stop() + onCancel() + } + } + + // Remove all temporary picker listeners and preview highlighting. + const stop = () => { + clearHighlight() + previewDocument.removeEventListener( 'mousemove', mouseMoveHandler, true ) + previewDocument.removeEventListener( 'click', clickHandler, true ) + document.removeEventListener( 'keydown', keyHandler, true ) + } + + previewDocument.addEventListener( 'mousemove', mouseMoveHandler, true ) + previewDocument.addEventListener( 'click', clickHandler, true ) + document.addEventListener( 'keydown', keyHandler, true ) + + return stop + } +} + +export default ElementorInteractionsEditor diff --git a/src/editor/editors/gutenberg.js b/src/editor/editors/gutenberg.js new file mode 100644 index 0000000..0c42d05 --- /dev/null +++ b/src/editor/editors/gutenberg.js @@ -0,0 +1,129 @@ +import IconSVG from '../assets/icon.svg' +import InteractionsApp from '../app' +import InteractionsEditorAbstract from './abstract' + +import { registerPlugin } from '@wordpress/plugins' +import { __ } from '@wordpress/i18n' +import { + useSelect, + dispatch, + select, +} from '@wordpress/data' + +// Stable Gutenberg sidebar shell used to keep the shared app mounted. +const GutenbergSidebarWrapper = ( { + children, + SideBar, +} ) => ( + } + > + { children } + +) + +// Gutenberg editor adapter. +class GutenbergInteractionsEditor extends InteractionsEditorAbstract { + getEditorMode() { + return 'gutenberg' + } + + // Register the Gutenberg sidebar plugin. + init() { + if ( this.initialized ) { + return this + } + + const GutenbergInteractionsEditorComponent = () => { + const SideEditorPluginSidebar = window.wp.editSite?.PluginSidebar + const PostEditorPluginSidebar = window.wp.editPost?.PluginSidebar + const SideBar = SideEditorPluginSidebar ? SideEditorPluginSidebar + : PostEditorPluginSidebar ? PostEditorPluginSidebar : null + + const selectedBlockAnchor = useSelect( select => { + const clientId = select( 'core/block-editor' )?.getSelectedBlockClientId?.() + return clientId ? select( 'core/block-editor' ).getBlockAttributes( clientId )?.anchor : null + }, [] ) + + if ( ! SideBar ) { + return null + } + + return ( + + + + ) + } + + registerPlugin( 'interact-editor', { + render: GutenbergInteractionsEditorComponent, + } ) + + return super.init() + } + + // Return the Gutenberg editor canvas document. + getCanvasDocument() { + const iframe = document.querySelector( 'iframe[name="editor-canvas"]' ) + return iframe?.contentDocument || document + } + + // Open the Interactions sidebar in Gutenberg. + openPanel() { + if ( dispatch( 'core/edit-post' ) ) { + return dispatch( 'core/edit-post' ).openGeneralSidebar( 'interact-editor/sidebar' ) + } + return dispatch( 'core/edit-site' ).openGeneralSidebar( 'interact-editor/sidebar' ) + } + + // Return the currently selected block anchor. + getSelectedBlockAnchor() { + const blockEditorStore = select( 'core/block-editor' ) + if ( ! blockEditorStore?.getSelectedBlockClientId ) { + return null + } + const clientId = blockEditorStore.getSelectedBlockClientId() + return clientId ? blockEditorStore.getBlockAttributes( clientId )?.anchor : null + } + + // Return the current Gutenberg selection as an interaction target. + getCurrentSelectedTarget() { + const blockEditorStore = select( 'core/block-editor' ) + const clientId = blockEditorStore?.getSelectedBlockClientId?.() + if ( ! clientId ) { + return null + } + + const block = blockEditorStore.getBlock?.( clientId ) + if ( ! block ) { + return null + } + + const hasAnchorAttribute = !! select( 'core/blocks' ).getBlockType( block.name )?.attributes?.anchor + if ( hasAnchorAttribute ) { + return { + type: 'block', + value: this.getSelectedBlockAnchor() || '', + blockName: block.name || '', + options: { clientId }, + } + } + + const className = block.attributes?.className?.split( ' ' )?.[ 0 ] || '' + return { + type: 'class', + value: className, + blockName: block.name || '', + options: { clientId }, + } + } +} + +export default GutenbergInteractionsEditor diff --git a/src/editor/editors/index.js b/src/editor/editors/index.js new file mode 100644 index 0000000..259a301 --- /dev/null +++ b/src/editor/editors/index.js @@ -0,0 +1,42 @@ +import { editorMode } from 'interactions' +import GutenbergInteractionsEditor from './gutenberg' +import ElementorInteractionsEditor from './elementor' + +let activeEditor = null + +// Create the active editor adapter for the current editor environment. +const createInteractionsEditor = () => { + return editorMode === 'elementor' + ? new ElementorInteractionsEditor() + : new GutenbergInteractionsEditor() +} + +// Return the memoized editor adapter instance. +export const getInteractionsEditor = () => { + if ( ! activeEditor ) { + activeEditor = createInteractionsEditor() + } + return activeEditor +} + +export const getEditorMode = () => getInteractionsEditor().getEditorMode() + +export const isElementorEditor = () => getInteractionsEditor().isElementor() + +export const isGutenbergEditor = () => getInteractionsEditor().isGutenberg() + +export const getCurrentEditorPostContext = () => getInteractionsEditor().getCurrentPostContext() + +export const getSelectedBlockAnchor = () => getInteractionsEditor().getSelectedBlockAnchor() + +export const getEditorCanvasDocument = () => getInteractionsEditor().getCanvasDocument() + +export const getEditorCanvasElement = () => getInteractionsEditor().getCanvasElement() + +export const openInteractionsSidebar = () => getInteractionsEditor().openInteractionsPanel() + +export const getCurrentSelectedTarget = () => getInteractionsEditor().getCurrentSelectedTarget() + +export const registerElementorSelectionTracking = () => getInteractionsEditor().registerSelectionTracking() + +export const startElementorElementPicker = args => getInteractionsEditor().startElementPicker( args ) diff --git a/src/editor/hooks/use-interactions.js b/src/editor/hooks/use-interactions.js index 10d6c85..553e80e 100644 --- a/src/editor/hooks/use-interactions.js +++ b/src/editor/hooks/use-interactions.js @@ -10,6 +10,7 @@ import { domReady } from '~interact/shared/dom-ready.js' import apiFetch from '@wordpress/api-fetch' import { __ } from '@wordpress/i18n' import { ensureInteractionDefaults } from '../util' +import { getCurrentEditorPostContext, getEditorMode } from '~interact/editor/editors' const DEFAULT_STATE = { interactions: [], @@ -92,16 +93,12 @@ register( createReduxStore( 'interact/interactions', { * Whether or not the interaction should be shown in the editor based on what's * currently beign edited in the Block Editor. * - * @param {Array} interaction - * @param {Object} select wp.data.select + * @param {Array} interaction * * @return {boolean} Whether or not the interaction should be shown in the editor. */ -export const isInteractionShown = ( interaction, select ) => { - // If the editor is not available (e.g. in Widgets editor), don't do anything. - if ( ! select( 'core/editor' ) ) { - return false - } +export const isInteractionShown = interaction => { + const currentContext = getCurrentEditorPostContext() return interaction.locations.some( locationGroup => { return locationGroup.every( location => { const { @@ -113,15 +110,15 @@ export const isInteractionShown = ( interaction, select ) => { case 'page': { // If blank, then it's all posts/pages. if ( ! value || isNaN( +value ) ) { - const postType = select( 'core/editor' ).getCurrentPostType() + const postType = currentContext.postType const postTypeParam = value || param return operator === '==' ? postType === postTypeParam : postType !== postTypeParam } - const match = value.toString() === select( 'core/editor' ).getCurrentPostId()?.toString() + const match = value.toString() === currentContext.postId?.toString() return operator === '==' ? match : ! match } case 'post_type': { - const match = value.toString() === select( 'core/editor' ).getCurrentPostType()?.toString() + const match = value.toString() === currentContext.postType?.toString() return operator === '==' ? match : ! match } case 'post_status': @@ -131,20 +128,20 @@ export const isInteractionShown = ( interaction, select ) => { return true case 'post_template': case 'page_template': { - const match = value.toString() === select( 'core/editor' ).getCurrentPost()?.template.toString() + const match = value.toString() === currentContext.postTemplate?.toString() return operator === '==' ? match : ! match } case 'post_parent': case 'page_parent': { - const match = value.toString() === select( 'core/editor' ).getCurrentPost()?.parent.toString() + const match = value.toString() === currentContext.postParent?.toString() return operator === '==' ? match : ! match } case 'all': // Entire website return true case 'wp_template': // Site editor templates: home, 404, etc - const currentPostType = select( 'core/editor' ).getCurrentPostType() + const currentPostType = currentContext.postType if ( currentPostType === 'wp_template' ) { - const match = value.toString() === select( 'core/editor' ).getCurrentPostId()?.toString() + const match = value.toString() === currentContext.postId?.toString() return operator === '==' ? match : ! match } break @@ -177,7 +174,7 @@ const useInteractions = () => { const updateInteraction = newInteraction => { // Check if we updated any anchors/attributes, if we did, then we need to ask whether to also update the post. const didModifyPostContent = select( 'interact/interactions' ).didModifyPostContent() - if ( didModifyPostContent ) { + if ( didModifyPostContent && getEditorMode() !== 'elementor' ) { if ( confirm( __( 'Some block anchors have been updated for your interactions to work correctly. Do you want to save these post changes? (Any modified synced patterns will also be saved)', 'interactions' ) ) ) { // eslint-disable-line no-alert dispatch( 'interact/interactions' ).setDidModifyPostContent( false ) // Save the post. @@ -244,7 +241,7 @@ const useInteractions = () => { } const interactions = select( 'interact/interactions' ).getInteractions() - const interactionsFiltered = interactions.filter( interaction => isInteractionShown( interaction, select ) ) + const interactionsFiltered = interactions.filter( interaction => isInteractionShown( interaction ) ) return { interactions, diff --git a/src/editor/plugins/index.js b/src/editor/plugins/index.js index c448b35..1039a02 100644 --- a/src/editor/plugins/index.js +++ b/src/editor/plugins/index.js @@ -1,4 +1,8 @@ -import './block-toolbar-button' -import './top-toolbar-button' -import './block-highlight' -import './block-select' +import { editorMode } from 'interactions' + +if ( editorMode !== 'elementor' ) { + require( './block-toolbar-button' ) + require( './top-toolbar-button' ) + require( './block-highlight' ) + require( './block-select' ) +} diff --git a/src/editor/util/index.js b/src/editor/util/index.js index d34bf05..c2356e5 100644 --- a/src/editor/util/index.js +++ b/src/editor/util/index.js @@ -6,6 +6,10 @@ import { import { select, dispatch } from '@wordpress/data' import { sprintf, __ } from '@wordpress/i18n' import { addClientIdAnchorPair } from '../components/timeline/with-tracked-anchors' +import { + getCurrentEditorPostContext, + openInteractionsSidebar, +} from '~interact/editor/editors' const getUniqueTitle = title => { const interactions = select( 'interact/interactions' ).getInteractions() @@ -52,12 +56,17 @@ const getBlockNameFromAnchor = anchor => { // Returns the current page export const getLocationForCurrentPage = () => { - const currentPostType = select( 'core/editor' ).getCurrentPostType() + const { + postId, + postType, + } = getCurrentEditorPostContext() + + const currentPostType = postType let locationParam = currentPostType === 'page' ? 'page' : currentPostType === 'wp_template' ? 'wp_template' // Site editor templates : !! currentPostType ? 'post' : null - let locationValue = select( 'core/editor' ).getCurrentPostId() + let locationValue = postId if ( ! locationParam ) { locationParam = 'all' @@ -382,14 +391,4 @@ export const getOrGenerateBlockClass = ( clientId, updateAttribute = true ) => { return className } -/** - * Utility function to open the Interactions sidebar. - * - * @return {Object} Dispatch action object - */ -export const openInteractionsSidebar = () => { - if ( dispatch( 'core/edit-post' ) ) { - return dispatch( 'core/edit-post' ).openGeneralSidebar( 'interact-editor/sidebar' ) - } - return dispatch( 'core/edit-site' ).openGeneralSidebar( 'interact-editor/sidebar' ) -} +export { openInteractionsSidebar } From 5a86027c8cdd05101e9374aad8d930f630d2de59 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 15:01:42 +0800 Subject: [PATCH 02/18] fix: refactor wrappers --- src/editor/editors/elementor.js | 12 ++---------- src/editor/editors/gutenberg.js | 24 +++++++----------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/editor/editors/elementor.js b/src/editor/editors/elementor.js index 45cbf50..2dfc58d 100644 --- a/src/editor/editors/elementor.js +++ b/src/editor/editors/elementor.js @@ -43,14 +43,6 @@ class ElementorInteractionsEditor extends InteractionsEditorAbstract { return () => window.removeEventListener( 'interact/open-elementor-sidebar', openHandler ) }, [] ) - const Wrapper = ( { - children, - } ) => ( -
- { children } -
- ) - return ( <>
- +
- +
diff --git a/src/editor/editors/gutenberg.js b/src/editor/editors/gutenberg.js index 0c42d05..4edf85d 100644 --- a/src/editor/editors/gutenberg.js +++ b/src/editor/editors/gutenberg.js @@ -10,21 +10,6 @@ import { select, } from '@wordpress/data' -// Stable Gutenberg sidebar shell used to keep the shared app mounted. -const GutenbergSidebarWrapper = ( { - children, - SideBar, -} ) => ( - } - > - { children } - -) - // Gutenberg editor adapter. class GutenbergInteractionsEditor extends InteractionsEditorAbstract { getEditorMode() { @@ -53,12 +38,17 @@ class GutenbergInteractionsEditor extends InteractionsEditorAbstract { } return ( - + } + > - + ) } From 15aac8201907ae7d2834847a5fbd5279723c4371 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 15:14:03 +0800 Subject: [PATCH 03/18] fix: enqueue the needed style by elementor editor --- src/editor/editor.php | 5 +++++ src/editor/editor.scss | 13 +++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/editor/editor.php b/src/editor/editor.php index 3c9df00..b4b6c5e 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -39,6 +39,11 @@ public function enqueue_gutenberg_editor() { * @return void */ public function enqueue_elementor_editor() { + // Loads the core WordPress editor styles needed by the elementor editor UI. + // if ( wp_style_is( 'wp-components', 'registered' ) ) { + // wp_enqueue_style( 'wp-components' ); + // } + $this->enqueue_editor( 'elementor' ); } diff --git a/src/editor/editor.scss b/src/editor/editor.scss index e2e3c17..37f82b3 100644 --- a/src/editor/editor.scss +++ b/src/editor/editor.scss @@ -139,6 +139,8 @@ --wp-components-color-accent: #05f; } +/* Interaction Elementor Editor Panel Styles */ + .interact-elementor-launcher { position: fixed !important; right: 32px; @@ -148,20 +150,15 @@ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18); } -.interact-elementor-backdrop { - position: fixed; - inset: 0; - background: rgba(17, 24, 39, 0.18); - z-index: 99998; -} - .interact-elementor-panel { position: fixed; top: 0; right: 0; bottom: 0; - width: min(420px, calc(100vw - 48px)); + width: 280px; background: #fff; + color: #1e1e1e; + line-height: 18px; box-shadow: -24px 0 48px rgba(15, 23, 42, 0.18); transform: translateX(100%); transition: transform 180ms ease; From 527c3a6923f49244912c1cb9bb0f44eca9d7ff17 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 15:19:32 +0800 Subject: [PATCH 04/18] feat: laod wp-components style --- src/editor/editor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/editor.php b/src/editor/editor.php index b4b6c5e..035d2e9 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -40,9 +40,9 @@ public function enqueue_gutenberg_editor() { */ public function enqueue_elementor_editor() { // Loads the core WordPress editor styles needed by the elementor editor UI. - // if ( wp_style_is( 'wp-components', 'registered' ) ) { - // wp_enqueue_style( 'wp-components' ); - // } + if ( wp_style_is( 'wp-components', 'registered' ) ) { + wp_enqueue_style( 'wp-components' ); + } $this->enqueue_editor( 'elementor' ); } From 11367f4c26678ab4421be1dbba57d4f68931837a Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 15:20:21 +0800 Subject: [PATCH 05/18] fix: guard dismissed-error parsing against malformed localStorage --- src/editor/app.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/editor/app.js b/src/editor/app.js index e0cba9e..5f7dd0b 100644 --- a/src/editor/app.js +++ b/src/editor/app.js @@ -31,6 +31,15 @@ import { useSelect, useDispatch } from '@wordpress/data' import useOnPostPreview from './use-on-post-save' +const getDismissedErrors = () => { + try { + const dismissedErrors = JSON.parse( localStorage.getItem( 'interact-dismissed-errors' ) || '[]' ) + return Array.isArray( dismissedErrors ) ? dismissedErrors : [] + } catch ( error ) { + return [] + } +} + const InteractionsApp = ( { selectedBlockAnchor = null, enablePostPreviewGuard = true, @@ -136,7 +145,7 @@ const InteractionsApp = ( { }, [ selectedInteraction ] ) useEffect( () => { - const dismissedErrors = JSON.parse( localStorage.getItem( 'interact-dismissed-errors' ) || '[]' ) + const dismissedErrors = getDismissedErrors() const errorKey = loadingError?.interactionKey if ( ! loadingError?.interactionKey ) { @@ -196,7 +205,7 @@ const InteractionsApp = ( { variant="secondary" size="small" onClick={ () => { - const dismissedErrors = JSON.parse( localStorage.getItem( 'interact-dismissed-errors' ) || '[]' ) + const dismissedErrors = getDismissedErrors() const errorKey = loadingError?.interactionKey localStorage.setItem( 'interact-dismissed-errors', JSON.stringify( [ ...dismissedErrors, errorKey ] ) ) setIsShowingError( false ) From cf97ffbde382742577f37f41e96bfe00178717e7 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 15:30:00 +0800 Subject: [PATCH 06/18] fix: normalize imported element actions the same way as page imports --- src/editor/app.js | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/editor/app.js b/src/editor/app.js index 5f7dd0b..f263317 100644 --- a/src/editor/app.js +++ b/src/editor/app.js @@ -31,6 +31,7 @@ import { useSelect, useDispatch } from '@wordpress/data' import useOnPostPreview from './use-on-post-save' +// Get dismissed errors from localStorage with error handling. const getDismissedErrors = () => { try { const dismissedErrors = JSON.parse( localStorage.getItem( 'interact-dismissed-errors' ) || '[]' ) @@ -40,6 +41,31 @@ const getDismissedErrors = () => { } } +// Normalize imported interaction data to ensure it has the expected structure, even if some fields are missing. +const normalizeImportedInteraction = data => { + const timelines = data.timelines || [] + + return { + ...data, + timelines: timelines.map( timeline => { + const actionsToImport = timeline.actions || [] + const actions = actionsToImport.map( action => ( + createNewAction( { + actionType: action.type ?? '', + start: action.timing?.start ?? 0, + targetType: action.target?.type ?? '', + props: { ...action }, + } ) + ) ) + + return { + ...timeline, + actions, + } + } ), + } +} + const InteractionsApp = ( { selectedBlockAnchor = null, enablePostPreviewGuard = true, @@ -262,7 +288,9 @@ const InteractionsApp = ( { ) }

), importLabel: __( 'Import interaction', 'interactions' ), - onImport: onAddInteractionHandler, + onImport: ( type, target, data ) => { + onAddInteractionHandler( type, target, normalizeImportedInteraction( data ) ) + }, } ) } /> ), importLabel: __( 'Import interaction', 'interactions' ), onImport: ( type, target, data ) => { - const timelines = data.timelines || [] - timelines.forEach( ( timeline, i ) => { - const actionsToImport = timeline.actions || [] - const newActions = actionsToImport.map( action => ( - createNewAction( { - actionType: action.type ?? '', - start: action.timing?.start ?? 0, - targetType: action.target?.type ?? '', - props: { ...action }, - } ) - ) ) - data.timelines[ i ] = { ...timeline, actions: newActions } - } ) - - onAddInteractionHandler( type, target, data ) + onAddInteractionHandler( type, target, normalizeImportedInteraction( data ) ) }, } ) } /> From ed9014d4dfc62ab449dad2772583bf2e2e68bbfc Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 15:48:16 +0800 Subject: [PATCH 07/18] fix: run getCurrentSelectedTarget once --- .../components/add-interaction-popover/index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/editor/components/add-interaction-popover/index.js b/src/editor/components/add-interaction-popover/index.js index 38e1210..74f3ed8 100644 --- a/src/editor/components/add-interaction-popover/index.js +++ b/src/editor/components/add-interaction-popover/index.js @@ -73,11 +73,16 @@ const AddInteractionPopover = props => { } } ) - const [ target, setTarget ] = useState( { - type: getCurrentSelectedTarget()?.type || ( isElementor ? 'selector' : 'block' ), - value: getCurrentSelectedTarget()?.value || getOrGenerateBlockAnchor( getSelectedBlockClientId(), false ) || '', - blockName: getCurrentSelectedTarget()?.blockName || first( getBlockNamesByClientId( getSelectedBlockClientId() ) ) || '', - options: getCurrentSelectedTarget()?.options || '', + const [ target, setTarget ] = useState( () => { + const current = getCurrentSelectedTarget() + const clientId = getSelectedBlockClientId() + + return { + type: current?.type || ( isElementor ? 'selector' : 'block' ), + value: current?.value || getOrGenerateBlockAnchor( clientId, false ) || '', + blockName: current?.blockName || first( getBlockNamesByClientId( clientId ) ) || '', + options: current?.options || '', + } } ) const libraryTitle = ! showElementOption && showPageOption ? __( 'My Page Interactions', 'interactions' ) From ecc4b53d0dc17784a9c91dc58774d293d1ccbb5f Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Fri, 8 May 2026 16:01:45 +0800 Subject: [PATCH 08/18] fix: is_admin might prevent elementor enqueue --- src/editor/editor.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/editor.php b/src/editor/editor.php index 035d2e9..75224fd 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -19,9 +19,10 @@ class Interact_Editor { function __construct() { if ( is_admin() ) { add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_gutenberg_editor' ) ); - add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue_elementor_editor' ) ); add_action( 'enqueue_block_assets', array( $this, 'enqueue_assets' ) ); } + ++ add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue_elementor_editor' ) ); } /** From 79ebfcbd06cf9a277ebfefa9316b1a5ab2987ac1 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Mon, 11 May 2026 11:04:32 +0800 Subject: [PATCH 09/18] fix: patch elementor.js --- src/editor/editors/elementor.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/editor/editors/elementor.js b/src/editor/editors/elementor.js index 2dfc58d..f8097c4 100644 --- a/src/editor/editors/elementor.js +++ b/src/editor/editors/elementor.js @@ -96,7 +96,7 @@ class ElementorInteractionsEditor extends InteractionsEditorAbstract { // Return the Elementor preview canvas document. getCanvasDocument() { const iframe = document.querySelector( '#elementor-preview-iframe' ) - return iframe?.contentDocument || document + return iframe?.contentDocument || null } // Open the Interactions sidebar in Elementor. @@ -178,12 +178,13 @@ class ElementorInteractionsEditor extends InteractionsEditorAbstract { } ) } - [ + const actions = [ 'panel/open_editor/section', 'panel/open_editor/column', 'panel/open_editor/container', 'panel/open_editor/widget', - ].forEach( register ) + ] + actions.forEach( register ) return NOOP } @@ -260,11 +261,13 @@ class ElementorInteractionsEditor extends InteractionsEditorAbstract { clearHighlight() previewDocument.removeEventListener( 'mousemove', mouseMoveHandler, true ) previewDocument.removeEventListener( 'click', clickHandler, true ) + previewDocument.removeEventListener( 'keydown', keyHandler, true ) document.removeEventListener( 'keydown', keyHandler, true ) } previewDocument.addEventListener( 'mousemove', mouseMoveHandler, true ) previewDocument.addEventListener( 'click', clickHandler, true ) + previewDocument.addEventListener( 'keydown', keyHandler, true ) document.addEventListener( 'keydown', keyHandler, true ) return stop From 21d65a8a88c9dc7c41ddd78ab6cefed1a67ea9af Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Mon, 11 May 2026 11:27:14 +0800 Subject: [PATCH 10/18] fix: generate wp components based on imported components --- package-lock.json | 1137 ++++++++++++++++++++++-- package.json | 22 +- scripts/generate-wp-components-css.mjs | 179 ++++ src/editor/editor.php | 13 +- 4 files changed, 1249 insertions(+), 102 deletions(-) create mode 100644 scripts/generate-wp-components-css.mjs diff --git a/package-lock.json b/package-lock.json index 90050bb..77813be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "interactions", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "interactions", - "version": "1.3.2", + "version": "1.3.3", "license": "GPL-2.0-or-later", "dependencies": { + "@wordpress/components": "^33.0.0", "@wordpress/icons": "^10.31.0", "canvas-confetti": "^1.9.3", "classnames": "^2.5.1", @@ -53,11 +54,48 @@ "node": ">=6.0.0" } }, + "node_modules/@ariakit/core": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.20.tgz", + "integrity": "sha512-DJbUnui0fM+2ZgiWLOMuFOmlWSJDNV3f6tqghIYRTWEm51TN/LoU6uM8og6/g7Nrwl4Uo5l8AoQT9Kkr/i/uRg==", + "license": "MIT" + }, + "node_modules/@ariakit/react": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.26.tgz", + "integrity": "sha512-NcoPrYE4vgwyODAhdpNNuA7ldwODDuFqZl6jORPVDY3l+oRjl/OYwtQyyC3ZhC/4mjntYBYuKKrPJEizLmoxpg==", + "license": "MIT", + "dependencies": { + "@ariakit/react-core": "0.4.26" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@ariakit/react-core": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.26.tgz", + "integrity": "sha512-/Peh1KiVpjj79nCJIa6lEdzSTT9P9FZoy+CxByIFKL3YKdlXmDIIhS1E/tAqKbDq4ODVdynnqmrIDxE5wCoZYw==", + "license": "MIT", + "dependencies": { + "@ariakit/core": "0.4.20", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -128,7 +166,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", @@ -226,7 +263,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -248,7 +284,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -346,7 +381,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -355,7 +389,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -400,7 +433,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "dependencies": { "@babel/types": "^7.28.4" }, @@ -1874,7 +1906,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -1888,7 +1919,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1906,7 +1936,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -2022,6 +2051,18 @@ "@csstools/css-tokenizer": "^3.0.1" } }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, + "node_modules/@date-fns/utc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.1.tgz", + "integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA==", + "license": "MIT" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2041,6 +2082,180 @@ "url": "https://github.com/sponsors/JounQin" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", @@ -2166,6 +2381,44 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", @@ -2709,7 +2962,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -2729,7 +2981,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2747,14 +2998,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -4401,6 +4650,52 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tabby_ai/hijri-converter": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tabby_ai/hijri-converter/-/hijri-converter-1.0.5.tgz", + "integrity": "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@tannin/compile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz", + "integrity": "sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==", + "license": "MIT", + "dependencies": { + "@tannin/evaluate": "^1.2.0", + "@tannin/postfix": "^1.1.0" + } + }, + "node_modules/@tannin/evaluate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.2.0.tgz", + "integrity": "sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg==", + "license": "MIT" + }, + "node_modules/@tannin/plural-forms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.1.0.tgz", + "integrity": "sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw==", + "license": "MIT", + "dependencies": { + "@tannin/compile": "^1.1.0" + } + }, + "node_modules/@tannin/postfix": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.1.0.tgz", + "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==", + "license": "MIT" + }, + "node_modules/@tannin/sprintf": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@tannin/sprintf/-/sprintf-1.3.3.tgz", + "integrity": "sha512-RwARl+hFwhzy0tg9atWcchLFvoQiOh4rrP7uG2N5E4W80BPCUX0ElcUR9St43fxB9EfjsW2df9Qp+UsTbvQDjA==", + "license": "MIT" + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -4575,6 +4870,18 @@ "@types/node": "*" } }, + "node_modules/@types/gradient-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-1.1.0.tgz", + "integrity": "sha512-SaEcbgQscHtGJ1QL+ajgDTmmqU2f6T+00jZRcFlVHUW2Asivc84LNUev/UQFyu117AsdyrtI+qpwLvgjJXJxmw==", + "license": "MIT" + }, + "node_modules/@types/highlight-words-core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.1.tgz", + "integrity": "sha512-9VZUA5omXBfn+hDxFjUDu1FOJTBM3LmvqfDey+Z6Aa8B8/JmF5SMj6FBrjfgJ/Q3YXOZd3qyTDfJyMZSs/wCUA==", + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -4649,6 +4956,12 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, + "node_modules/@types/mousetrap": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz", + "integrity": "sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==", + "license": "MIT" + }, "node_modules/@types/mysql": { "version": "2.15.26", "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", @@ -4685,8 +4998,7 @@ "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "dev": true + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/pg": { "version": "8.6.1", @@ -4726,12 +5038,13 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.3.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", - "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { @@ -5098,6 +5411,24 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -5288,6 +5619,20 @@ } } }, + "node_modules/@wordpress/a11y": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.45.0.tgz", + "integrity": "sha512-KOgdBsZP34nAi+UfrhIAZDt2I1ZDb3DXAgIeQk7QxTIc9OlQKMNfrYwPG0jidgfKwmjFxh8vV8HbZcBzTD29Rw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/dom-ready": "^4.45.0", + "@wordpress/i18n": "^6.18.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/babel-preset-default": { "version": "8.31.0", "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-8.31.0.tgz", @@ -5361,6 +5706,199 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/components": { + "version": "33.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-33.0.0.tgz", + "integrity": "sha512-VeLDtfz8612bdRqgQiSMtIIEGDi4ZByj0XUvjT7E6RVLgczQyV9DTpGOPyL6PbTyAluIx6hjt9bzsaC+bM6G+w==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@ariakit/react": "^0.4.22", + "@date-fns/utc": "^2.1.1", + "@emotion/cache": "^11.14.0", + "@emotion/css": "^11.13.5", + "@emotion/react": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/styled": "^11.14.1", + "@emotion/utils": "^1.4.2", + "@floating-ui/react-dom": "2.0.8", + "@types/gradient-parser": "1.1.0", + "@types/highlight-words-core": "1.2.1", + "@types/react": "^18.3.27", + "@use-gesture/react": "^10.3.1", + "@wordpress/a11y": "^4.45.0", + "@wordpress/base-styles": "^7.0.0", + "@wordpress/compose": "^7.45.0", + "@wordpress/date": "^5.45.0", + "@wordpress/deprecated": "^4.45.0", + "@wordpress/dom": "^4.45.0", + "@wordpress/element": "^6.45.0", + "@wordpress/escape-html": "^3.45.0", + "@wordpress/hooks": "^4.45.0", + "@wordpress/html-entities": "^4.45.0", + "@wordpress/i18n": "^6.18.0", + "@wordpress/icons": "^13.0.0", + "@wordpress/is-shallow-equal": "^5.45.0", + "@wordpress/keycodes": "^4.45.0", + "@wordpress/primitives": "^4.45.0", + "@wordpress/private-apis": "^1.45.0", + "@wordpress/rich-text": "^7.45.0", + "@wordpress/warning": "^3.45.0", + "change-case": "^4.1.2", + "clsx": "^2.1.1", + "colord": "^2.7.0", + "csstype": "^3.2.3", + "date-fns": "^3.6.0", + "deepmerge": "^4.3.0", + "fast-deep-equal": "^3.1.3", + "framer-motion": "^11.15.0", + "gradient-parser": "1.1.1", + "highlight-words-core": "^1.2.2", + "is-plain-object": "^5.0.0", + "memize": "^2.1.0", + "path-to-regexp": "^6.2.1", + "re-resizable": "^6.4.0", + "react-colorful": "^5.6.1", + "react-day-picker": "^9.7.0", + "remove-accents": "^0.5.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@wordpress/components/node_modules/@wordpress/base-styles": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-7.0.0.tgz", + "integrity": "sha512-Q0BbZzfeYbQZKHnyNT4RF8RGVugN5jStGtpRKhBYQW7ut7sS61LbbpP7jR0D0sDPYoEEC8jKZQSZwSM23B4jow==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/components/node_modules/@wordpress/icons": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-13.0.0.tgz", + "integrity": "sha512-+CLbvNdzMUHxQK5I6gFdHb3X6EVAH6SOSIj0xtMWm6PZO+Nnf7tXHfNBuxqTnGfxT5grtfb6D3A9ZMBU+Tpv+Q==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/element": "^6.45.0", + "@wordpress/primitives": "^4.45.0", + "change-case": "4.1.2" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/components/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/@wordpress/components/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/@wordpress/components/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@wordpress/compose": { + "version": "7.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-7.45.0.tgz", + "integrity": "sha512-/keWdRFUe7bnzh2ZtOYLexknpj0K0G56WFw7RLZehl54a9EmzjYjAODBOF9DB3c07pJuNuy7c5QgqMPi0cqLlw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@types/mousetrap": "^1.6.8", + "@wordpress/deprecated": "^4.45.0", + "@wordpress/dom": "^4.45.0", + "@wordpress/element": "^6.45.0", + "@wordpress/is-shallow-equal": "^5.45.0", + "@wordpress/keycodes": "^4.45.0", + "@wordpress/priority-queue": "^3.45.0", + "@wordpress/undo-manager": "^1.45.0", + "change-case": "^4.1.2", + "mousetrap": "^1.6.5", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/data": { + "version": "10.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.45.0.tgz", + "integrity": "sha512-OR/uMpcEbCh1aBkbzateXffNrL829M+N92qtuD+Gt08Mey129WIEVR9kBC2Tf02VtXs644OKZD6cz77KlxH8XA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/compose": "^7.45.0", + "@wordpress/deprecated": "^4.45.0", + "@wordpress/element": "^6.45.0", + "@wordpress/is-shallow-equal": "^5.45.0", + "@wordpress/priority-queue": "^3.45.0", + "@wordpress/private-apis": "^1.45.0", + "@wordpress/redux-routine": "^5.45.0", + "deepmerge": "^4.3.0", + "equivalent-key-map": "^0.2.2", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "redux": "^5.0.1", + "rememo": "^4.0.2", + "use-memo-one": "^1.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@wordpress/date": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/date/-/date-5.45.0.tgz", + "integrity": "sha512-34v3hCxn68kYzWs8bhuAt8cfMxdFX9ukKn3a3FB+tAJXpxafnPCcZoWfJHn4I8hepCbreFrf3UiGdA+id2kQ4A==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/deprecated": "^4.45.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.40" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { "version": "6.31.0", "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.31.0.tgz", @@ -5383,6 +5921,42 @@ "integrity": "sha512-dnSoUiLAoVaMXxFsVi4CrPVYMKOuDBXTghXSmMINX44RZ8WM9cXlY7UqrQnlAcODCVO7FV3+8t/5nDKAjimLfg==", "dev": true }, + "node_modules/@wordpress/deprecated": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.45.0.tgz", + "integrity": "sha512-qer/fk/lgmmisb8/hj1xZtsbJbZhCoOblhyxI2k7RRul7rQDdk+fm28LJYV+eIF0ldSVX30f4dmz1pvcVHQEEg==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/hooks": "^4.45.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/dom": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.45.0.tgz", + "integrity": "sha512-6RObr/KEZS1FnZwpcDAsKlJ3qw2KLF5+A/LsxlM9fSWDGSO05CEaTp+VmWgx9pwjQWbPEa7N73ijEy8cCNSZWA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/deprecated": "^4.45.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/dom-ready": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-4.45.0.tgz", + "integrity": "sha512-0lFImpg9DGXcGCDQePdoU8haz7QYsKOFXUMTpRvi/Te38LFXzgZtOUBQbY8fRBlLxrgrj4FsAIc7bzdLn73wNQ==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/e2e-test-utils-playwright": { "version": "1.31.0", "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.31.0.tgz", @@ -5405,14 +5979,14 @@ } }, "node_modules/@wordpress/element": { - "version": "6.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.31.0.tgz", - "integrity": "sha512-KOier6Y4b4Y5yEV1GYen81R9gCEOvJT6eVbsc93w2fFEKi2FK/oI7IKzGv9GeJMkoCWvTSX6C/ZYTWk6fCUfeA==", + "version": "6.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.45.0.tgz", + "integrity": "sha512-WFrGNPEnj8uE+XhFW9NVbxvqraYpConaEokLv9IszFYVfyg8juXSQcHOAfEnxjC08HBPfVcayr2igu/XUgGOAw==", + "license": "GPL-2.0-or-later", "dependencies": { - "@babel/runtime": "7.25.7", - "@types/react": "^18.2.79", - "@types/react-dom": "^18.2.25", - "@wordpress/escape-html": "^3.31.0", + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.1", + "@wordpress/escape-html": "^3.45.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.3.0", @@ -5424,12 +5998,10 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.31.0.tgz", - "integrity": "sha512-9g9qd7Q16PWDeYEa2dU+84d1SvjP4LfS7n7AuXkwl5+F7KfL2nZTmDTHWutw9jVjdDAGmjm1VNIj4ydQk9vaLA==", - "dependencies": { - "@babel/runtime": "7.25.7" - }, + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.45.0.tgz", + "integrity": "sha512-IW4mnA+65XKhABuBkwrQNAlbq97luC6ZIBfdSq0Tkq+AFPqE1lJTMlLo7iBkTpsHsBLyznViPXultq40fz8L7w==", + "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", "npm": ">=8.19.2" @@ -5478,6 +6050,46 @@ } } }, + "node_modules/@wordpress/hooks": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.45.0.tgz", + "integrity": "sha512-+gOlu8TdohqL1INQNxS/7CxhM4T4MuYnKietWV9zWDmNQV2ysM0SdamNk5pWERJ4w0yY9XhtMBcwR/piJtePZg==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/html-entities": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/html-entities/-/html-entities-4.45.0.tgz", + "integrity": "sha512-7W95xaOv4UgMSWlEmyO7YkBsUae3QlQu3GKENVH7Pt/osbJGSPInAJ1ruO4oeUwGPygWOL7b7IzRsgTNP0M/Wg==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/i18n": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.18.0.tgz", + "integrity": "sha512-6dYCih4wUwi7Csu4RNfHiAKkgWhpSQdl8YthvQUF59Sfsoia3RCdtd4K2l7W4f18ldFA/RXjShMjvSexWy6OyQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.45.0", + "gettext-parser": "^1.3.1", + "memize": "^2.1.0", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/icons": { "version": "10.31.0", "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.31.0.tgz", @@ -5492,6 +6104,16 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/is-shallow-equal": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.45.0.tgz", + "integrity": "sha512-saamGjAuhZOiFOyznsriPGrO8GRDremImMO4q92qjQqmDqssC+FRDQnwr9D8BaedSnVvUDcriGeYBObEEnIJ2A==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/jest-console": { "version": "8.31.0", "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.31.0.tgz", @@ -5527,6 +6149,19 @@ "jest": ">=29" } }, + "node_modules/@wordpress/keycodes": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.45.0.tgz", + "integrity": "sha512-N+Wp572xZovLM45cYo6HfUNTQNDfEqakAYIOcY8bUqA2iFelN6AUkNfUIkIxmrE0EqkQAQ5odES03g8ym7e1IA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/i18n": "^6.18.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/npm-package-json-lint-config": { "version": "5.31.0", "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.31.0.tgz", @@ -5571,12 +6206,12 @@ } }, "node_modules/@wordpress/primitives": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.31.0.tgz", - "integrity": "sha512-cY4EKYQRqHu9NZuoWchxc/KWiofwGskzxz0oCfgbdkRlfTag8yBjWMayz+fRNaenw0l5pzLyIg3rcNDN8xLezw==", + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.45.0.tgz", + "integrity": "sha512-x+i6EKUvz96EkUb2KuBTLNGm8d5+ZS0FYjUEnIhp5dtWxjMe8dJT6LS+n363vg+K28LVvjptiTAaByccnNKc9w==", + "license": "GPL-2.0-or-later", "dependencies": { - "@babel/runtime": "7.25.7", - "@wordpress/element": "^6.31.0", + "@wordpress/element": "^6.45.0", "clsx": "^2.1.1" }, "engines": { @@ -5587,6 +6222,74 @@ "react": "^18.0.0" } }, + "node_modules/@wordpress/priority-queue": { + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.45.0.tgz", + "integrity": "sha512-0sIX2PRPzo5nk252f60xpPj3/BUZxEOLcabCC7FuvQDYPGZrRyS6Dy0vDDzozZxHGuUYCT65t8ubBwXx37wXCw==", + "license": "GPL-2.0-or-later", + "dependencies": { + "requestidlecallback": "^0.3.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/private-apis": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.45.0.tgz", + "integrity": "sha512-UjhIDpoyKKUghPM0tkqd5Whsuk4kqfAfhb5VYGoEYtunDs0rB8IxgFO7hE0PhimHL74QVgaJOlprRZVRCCoQ6w==", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/redux-routine": { + "version": "5.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-5.45.0.tgz", + "integrity": "sha512-6ShpBns4jIBFXrYFBcKA5pnFm/kjr1SqFvLj5DwLgMV61eI3Rr9LyZwIzNR2BGg067ryxu4W172Uqjke/mZjcQ==", + "license": "GPL-2.0-or-later", + "dependencies": { + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "rungen": "^0.3.2" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "redux": ">=4" + } + }, + "node_modules/@wordpress/rich-text": { + "version": "7.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.45.0.tgz", + "integrity": "sha512-C5+JQqNzA3fiQq0hN9pQPKsjcwO/fczouHqubq3847kAUrClROqqI1GJHE34WLl1Vp+/tWQuBkIjQ/95olKteA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/a11y": "^4.45.0", + "@wordpress/compose": "^7.45.0", + "@wordpress/data": "^10.45.0", + "@wordpress/deprecated": "^4.45.0", + "@wordpress/dom": "^4.45.0", + "@wordpress/element": "^6.45.0", + "@wordpress/escape-html": "^3.45.0", + "@wordpress/i18n": "^6.18.0", + "@wordpress/keycodes": "^4.45.0", + "@wordpress/private-apis": "^1.45.0", + "colord": "2.9.3", + "memize": "^2.1.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@wordpress/scripts": { "version": "30.24.0", "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-30.24.0.tgz", @@ -5777,11 +6480,24 @@ "stylelint-scss": "^6.4.0" } }, + "node_modules/@wordpress/undo-manager": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.45.0.tgz", + "integrity": "sha512-BqclZIPjzBYIjLqLZFihs+Ce+w+yBQuj44VYSrRDOj56AbMtwmClIUqgIVBZAe2En/2ncixTTWOZG9KluvEXfA==", + "license": "GPL-2.0-or-later", + "dependencies": { + "@wordpress/is-shallow-equal": "^5.45.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/warning": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.31.0.tgz", - "integrity": "sha512-Npw1Apa6r+K+jtX40ABWAXv7J1bVnOi6h9VPiMY8l/iZoRHBXao8HTgQnIoCm+GzymaQs6NQoH4X8UAClggeXA==", - "dev": true, + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.45.0.tgz", + "integrity": "sha512-NQ9tAhPdwhfceVIzWra1rbumvgAFAEDTgZlWsX880zLiq1F8JTwBouwW6wfIhA3XLcY6Yj7cBBYLa8vnNiDZDw==", + "license": "GPL-2.0-or-later", "engines": { "node": ">=18.12.0", "npm": ">=8.19.2" @@ -6547,6 +7263,21 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", @@ -7046,7 +7777,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -7415,8 +8145,7 @@ "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, "node_modules/colorette": { "version": "2.0.20", @@ -7760,7 +8489,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -8093,9 +8821,10 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, "node_modules/cwd": { "version": "0.10.0", @@ -8206,6 +8935,12 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -8216,7 +8951,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -8302,7 +9036,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8655,6 +9388,15 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -8723,11 +9465,16 @@ "node": ">=4" } }, + "node_modules/equivalent-key-map": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/equivalent-key-map/-/equivalent-key-map-0.2.2.tgz", + "integrity": "sha512-xvHeyCDbZzkpN4VHQj/n+j2lOwL0VWszG30X4cOrc9Y7Tuo2qCdZK/0AMod23Z5dCtNUbaju6p0rwOhHUk05ew==", + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -8935,7 +9682,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -10049,8 +10795,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -10350,6 +11095,12 @@ "find-process": "bin/find-process.js" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -10524,6 +11275,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -10572,7 +11350,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10737,6 +11514,16 @@ "node": ">= 14" } }, + "node_modules/gettext-parser": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz", + "integrity": "sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA==", + "license": "MIT", + "dependencies": { + "encoding": "^0.1.12", + "safe-buffer": "^5.1.1" + } + }, "node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -10902,6 +11689,14 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/gradient-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gradient-parser/-/gradient-parser-1.1.1.tgz", + "integrity": "sha512-Hu0YfNU+38EsTmnUfLXUKFMXq9yz7htGYpF4x+dlbBhUCvIvzLt0yVLT/gJRmvLKFJdqNFrz4eKkIUjIXSr7Tw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -11017,7 +11812,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -11034,6 +11828,27 @@ "tslib": "^2.0.3" } }, + "node_modules/highlight-words-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz", + "integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==", + "license": "MIT" + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -11291,7 +12106,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -11390,7 +12204,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -11406,7 +12219,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -11577,8 +12389,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -11679,7 +12490,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "dependencies": { "hasown": "^2.0.2" }, @@ -11903,6 +12713,12 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -12992,7 +13808,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -13009,8 +13824,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -13416,8 +14230,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/linkify-it": { "version": "3.0.3", @@ -13844,6 +14657,12 @@ "node": ">= 4.0.0" } }, + "node_modules/memize": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/memize/-/memize-2.1.1.tgz", + "integrity": "sha512-8Nl+i9S5D6KXnruM03Jgjb+LwSupvR13WBr4hJegaaEyobvowCVupi79y2WSiWvO1mzBWxPwEYE5feCe8vyA5w==", + "license": "MIT" + }, "node_modules/meow": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", @@ -14145,6 +14964,48 @@ "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "dev": true }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==", + "license": "Apache-2.0 WITH LLVM-exception" + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -14157,8 +15018,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -14899,7 +15759,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -14917,7 +15776,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -15021,8 +15879,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.11.1", @@ -15065,7 +15922,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -15110,8 +15966,7 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -16359,6 +17214,16 @@ "node": ">=0.10.0" } }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -16370,6 +17235,48 @@ "node": ">=0.10.0" } }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-day-picker": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.14.0.tgz", + "integrity": "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "@tabby_ai/hijri-converter": "1.0.5", + "date-fns": "^4.1.0", + "date-fns-jalali": "4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-day-picker/node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -16546,6 +17453,12 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -16658,6 +17571,24 @@ "node": ">=6" } }, + "node_modules/rememo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rememo/-/rememo-4.0.2.tgz", + "integrity": "sha512-NVfSP9NstE3QPNs/TnegQY0vnJnstKQSpcrsI2kBTB3dB2PkdfKdTa+abbjMIDqpc63fE5LfjLgfMst0ULMFxQ==", + "license": "MIT" + }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, + "node_modules/requestidlecallback": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/requestidlecallback/-/requestidlecallback-0.3.0.tgz", + "integrity": "sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ==", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -16709,7 +17640,6 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", @@ -16929,6 +17859,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rungen": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/rungen/-/rungen-0.3.2.tgz", + "integrity": "sha512-zWl10xu2D7zoR8zSC2U6bg5bYF6T/Wk7rxwp8IPaJH7f0Ge21G03kNHVgHR7tyVkSSfAOG0Rqf/Cl38JftSmtw==", + "license": "MIT" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -16961,7 +17897,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -17013,8 +17948,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { "version": "1.92.1", @@ -18581,6 +19515,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18613,7 +19553,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -18745,6 +19684,15 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/tannin": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.2.0.tgz", + "integrity": "sha512-U7GgX/RcSeUETbV7gYgoz8PD7Ni4y95pgIP/Z6ayI3CfhSujwKEBlGFTCRN+Aqnuyf4AN2yHL+L8x+TCGjb9uA==", + "license": "MIT", + "dependencies": { + "@tannin/plural-forms": "^1.1.0" + } + }, "node_modules/tapable": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", @@ -19555,6 +20503,24 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -20314,7 +21280,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index 65b5d93..d652d9f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "build": "node scripts/update-build-type.js free && node scripts/sync-version.js ${npm_config_suffix} && npx wp-scripts build && npm run build:css && npm run build:frontend-php && npm run optimize-videos && BUILD_TYPE=free npm run package ${npm_config_suffix}", "build:frontend-php": "node scripts/generate-frontend-php-scripts.mjs production free", "build:frontend-php:premium": "node scripts/generate-frontend-php-scripts.mjs production premium", - "build:css": "webpack --config webpack.css.config.js && npm run clean:css-js", + "build:css": "webpack --config webpack.css.config.js && npm run build:css:wp-components && npm run clean:css-js", + "build:css:wp-components": "node scripts/generate-wp-components-css.mjs && npx sass build/wp-components.scss dist/wp-components.css --load-path=node_modules --no-source-map", "clean:css-js": "rm -f dist/*.css.js", "start": "node scripts/update-build-type.js free && concurrently \"wp-scripts start\" \"webpack --config webpack.css.config.js --watch\"", "lint:js": "wp-scripts lint-js", @@ -34,28 +35,29 @@ "extends @wordpress/browserslist-config" ], "devDependencies": { + "@node-minify/core": "^8.0.6", + "@node-minify/uglify-js": "^8.0.6", + "@svgr/webpack": "^8.1.0", + "@wordpress/babel-preset-default": "^8.19.0", "@wordpress/eslint-plugin": "^22.15.0", "@wordpress/prettier-config": "^4.29.0", "@wordpress/scripts": "^30.0.0", "@wordpress/stylelint-config": "^23.21.0", - "@wordpress/babel-preset-default": "^8.19.0", - "@svgr/webpack": "^8.1.0", - "@node-minify/core": "^8.0.6", - "@node-minify/uglify-js": "^8.0.6", + "archiver": "^6.0.1", "babel-loader": "^9.2.1", "concurrently": "^8.2.2", + "eslint": "^8.57.0", + "eslint-plugin-compat": "^6.0.2", "file-loader": "^6.2.0", "fluent-ffmpeg": "^2.1.3", "mini-css-extract-plugin": "^2.7.6", + "prettier": "^3.3.3", "sass": "^1.69.0", "sass-loader": "^14.0.0", - "style-loader": "^3.3.3", - "archiver": "^6.0.1", - "eslint": "^8.57.0", - "eslint-plugin-compat": "^6.0.2", - "prettier": "^3.3.3" + "style-loader": "^3.3.3" }, "dependencies": { + "@wordpress/components": "^33.0.0", "@wordpress/icons": "^10.31.0", "canvas-confetti": "^1.9.3", "classnames": "^2.5.1", diff --git a/scripts/generate-wp-components-css.mjs b/scripts/generate-wp-components-css.mjs new file mode 100644 index 0000000..16ed570 --- /dev/null +++ b/scripts/generate-wp-components-css.mjs @@ -0,0 +1,179 @@ +import fs from 'fs' +import path from 'path' +import globPackage from 'glob' +import babelParser from '@babel/parser' + +const globSync = globPackage.sync +const { parse } = babelParser + +const ROOT = process.cwd() +const SOURCE_GLOB = 'src/**/*.js' +const OUTPUT_SCSS = path.join( ROOT, 'build', 'wp-components.scss' ) +const OUTPUT_REPORT = path.join( ROOT, 'build', 'wp-components-report.json' ) + +// Shared styles that are not imported directly, but are needed by some of the +// component styles that we do include. +const SHARED_STYLE_ENTRIES = [ + 'button-group', + 'form-toggle', + 'tip', + 'validated-form-controls', +] + +const EXPORT_TO_STYLE_ENTRY = { + BaseControl: 'base-control', + Button: 'button', + ColorIndicator: 'color-indicator', + ColorPicker: 'color-picker', + ComboboxControl: 'combobox-control', + Dashicon: 'dashicon', + Dropdown: 'dropdown', + FlexItem: 'flex', + MenuGroup: 'menu-group', + MenuItem: 'menu-item', + MenuItemsChoice: 'menu-items-choice', + Modal: 'modal', + Notice: 'notice', + PanelBody: 'panel', + Popover: 'popover', + RangeControl: 'range-control', + SearchControl: 'search-control', + SelectControl: 'select-control', + Spinner: 'spinner', + TextControl: 'text-control', + TextareaControl: 'textarea-control', + ToggleControl: 'toggle-control', + ToolbarButton: 'toolbar/toolbar-button', + ToolbarGroup: 'toolbar/toolbar-group', + Tooltip: 'tooltip', + __experimentalHStack: 'h-stack', + __experimentalNumberControl: 'number-control', + __experimentalToggleGroupControl: 'toggle-group-control', + __experimentalToggleGroupControlOption: 'toggle-group-control', +} + +const toNamespace = entry => { + return 'wpComponents' + entry + .replace( /[^a-zA-Z0-9]+(.)/g, ( _, char ) => char.toUpperCase() ) + .replace( /^(.)/, char => char.toUpperCase() ) +} + +const findStyleFile = entry => { + return path.join( + ROOT, + 'node_modules', + '@wordpress', + 'components', + 'src', + entry, + 'style.scss' + ) +} + +const files = globSync( SOURCE_GLOB, { cwd: ROOT, absolute: true } ) +const usedExports = new Set() + +// Scan every JS module in the codebase and collect the named exports imported +// from @wordpress/components so the generated stylesheet only includes the +// component styles we actually reference. +for ( const file of files ) { + const source = fs.readFileSync( file, 'utf8' ) + const ast = parse( source, { + sourceType: 'module', + plugins: [ 'jsx' ], + } ) + + for ( const node of ast.program.body ) { + if ( node.type !== 'ImportDeclaration' || node.source.value !== '@wordpress/components' ) { + continue + } + + for ( const specifier of node.specifiers ) { + if ( specifier.type === 'ImportSpecifier' ) { + usedExports.add( specifier.imported.name ) + } + } + } +} + +const styleEntries = new Set() +const missingExports = [] + +// Map every discovered component import to its source Sass entry when one is +// available in the package. Components that do not ship a standalone style.scss +// are reported so we can keep track of the remaining gaps. +for ( const componentExport of [ ...usedExports ].sort() ) { + const styleEntry = EXPORT_TO_STYLE_ENTRY[ componentExport ] + if ( ! styleEntry ) { + missingExports.push( { + export: componentExport, + reason: 'No style entry mapping defined.', + } ) + continue + } + + if ( ! fs.existsSync( findStyleFile( styleEntry ) ) ) { + missingExports.push( { + export: componentExport, + styleEntry, + reason: 'Mapped style entry has no style.scss file.', + } ) + continue + } + + styleEntries.add( styleEntry ) +} + +// Some imported components depend on shared styles that are not imported +// directly, so add them explicitly to keep the reduced bundle stable. +for ( const sharedEntry of SHARED_STYLE_ENTRIES ) { + if ( fs.existsSync( findStyleFile( sharedEntry ) ) ) { + styleEntries.add( sharedEntry ) + } +} + +fs.mkdirSync( path.dirname( OUTPUT_SCSS ), { recursive: true } ) + +// Generate a thin Sass entrypoint that re-exports the discovered component +// styles. Sass requires each @use to have a unique namespace, even when we only +// care about the emitted CSS side effects. +const scssContents = [ + '// Auto-generated by scripts/generate-wp-components-css.mjs', + '// Reduced bundle for @wordpress/components styles used in this codebase.', + '// Do not edit manually.', + '', + '@use "@wordpress/base-styles/mixins" as *;', + '', + ...Array.from( styleEntries ) + .sort() + .map( entry => `@use "@wordpress/components/src/${ entry }/style" as ${ toNamespace( entry ) };` ), + '', +].join( '\n' ) + +fs.writeFileSync( OUTPUT_SCSS, scssContents ) + +const report = { + generatedAt: new Date().toISOString(), + scannedPattern: SOURCE_GLOB, + sharedStyleEntries: SHARED_STYLE_ENTRIES, + usedExports: Array.from( usedExports ).sort(), + styleEntries: Array.from( styleEntries ).sort(), + missingExports, + outputScss: path.relative( ROOT, OUTPUT_SCSS ), +} + +fs.writeFileSync( OUTPUT_REPORT, JSON.stringify( report, null, 2 ) + '\n' ) + +process.stdout.write( + JSON.stringify( + { + outputScss: path.relative( ROOT, OUTPUT_SCSS ), + outputReport: path.relative( ROOT, OUTPUT_REPORT ), + usedExports: report.usedExports.length, + styleEntries: report.styleEntries.length, + missingExports: missingExports.length, + }, + null, + 2 + ) + '\n' +) diff --git a/src/editor/editor.php b/src/editor/editor.php index 75224fd..32ad19c 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -21,8 +21,7 @@ function __construct() { add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_gutenberg_editor' ) ); add_action( 'enqueue_block_assets', array( $this, 'enqueue_assets' ) ); } - -+ add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue_elementor_editor' ) ); + add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue_elementor_editor' ) ); } /** @@ -40,10 +39,12 @@ public function enqueue_gutenberg_editor() { * @return void */ public function enqueue_elementor_editor() { - // Loads the core WordPress editor styles needed by the elementor editor UI. - if ( wp_style_is( 'wp-components', 'registered' ) ) { - wp_enqueue_style( 'wp-components' ); - } + wp_enqueue_style( + 'interact-editor-wp-components', + plugins_url( 'dist/wp-components.css', INTERACT_FILE ), + array(), + INTERACT_VERSION + ); $this->enqueue_editor( 'elementor' ); } From 0441e27af016dab09fc9e4502238ccb7f2da3c1c Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Tue, 12 May 2026 10:54:33 +0800 Subject: [PATCH 11/18] fix: enqueue the wp-components for elementor --- package.json | 3 +- scripts/generate-wp-components-css.mjs | 179 ------------------------- src/editor/editor.php | 9 +- 3 files changed, 4 insertions(+), 187 deletions(-) delete mode 100644 scripts/generate-wp-components-css.mjs diff --git a/package.json b/package.json index d652d9f..ae30ca6 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "build": "node scripts/update-build-type.js free && node scripts/sync-version.js ${npm_config_suffix} && npx wp-scripts build && npm run build:css && npm run build:frontend-php && npm run optimize-videos && BUILD_TYPE=free npm run package ${npm_config_suffix}", "build:frontend-php": "node scripts/generate-frontend-php-scripts.mjs production free", "build:frontend-php:premium": "node scripts/generate-frontend-php-scripts.mjs production premium", - "build:css": "webpack --config webpack.css.config.js && npm run build:css:wp-components && npm run clean:css-js", - "build:css:wp-components": "node scripts/generate-wp-components-css.mjs && npx sass build/wp-components.scss dist/wp-components.css --load-path=node_modules --no-source-map", + "build:css": "webpack --config webpack.css.config.js && npm run clean:css-js", "clean:css-js": "rm -f dist/*.css.js", "start": "node scripts/update-build-type.js free && concurrently \"wp-scripts start\" \"webpack --config webpack.css.config.js --watch\"", "lint:js": "wp-scripts lint-js", diff --git a/scripts/generate-wp-components-css.mjs b/scripts/generate-wp-components-css.mjs deleted file mode 100644 index 16ed570..0000000 --- a/scripts/generate-wp-components-css.mjs +++ /dev/null @@ -1,179 +0,0 @@ -import fs from 'fs' -import path from 'path' -import globPackage from 'glob' -import babelParser from '@babel/parser' - -const globSync = globPackage.sync -const { parse } = babelParser - -const ROOT = process.cwd() -const SOURCE_GLOB = 'src/**/*.js' -const OUTPUT_SCSS = path.join( ROOT, 'build', 'wp-components.scss' ) -const OUTPUT_REPORT = path.join( ROOT, 'build', 'wp-components-report.json' ) - -// Shared styles that are not imported directly, but are needed by some of the -// component styles that we do include. -const SHARED_STYLE_ENTRIES = [ - 'button-group', - 'form-toggle', - 'tip', - 'validated-form-controls', -] - -const EXPORT_TO_STYLE_ENTRY = { - BaseControl: 'base-control', - Button: 'button', - ColorIndicator: 'color-indicator', - ColorPicker: 'color-picker', - ComboboxControl: 'combobox-control', - Dashicon: 'dashicon', - Dropdown: 'dropdown', - FlexItem: 'flex', - MenuGroup: 'menu-group', - MenuItem: 'menu-item', - MenuItemsChoice: 'menu-items-choice', - Modal: 'modal', - Notice: 'notice', - PanelBody: 'panel', - Popover: 'popover', - RangeControl: 'range-control', - SearchControl: 'search-control', - SelectControl: 'select-control', - Spinner: 'spinner', - TextControl: 'text-control', - TextareaControl: 'textarea-control', - ToggleControl: 'toggle-control', - ToolbarButton: 'toolbar/toolbar-button', - ToolbarGroup: 'toolbar/toolbar-group', - Tooltip: 'tooltip', - __experimentalHStack: 'h-stack', - __experimentalNumberControl: 'number-control', - __experimentalToggleGroupControl: 'toggle-group-control', - __experimentalToggleGroupControlOption: 'toggle-group-control', -} - -const toNamespace = entry => { - return 'wpComponents' + entry - .replace( /[^a-zA-Z0-9]+(.)/g, ( _, char ) => char.toUpperCase() ) - .replace( /^(.)/, char => char.toUpperCase() ) -} - -const findStyleFile = entry => { - return path.join( - ROOT, - 'node_modules', - '@wordpress', - 'components', - 'src', - entry, - 'style.scss' - ) -} - -const files = globSync( SOURCE_GLOB, { cwd: ROOT, absolute: true } ) -const usedExports = new Set() - -// Scan every JS module in the codebase and collect the named exports imported -// from @wordpress/components so the generated stylesheet only includes the -// component styles we actually reference. -for ( const file of files ) { - const source = fs.readFileSync( file, 'utf8' ) - const ast = parse( source, { - sourceType: 'module', - plugins: [ 'jsx' ], - } ) - - for ( const node of ast.program.body ) { - if ( node.type !== 'ImportDeclaration' || node.source.value !== '@wordpress/components' ) { - continue - } - - for ( const specifier of node.specifiers ) { - if ( specifier.type === 'ImportSpecifier' ) { - usedExports.add( specifier.imported.name ) - } - } - } -} - -const styleEntries = new Set() -const missingExports = [] - -// Map every discovered component import to its source Sass entry when one is -// available in the package. Components that do not ship a standalone style.scss -// are reported so we can keep track of the remaining gaps. -for ( const componentExport of [ ...usedExports ].sort() ) { - const styleEntry = EXPORT_TO_STYLE_ENTRY[ componentExport ] - if ( ! styleEntry ) { - missingExports.push( { - export: componentExport, - reason: 'No style entry mapping defined.', - } ) - continue - } - - if ( ! fs.existsSync( findStyleFile( styleEntry ) ) ) { - missingExports.push( { - export: componentExport, - styleEntry, - reason: 'Mapped style entry has no style.scss file.', - } ) - continue - } - - styleEntries.add( styleEntry ) -} - -// Some imported components depend on shared styles that are not imported -// directly, so add them explicitly to keep the reduced bundle stable. -for ( const sharedEntry of SHARED_STYLE_ENTRIES ) { - if ( fs.existsSync( findStyleFile( sharedEntry ) ) ) { - styleEntries.add( sharedEntry ) - } -} - -fs.mkdirSync( path.dirname( OUTPUT_SCSS ), { recursive: true } ) - -// Generate a thin Sass entrypoint that re-exports the discovered component -// styles. Sass requires each @use to have a unique namespace, even when we only -// care about the emitted CSS side effects. -const scssContents = [ - '// Auto-generated by scripts/generate-wp-components-css.mjs', - '// Reduced bundle for @wordpress/components styles used in this codebase.', - '// Do not edit manually.', - '', - '@use "@wordpress/base-styles/mixins" as *;', - '', - ...Array.from( styleEntries ) - .sort() - .map( entry => `@use "@wordpress/components/src/${ entry }/style" as ${ toNamespace( entry ) };` ), - '', -].join( '\n' ) - -fs.writeFileSync( OUTPUT_SCSS, scssContents ) - -const report = { - generatedAt: new Date().toISOString(), - scannedPattern: SOURCE_GLOB, - sharedStyleEntries: SHARED_STYLE_ENTRIES, - usedExports: Array.from( usedExports ).sort(), - styleEntries: Array.from( styleEntries ).sort(), - missingExports, - outputScss: path.relative( ROOT, OUTPUT_SCSS ), -} - -fs.writeFileSync( OUTPUT_REPORT, JSON.stringify( report, null, 2 ) + '\n' ) - -process.stdout.write( - JSON.stringify( - { - outputScss: path.relative( ROOT, OUTPUT_SCSS ), - outputReport: path.relative( ROOT, OUTPUT_REPORT ), - usedExports: report.usedExports.length, - styleEntries: report.styleEntries.length, - missingExports: missingExports.length, - }, - null, - 2 - ) + '\n' -) diff --git a/src/editor/editor.php b/src/editor/editor.php index 32ad19c..1664f3a 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -39,12 +39,9 @@ public function enqueue_gutenberg_editor() { * @return void */ public function enqueue_elementor_editor() { - wp_enqueue_style( - 'interact-editor-wp-components', - plugins_url( 'dist/wp-components.css', INTERACT_FILE ), - array(), - INTERACT_VERSION - ); + if ( wp_style_is( 'wp-components', 'registered' ) ) { + wp_enqueue_style( 'wp-components' ); + } $this->enqueue_editor( 'elementor' ); } From 084bebed23c242e303872c227403817a7d9caba7 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Tue, 12 May 2026 11:16:38 +0800 Subject: [PATCH 12/18] fix: fix drag and drop in action timeline --- src/editor/components/timeline/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/editor/components/timeline/index.js b/src/editor/components/timeline/index.js index 0c04d38..ab84b81 100644 --- a/src/editor/components/timeline/index.js +++ b/src/editor/components/timeline/index.js @@ -1165,13 +1165,15 @@ const ActionDropGap = props => { const [ isHighlighted, setIsHighlighted ] = useState( false ) - const onDragOverHandler = () => { + const onDragOverHandler = ev => { + ev.preventDefault() setIsHighlighted( true ) } const onDragLeaveHandler = () => { setIsHighlighted( false ) } const onDragDropHandler = ev => { + ev.preventDefault() const actionKeyDrop = ev.dataTransfer.getData( 'text/plain' ) onDrop( actionKeyDrop ) setIsHighlighted( false ) @@ -1270,6 +1272,7 @@ const ActionItem = props => { } const onDragOverHandler = ev => { + ev.preventDefault() const rect = dragItemRef.current.getBoundingClientRect() if ( ev.clientY < rect.top + ( rect.height / 2 ) ) { setHighlightLocation( 'top' ) @@ -1283,6 +1286,7 @@ const ActionItem = props => { } const onDragDropHandler = ev => { + ev.preventDefault() const actionKeyDrop = ev.dataTransfer.getData( 'text/plain' ) onDrop( actionKeyDrop, highlightLocation ) setHighlightLocation( null ) From a84b1b0c5515817b39fc4d06c49d8513e89a63d5 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Tue, 12 May 2026 11:41:58 +0800 Subject: [PATCH 13/18] fix: add default styles to be used in elementor panel --- src/editor/editor.scss | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/editor/editor.scss b/src/editor/editor.scss index 37f82b3..0d3ad7c 100644 --- a/src/editor/editor.scss +++ b/src/editor/editor.scss @@ -165,6 +165,15 @@ z-index: 99999; display: flex; flex-direction: column; + + h2 { + font-size: 13px; + margin: 1.33em; + } + + p { + margin: 1em 0; + } } .interact-elementor-panel.is-open { @@ -196,6 +205,20 @@ min-height: 100%; } +.interact-popover { + color: #3c434a; + line-height: 1.4em; + + h2 { + font-size: 13px; + margin: 1.33em; + } + + p { + margin: 1em 0; + } +} + /* Wordpress 7.0 compatibility */ .interact-sidebar, .interact-popover { From e30c49bf6f9c9008d968040cee5fcfa3821a13bd Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Wed, 13 May 2026 13:39:57 +0800 Subject: [PATCH 14/18] fix: add script for scoping wp-components --- package.json | 5 +- scripts/build-scoped-wp-components-css.mjs | 101 +++++++++++++++++++++ src/editor/editor.php | 9 +- 3 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 scripts/build-scoped-wp-components-css.mjs diff --git a/package.json b/package.json index ae30ca6..c7e050a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "build": "node scripts/update-build-type.js free && node scripts/sync-version.js ${npm_config_suffix} && npx wp-scripts build && npm run build:css && npm run build:frontend-php && npm run optimize-videos && BUILD_TYPE=free npm run package ${npm_config_suffix}", "build:frontend-php": "node scripts/generate-frontend-php-scripts.mjs production free", "build:frontend-php:premium": "node scripts/generate-frontend-php-scripts.mjs production premium", - "build:css": "webpack --config webpack.css.config.js && npm run clean:css-js", + "build:css": "webpack --config webpack.css.config.js && npm run build:css:wp-components-scoped && npm run clean:css-js", + "build:css:wp-components-scoped": "node scripts/build-scoped-wp-components-css.mjs", "clean:css-js": "rm -f dist/*.css.js", "start": "node scripts/update-build-type.js free && concurrently \"wp-scripts start\" \"webpack --config webpack.css.config.js --watch\"", "lint:js": "wp-scripts lint-js", @@ -25,7 +26,7 @@ "optimize-videos": "node scripts/optimize-videos.js", "package": "node scripts/package.js", "sync-version": "node scripts/sync-version.js", - "build:premium": "node scripts/update-build-type.js premium && node scripts/sync-version.js ${npm_config_suffix} && npm run clean:css-js && npx wp-scripts build --config pro__premium_only/webpack.config.js && npx webpack --config pro__premium_only/webpack.css.config.js && npm run build:frontend-php:premium && npm run optimize-videos && BUILD_TYPE=premium npm run package ${npm_config_suffix}", + "build:premium": "node scripts/update-build-type.js premium && node scripts/sync-version.js ${npm_config_suffix} && npm run clean:css-js && npx wp-scripts build --config pro__premium_only/webpack.config.js && npx webpack --config pro__premium_only/webpack.css.config.js && npm run build:css:wp-components-scoped && npm run build:frontend-php:premium && npm run optimize-videos && BUILD_TYPE=premium npm run package ${npm_config_suffix}", "start:premium": "cd pro__premium_only && npm run start", "lint:premium": "cd pro__premium_only && npm run lint", "lint:premium:fix": "cd pro__premium_only && npm run lint:fix" diff --git a/scripts/build-scoped-wp-components-css.mjs b/scripts/build-scoped-wp-components-css.mjs new file mode 100644 index 0000000..a208c6d --- /dev/null +++ b/scripts/build-scoped-wp-components-css.mjs @@ -0,0 +1,101 @@ +import fs from 'fs' +import path from 'path' +import postcss from 'postcss' + +const ROOT = process.cwd() +const INPUT_CSS = path.join( + ROOT, + 'node_modules', + '@wordpress', + 'components', + 'build-style', + 'style.css' +) +const OUTPUT_CSS = path.join( ROOT, 'dist', 'wp-components-scoped.css' ) + +const COMPONENT_SCOPES = [ + '#interact-elementor-root', + '.interact-popover', +] +const BODY_SCOPE = 'body.interact-elementor-editor' +const PORTAL_PATTERNS = [ + '.components-modal', + '.components-snackbar', + '.components-tooltip', + '.components-guide', +] + +// Wrap scope selectors in :where() so scoping does not increase specificity. +const wrapScope = scope => `:where(${ scope })` + +// Some WordPress component UI is rendered in portals outside our sidebar root. +const isPortalSelector = selector => { + return PORTAL_PATTERNS.some( pattern => selector.includes( pattern ) ) +} + +// Check if a rule is inside a keyframes block, which should not be scoped. +const isInsideKeyframes = rule => { + let current = rule.parent + while ( current ) { + if ( current.type === 'atrule' && current.name.includes( 'keyframes' ) ) { + return true + } + current = current.parent + } + return false +} + +// Scope each selector to the Interactions Elementor UI while keeping popovers +// and other portal-based components reachable outside the sidebar root. +const scopeRootSelector = selector => { + if ( selector.startsWith( ':root' ) ) { + return [ ...COMPONENT_SCOPES.map( wrapScope ), wrapScope( BODY_SCOPE ) ] + } + + if ( selector.startsWith( 'body' ) ) { + return [ selector.replace( /^body\b/, wrapScope( BODY_SCOPE ) ) ] + } + + if ( selector.startsWith( 'html' ) ) { + return [ `${ wrapScope( BODY_SCOPE ) } ${ selector }` ] + } + + if ( isPortalSelector( selector ) ) { + return [ `${ wrapScope( BODY_SCOPE ) } ${ selector }` ] + } + + if ( /(^|[\s>+~])\.components-popover(?![a-zA-Z0-9_-])/.test( selector ) ) { + return [ selector.replace( /(^|[\s>+~])\.components-popover(?![a-zA-Z0-9_-])/g, `$1${ wrapScope( '.interact-popover.components-popover' ) }` ) ] + } + + return COMPONENT_SCOPES.map( scope => `${ wrapScope( scope ) } ${ selector }` ) +} + +const css = fs.readFileSync( INPUT_CSS, 'utf8' ) +const root = postcss.parse( css ) + +root.walkRules( rule => { + if ( ! Array.isArray( rule.selectors ) ) { + return + } + + if ( isInsideKeyframes( rule ) ) { + return + } + + rule.selectors = rule.selectors.flatMap( selector => scopeRootSelector( selector ) ) +} ) + +fs.mkdirSync( path.dirname( OUTPUT_CSS ), { recursive: true } ) +fs.writeFileSync( OUTPUT_CSS, root.toString() ) + +process.stdout.write( + JSON.stringify( + { + input: path.relative( ROOT, INPUT_CSS ), + output: path.relative( ROOT, OUTPUT_CSS ), + }, + null, + 2 + ) + '\n' +) diff --git a/src/editor/editor.php b/src/editor/editor.php index 1664f3a..a505bd6 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -39,9 +39,12 @@ public function enqueue_gutenberg_editor() { * @return void */ public function enqueue_elementor_editor() { - if ( wp_style_is( 'wp-components', 'registered' ) ) { - wp_enqueue_style( 'wp-components' ); - } + wp_enqueue_style( + 'interact-editor-wp-components-scoped', + plugins_url( 'dist/wp-components-scoped.css', INTERACT_FILE ), + array(), + INTERACT_VERSION + ); $this->enqueue_editor( 'elementor' ); } From 9c40a3b94c06afab056e8f5324e10c028fffe1e0 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Wed, 3 Jun 2026 23:25:59 +0800 Subject: [PATCH 15/18] fix: proper target selection, styling --- .../add-interaction-popover/editor.scss | 5 +- .../add-interaction-popover/index.js | 50 +------------------ .../import-export-modal/editor.scss | 12 +++++ .../components/interaction-panel/editor.scss | 2 +- .../components/target-selector/editor.scss | 13 +++-- .../components/target-selector/index.js | 31 ++++++++---- src/editor/editor.scss | 22 ++++++++ src/editor/editors/elementor.js | 30 +++++++++-- 8 files changed, 91 insertions(+), 74 deletions(-) diff --git a/src/editor/components/add-interaction-popover/editor.scss b/src/editor/components/add-interaction-popover/editor.scss index 2835b08..975385f 100644 --- a/src/editor/components/add-interaction-popover/editor.scss +++ b/src/editor/components/add-interaction-popover/editor.scss @@ -56,9 +56,8 @@ .interact-target-block-button { align-self: end; - margin-bottom: 8px; - width: 30px; - height: 30px; + width: 32px; + height: 32px; min-width: 30px !important; + * { flex: 1; diff --git a/src/editor/components/add-interaction-popover/index.js b/src/editor/components/add-interaction-popover/index.js index 74f3ed8..72993e6 100644 --- a/src/editor/components/add-interaction-popover/index.js +++ b/src/editor/components/add-interaction-popover/index.js @@ -10,7 +10,6 @@ import { import { useInteractions } from '~interact/editor/hooks' import { getOrGenerateBlockAnchor, - getOrGenerateBlockClass, getLocationForCurrentPage, duplicateInteraction, setBlockAnchorIfPossible, @@ -35,9 +34,8 @@ import { __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components' import { useState } from '@wordpress/element' -import { useSelect, select } from '@wordpress/data' +import { useSelect } from '@wordpress/data' import { __, sprintf } from '@wordpress/i18n' -import { BlockPickerPopover } from '../target-selector' import { ProUpsell } from '../pro-crown' const NOOP = () => {} @@ -59,7 +57,6 @@ const AddInteractionPopover = props => { const [ selected, setSelected ] = useState( initialSelected ) const [ showDescription, setShowDescription ] = useState( null ) - const [ hidden, setHidden ] = useState( false ) const isElementor = isElementorEditor() const { @@ -106,45 +103,6 @@ const AddInteractionPopover = props => { return acc }, { elementInteractions: [], pageInteractions: [] } ) - if ( hidden && ! isElementor ) { - return ( - { - const valueArgs = { - ...target, - blockName, - } - - let pickerMode = target === 'block' ? 'id' : 'class' - if ( pickerMode === 'id' ) { - // If id, use the block id as the anchor. If the - // block doesn't support anchors is not supported, - // then use picker mode class. - const hasAnchorAttribute = !! select( 'core/blocks' ).getBlockType( blockName )?.attributes?.anchor - if ( hasAnchorAttribute ) { - valueArgs.value = getOrGenerateBlockAnchor( clientId, true ) - } else { - pickerMode = 'class' - } - } - - if ( pickerMode === 'class' ) { - // If class, use the first class name if there is one, or create a new one. - valueArgs.value = getOrGenerateBlockClass( clientId, true ) - valueArgs.type = 'class' - } - - setTarget( valueArgs ) - setHidden( false ) - } } - onClose={ () => setHidden( false ) } - /> - ) - } - return ( { { - if ( ! isElementor ) { - setHidden( true ) - } - } } /> ) } diff --git a/src/editor/components/import-export-modal/editor.scss b/src/editor/components/import-export-modal/editor.scss index e779c88..a195ecd 100644 --- a/src/editor/components/import-export-modal/editor.scss +++ b/src/editor/components/import-export-modal/editor.scss @@ -1,7 +1,19 @@ +.components-modal__screen-overlay:has(.interact-import-export-modal) { + position: fixed !important; + inset: 0 !important; + display: flex !important; + align-items: center; + justify-content: center; + padding: 24px; + z-index: 100001; +} + .interact-import-export-modal { width: 100%; max-width: 50%; max-height: 90%; + margin: 0 !important; + z-index: 100002; .components-modal__content { margin-top: 56px; diff --git a/src/editor/components/interaction-panel/editor.scss b/src/editor/components/interaction-panel/editor.scss index 20922fb..7673ca6 100644 --- a/src/editor/components/interaction-panel/editor.scss +++ b/src/editor/components/interaction-panel/editor.scss @@ -1,6 +1,6 @@ .interact-interaction-card { padding: 16px; - margin-top: 70px; // Push the contents down because the controls are fixed on top + margin-top: 60px; // Push the contents down because the controls are fixed on top h2 { margin: 0 0 8px; } diff --git a/src/editor/components/target-selector/editor.scss b/src/editor/components/target-selector/editor.scss index 757e00b..94d6a8b 100644 --- a/src/editor/components/target-selector/editor.scss +++ b/src/editor/components/target-selector/editor.scss @@ -21,6 +21,12 @@ display: none; } +.interact-target-block-button.is-picking { + border-color: var(--wp-components-color-accent, #05f) !important; + box-shadow: inset 0 0 0 1px var(--wp-components-color-accent, #05f); + background: color-mix(in srgb, var(--wp-components-color-accent, #05f) 10%, #fff); +} + .interact-target-selector__warn { background: #fff4e6; border-left: 3px solid #f90; @@ -69,10 +75,3 @@ } } } - -/* Wordpress 7.0 compatibility */ - -// Text control losses its margin bottom. -.interact-target-block-input .components-base-control__field { - margin-bottom: 8px; -} \ No newline at end of file diff --git a/src/editor/components/target-selector/index.js b/src/editor/components/target-selector/index.js index 7f4ba10..753ed51 100644 --- a/src/editor/components/target-selector/index.js +++ b/src/editor/components/target-selector/index.js @@ -43,6 +43,7 @@ const TargetSelector = props => { } = props const [ isPopoverOpen, setIsPopoverOpen ] = useState( false ) + const [ isPickerActive, setIsPickerActive ] = useState( false ) const [ buttonRef, setButtonRef ] = useState( null ) const prevValueRef = useRef( {} ) const elementPickerStopRef = useRef( null ) @@ -52,7 +53,7 @@ const TargetSelector = props => { const targetButton = ( <> +
+
+
+ + { __( 'Interactions', 'interactions' ) } +
+
+
+
+ +
+
+
+ + ) + } + + const mountNode = document.createElement( 'div' ) + mountNode.id = mountNodeId + mountNode.className = 'interact-builder-root interact-bricks-root' + document.body.appendChild( mountNode ) + document.body.classList.add( 'interact-builder-editor' ) + document.body.classList.add( 'interact-bricks-editor' ) + this.registerSelectionTracking() + createRoot( mountNode ).render( ) + + return super.init() + } + + getCanvasDocument() { + const iframe = document.querySelector( '#bricks-builder-iframe' ) + return iframe?.contentDocument || null + } + + openPanel() { + window.dispatchEvent( new CustomEvent( 'interact/open-bricks-sidebar' ) ) + return null + } + + getSelectedElementId( element ) { + if ( ! element ) { + return '' + } + + if ( element.dataset?.id ) { + return element.dataset.id + } + + const bricksNode = element.closest( '[data-id]' ) + if ( bricksNode?.dataset?.id ) { + return bricksNode.dataset.id + } + + const frontendNode = element.closest( '[id^="brxe-"]' ) + if ( frontendNode?.id ) { + return frontendNode.id.replace( /^brxe-/, '' ) + } + + return '' + } + + buildTargetFromElement( element ) { + const elementId = this.getSelectedElementId( element ) + if ( ! elementId ) { + return null + } + + const label = element?.dataset?.elementType || element?.tagName?.toLowerCase() || 'bricks-element' + + return { + type: 'selector', + value: `#brxe-${ elementId }`, + blockName: label, + } + } + + getCurrentSelectedTarget() { + if ( ! this.selectedElement ) { + return null + } + + return this.buildTargetFromElement( this.selectedElement.element ) + } + + registerSelectionTracking() { + let isBound = false + let observer = null + + const handleSelection = element => { + this.selectedElement = { + element, + } + } + + const bindListeners = () => { + if ( isBound ) { + return true + } + + const previewDocument = this.getCanvasDocument() + if ( ! previewDocument?.body ) { + return false + } + + previewDocument.addEventListener( 'click', event => { + const candidate = event.target.closest( '[data-id], [id^="brxe-"]' ) + if ( candidate ) { + handleSelection( candidate ) + } + }, true ) + + const structurePanel = document.querySelector( '#bricks-structure' ) + if ( structurePanel ) { + structurePanel.addEventListener( 'click', event => { + const candidate = event.target.closest( '[data-id]' ) + if ( candidate ) { + handleSelection( candidate ) + } + }, true ) + } + + isBound = true + return true + } + + if ( ! bindListeners() ) { + observer = new MutationObserver( () => { + if ( bindListeners() ) { + observer?.disconnect() + } + } ) + + observer.observe( document.body, { + childList: true, + subtree: true, + } ) + } + + return () => observer?.disconnect() + } + + startElementPicker( { + onPick = NOOP, + onCancel = NOOP, + } = {} ) { + const previewDocument = this.getCanvasDocument() + if ( ! previewDocument ) { + onCancel() + return NOOP + } + + let highlightedElement = null + + const clearHighlight = () => { + if ( highlightedElement ) { + highlightedElement.style.outline = highlightedElement.dataset.interactPrevOutline || '' + highlightedElement.style.outlineOffset = highlightedElement.dataset.interactPrevOutlineOffset || '' + delete highlightedElement.dataset.interactPrevOutline + delete highlightedElement.dataset.interactPrevOutlineOffset + } + highlightedElement = null + } + + const getCandidate = element => element?.closest?.( '[data-id], [id^="brxe-"]' ) || null + + const mouseMoveHandler = event => { + const candidate = getCandidate( event.target ) + if ( candidate === highlightedElement ) { + return + } + + clearHighlight() + if ( candidate ) { + highlightedElement = candidate + highlightedElement.dataset.interactPrevOutline = highlightedElement.style.outline || '' + highlightedElement.dataset.interactPrevOutlineOffset = highlightedElement.style.outlineOffset || '' + highlightedElement.style.outline = '2px solid #05f' + highlightedElement.style.outlineOffset = '2px' + } + } + + const clickHandler = event => { + const candidate = getCandidate( event.target ) + if ( ! candidate ) { + return + } + + event.preventDefault() + event.stopPropagation() + const target = this.buildTargetFromElement( candidate ) + stop() + + if ( target ) { + onPick( target ) + } else { + onCancel() + } + } + + const keyHandler = event => { + if ( event.key === 'Escape' ) { + stop() + onCancel() + } + } + + const stop = () => { + clearHighlight() + previewDocument.removeEventListener( 'mousemove', mouseMoveHandler, true ) + previewDocument.removeEventListener( 'click', clickHandler, true ) + previewDocument.removeEventListener( 'keydown', keyHandler, true ) + document.removeEventListener( 'keydown', keyHandler, true ) + } + + previewDocument.addEventListener( 'mousemove', mouseMoveHandler, true ) + previewDocument.addEventListener( 'click', clickHandler, true ) + previewDocument.addEventListener( 'keydown', keyHandler, true ) + document.addEventListener( 'keydown', keyHandler, true ) + + return stop + } +} + +export default BricksInteractionsEditor diff --git a/src/editor/editors/index.js b/src/editor/editors/index.js index 259a301..fba10b5 100644 --- a/src/editor/editors/index.js +++ b/src/editor/editors/index.js @@ -1,6 +1,7 @@ import { editorMode } from 'interactions' import GutenbergInteractionsEditor from './gutenberg' import ElementorInteractionsEditor from './elementor' +import BricksInteractionsEditor from './bricks' let activeEditor = null @@ -8,7 +9,9 @@ let activeEditor = null const createInteractionsEditor = () => { return editorMode === 'elementor' ? new ElementorInteractionsEditor() - : new GutenbergInteractionsEditor() + : editorMode === 'bricks' + ? new BricksInteractionsEditor() + : new GutenbergInteractionsEditor() } // Return the memoized editor adapter instance. @@ -23,8 +26,12 @@ export const getEditorMode = () => getInteractionsEditor().getEditorMode() export const isElementorEditor = () => getInteractionsEditor().isElementor() +export const isBricksEditor = () => getInteractionsEditor().isBricks() + export const isGutenbergEditor = () => getInteractionsEditor().isGutenberg() +export const isBuilderEditor = () => getInteractionsEditor().isBuilder() + export const getCurrentEditorPostContext = () => getInteractionsEditor().getCurrentPostContext() export const getSelectedBlockAnchor = () => getInteractionsEditor().getSelectedBlockAnchor() @@ -37,6 +44,6 @@ export const openInteractionsSidebar = () => getInteractionsEditor().openInterac export const getCurrentSelectedTarget = () => getInteractionsEditor().getCurrentSelectedTarget() -export const registerElementorSelectionTracking = () => getInteractionsEditor().registerSelectionTracking() +export const registerEditorSelectionTracking = () => getInteractionsEditor().registerSelectionTracking() -export const startElementorElementPicker = args => getInteractionsEditor().startElementPicker( args ) +export const startEditorElementPicker = args => getInteractionsEditor().startElementPicker( args ) diff --git a/src/editor/hooks/use-interactions.js b/src/editor/hooks/use-interactions.js index 553e80e..8ea0463 100644 --- a/src/editor/hooks/use-interactions.js +++ b/src/editor/hooks/use-interactions.js @@ -174,7 +174,7 @@ const useInteractions = () => { const updateInteraction = newInteraction => { // Check if we updated any anchors/attributes, if we did, then we need to ask whether to also update the post. const didModifyPostContent = select( 'interact/interactions' ).didModifyPostContent() - if ( didModifyPostContent && getEditorMode() !== 'elementor' ) { + if ( didModifyPostContent && getEditorMode() === 'gutenberg' ) { if ( confirm( __( 'Some block anchors have been updated for your interactions to work correctly. Do you want to save these post changes? (Any modified synced patterns will also be saved)', 'interactions' ) ) ) { // eslint-disable-line no-alert dispatch( 'interact/interactions' ).setDidModifyPostContent( false ) // Save the post. diff --git a/src/editor/plugins/index.js b/src/editor/plugins/index.js index 1039a02..f3af43f 100644 --- a/src/editor/plugins/index.js +++ b/src/editor/plugins/index.js @@ -1,6 +1,6 @@ import { editorMode } from 'interactions' -if ( editorMode !== 'elementor' ) { +if ( editorMode === 'gutenberg' ) { require( './block-toolbar-button' ) require( './top-toolbar-button' ) require( './block-highlight' ) From df361ab204620bead46339429b1e44a1da43d021 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Thu, 4 Jun 2026 09:55:09 +0800 Subject: [PATCH 17/18] fix: description, readme with the new integrations --- README.md | 6 +++--- readme.txt | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 71067a6..af5cee1 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ [![PHP](https://img.shields.io/badge/PHP-8.0%2B-purple.svg)](https://php.net/) [![WordPress](https://img.shields.io/badge/WordPress-6.6.4%2B-blue.svg)](https://wordpress.org/) -A WordPress plugin that adds animations, effects, and interactivity to Gutenberg blocks. +A WordPress plugin that adds animations, effects, and interactivity to Gutenberg, Elementor, and Bricks. ## 🔧 Requirements - **WordPress**: 6.6.4 or higher - **PHP**: 8.0 or higher - **Node.js**: 18 or higher -- **Block Editor**: Gutenberg (built-in WordPress editor) +- **Supported Editors**: Gutenberg (built-in WordPress editor), Elementor, and Bricks ## 🛠️ Development @@ -56,7 +56,7 @@ src/ ├── action-types/ # Available action types (PHP + JS) ├── interaction-types/ # Available trigger types (PHP + JS) ├── admin/ # Admin interface -├── editor/ # Block editor integration +├── editor/ # Editor and builder integrations ├── frontend/ # Frontend functionality ├── locations/ # Location rules └── rest-api/ # REST API endpoints diff --git a/readme.txt b/readme.txt index 7f01f92..348cdf0 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -=== Interactions - Create Interactive Experiences in the Block Editor === +=== Interactions - Create Interactive Experiences in WordPress Editors and Builders === Contributors: bfintal, gambitph Tags: animation, interaction, interactivity, blocks, gutenberg Requires at least: 6.7.5 @@ -8,15 +8,15 @@ Stable tag: 1.3.3 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html -Add animations and interactivity to your blocks. Choose from ready-made effects like scroll & hover in the Interactions Library, or build your own. +Add animations and interactivity to your WordPress content. Choose from ready-made effects like scroll and hover in the Interactions Library, or build your own. == Description == -**Interactions – WordPress Animations, Interactive Experiences for Gutenberg Blocks** +**Interactions – WordPress Animations and Interactive Experiences for Gutenberg, Elementor, and Bricks** [Visit our website](https://wpinteractions.com) to learn more about how Interactions work. -Want to make your website feel alive and interactive? **Interactions** is the easiest way to add animations, effects, interactivity, and functional features to WordPress — directly inside the block editor. Check our [samples page here](https://wpinteractions.com/samples/) to see a glimpse of what type of interactions you can create. +Want to make your website feel alive and interactive? **Interactions** is the easiest way to add animations, effects, interactivity, and functional features to WordPress — directly inside the editor or builder you already use. Check our [samples page here](https://wpinteractions.com/samples/) to see a glimpse of what type of interactions you can create. You don't need coding skills or complex tools. With Interactions, you can: @@ -65,7 +65,7 @@ Create [custom interactions](https://docs.wpinteractions.com/article/571-what-ar - Designers who want **scroll animations** without code - Marketers who want **attention-grabbing hover effects** - Bloggers who want **dynamic storytelling** with animations -- Site builders who want **to bring their block designs to life, or create unique micro-interactions** +- Site builders who want **to bring their designs to life, or create unique micro-interactions** - Developers who need **functional features** like post meta updates and data handling - Anyone building **modern interactive websites** in WordPress @@ -110,11 +110,11 @@ https://github.com/gambitph/Interactions 1. Install “Interactions” from the WordPress Plugin Directory, or upload it to `/wp-content/plugins/interactions/`. 2. Activate the plugin from the “Plugins” menu. -3. Edit a post or page with the block editor. -4. Open the **Interactions Library** panel from the top and pick an effect. +3. Edit a post or page with Gutenberg, Elementor, or Bricks. +4. In Gutenberg, open the **Interactions Library** panel from the top and pick an effect. – OR – Create your own using the **Trigger → Action builder**. -5. Save and preview your interactive blocks! +5. Save and preview your interactive elements. == Frequently Asked Questions == @@ -122,7 +122,8 @@ https://github.com/gambitph/Interactions No! While Interactions excels at animations, it's much more than that. It's a comprehensive interaction system that includes functional features like updating post meta, triggering DOM events, copying text to clipboard, and much more. You can build both visual effects and powerful functional features. = Does it work with Elementor or other page builders? = -No. Interactions is built specifically for the **WordPress block editor (Gutenberg)**. +Yes. Interactions works in the **WordPress block editor (Gutenberg)** and also includes editor integrations for **Elementor** and **Bricks**. +The full **Interactions Library** browsing flow is currently available in Gutenberg, while Elementor and Bricks support creating and managing interactions directly from their builder panels. = Can I add scroll animations to WordPress with this plugin? = Yes. You can animate blocks when they enter the viewport, fade in, slide in, or trigger other effects on scroll. From 412d4f507dd5c9050889f8baed2b682a93b87d6c Mon Sep 17 00:00:00 2001 From: bfintal Date: Thu, 18 Jun 2026 11:21:38 +0800 Subject: [PATCH 18/18] added scoped styles build in free --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7e050a..138e1ea 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "build:css": "webpack --config webpack.css.config.js && npm run build:css:wp-components-scoped && npm run clean:css-js", "build:css:wp-components-scoped": "node scripts/build-scoped-wp-components-css.mjs", "clean:css-js": "rm -f dist/*.css.js", - "start": "node scripts/update-build-type.js free && concurrently \"wp-scripts start\" \"webpack --config webpack.css.config.js --watch\"", + "start": "npm run build:css:wp-components-scoped && node scripts/update-build-type.js free && concurrently \"wp-scripts start\" \"webpack --config webpack.css.config.js --watch\"", "lint:js": "wp-scripts lint-js", "lint:js:fix": "wp-scripts lint-js --fix", "lint:css": "wp-scripts lint-style",