diff --git a/package-lock.json b/package-lock.json
index 1140d69..e60123f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,51 +1,51 @@
{
- "name": "react-starter-proj",
+ "name": "de-examples-tester",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"modules": true,
"packages": {
"": {
- "name": "react-starter-proj",
+ "name": "de-examples-tester",
"version": "1.0.0",
"hasInstallScript": true,
"dependencies": {
- "@emotion/react": "^11.13.5",
- "@emotion/styled": "^11.13.5",
- "@fontsource/inter": "^5.0.16",
- "@monaco-editor/react": "^4.7.0",
- "@mui/icons-material": "^6.1.9",
- "@mui/material": "^6.1.9",
- "@tanstack/react-query": "^5.81.2",
- "@types/react": "^18.2.48",
- "@types/react-dom": "^18.2.18",
- "acorn": "^8.12.1",
- "acorn-jsx": "^5.3.2",
- "acorn-typescript": "^1.4.13",
- "acorn-walk": "^8.3.4",
- "axios": "^1.6.2",
- "dotenv": "^16.4.5",
- "fs-extra": "^11.2.0",
- "prismjs": "^1.29.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-resizable": "^3.0.5",
- "source-map": "^0.7.4",
- "sucrase": "^3.35.0",
- "typedoc": "^0.25.12"
+ "@emotion/react": "11.13.5",
+ "@emotion/styled": "11.13.5",
+ "@fontsource/inter": "5.0.16",
+ "@monaco-editor/react": "4.7.0",
+ "@mui/icons-material": "6.1.9",
+ "@mui/material": "6.1.9",
+ "@tanstack/react-query": "5.81.2",
+ "@types/react": "18.2.48",
+ "@types/react-dom": "18.2.18",
+ "acorn": "8.15.0",
+ "acorn-jsx": "5.3.2",
+ "acorn-typescript": "1.4.13",
+ "acorn-walk": "8.3.4",
+ "axios": "1.9.0",
+ "dotenv": "16.4.5",
+ "fs-extra": "11.2.0",
+ "prismjs": "1.29.0",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "react-resizable": "3.0.5",
+ "source-map": "0.7.4",
+ "sucrase": "3.35.0",
+ "typedoc": "0.25.13"
},
"devDependencies": {
- "@types/node": "^22.13.5",
- "@types/prismjs": "^1.26.3",
- "@vitejs/plugin-react": "^4.0.0",
- "@webflow/designer-extension-typings": "^2.0.25",
- "@webflow/webflow-cli": "^1.8.6",
- "@xatom/wf-app-hot-reload": "^1.0.5",
- "concurrently": "^6.3.0",
- "nodemon": "^2.0.22",
- "prettier": "^3.2.5",
- "typescript": "^5.3.3",
- "vite": "^5.0.0"
+ "@types/node": "22.13.5",
+ "@types/prismjs": "1.26.3",
+ "@vitejs/plugin-react": "4.5.2",
+ "@webflow/designer-extension-typings": "^2.0.31",
+ "@webflow/webflow-cli": "1.8.6",
+ "@xatom/wf-app-hot-reload": "1.0.5",
+ "concurrently": "6.5.1",
+ "nodemon": "2.0.22",
+ "prettier": "3.2.5",
+ "typescript": "5.3.3",
+ "vite": "5.4.19"
}
},
"node_modules/@ampproject/remapping": {
@@ -4450,9 +4450,9 @@
"dev": true
},
"node_modules/@webflow/designer-extension-typings": {
- "version": "2.0.25",
- "resolved": "https://registry.npmjs.org/@webflow/designer-extension-typings/-/designer-extension-typings-2.0.25.tgz",
- "integrity": "sha512-nehI7TnpWzwdTAa0ZceV/7TEeBkjN8JMARHQAyDZXQQm6HmQJaaJaXsUYm4WNpuFjNMMxMM21MH0muZ4tJDUZQ==",
+ "version": "2.0.31",
+ "resolved": "https://registry.npmjs.org/@webflow/designer-extension-typings/-/designer-extension-typings-2.0.31.tgz",
+ "integrity": "sha512-TwjWWBvXTfR2cHtr+KNMdjmZgZdV2ONN61I092jo5Mc5b8aGubvwvfsGAxg66M0WGoBeD+EExTNF0MWJpV9QTQ==",
"dev": true,
"license": "MIT"
},
diff --git a/package.json b/package.json
index 43860f1..6339c29 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "react-starter-proj",
+ "name": "de-examples-tester",
"version": "1.0.0",
"private": true,
"type": "module",
@@ -14,41 +14,41 @@
"postinstall": "node scripts/sync-typings.js"
},
"devDependencies": {
- "@types/node": "^22.13.5",
- "@types/prismjs": "^1.26.3",
- "@vitejs/plugin-react": "^4.0.0",
- "@webflow/designer-extension-typings": "^2.0.25",
- "@webflow/webflow-cli": "^1.8.6",
- "@xatom/wf-app-hot-reload": "^1.0.5",
- "concurrently": "^6.3.0",
- "nodemon": "^2.0.22",
- "prettier": "^3.2.5",
- "typescript": "^5.3.3",
- "vite": "^5.0.0"
+ "@types/node": "22.13.5",
+ "@types/prismjs": "1.26.3",
+ "@vitejs/plugin-react": "4.5.2",
+ "@webflow/designer-extension-typings": "^2.0.31",
+ "@webflow/webflow-cli": "1.8.6",
+ "@xatom/wf-app-hot-reload": "1.0.5",
+ "concurrently": "6.5.1",
+ "nodemon": "2.0.22",
+ "prettier": "3.2.5",
+ "typescript": "5.3.3",
+ "vite": "5.4.19"
},
"dependencies": {
- "@emotion/react": "^11.13.5",
- "@emotion/styled": "^11.13.5",
- "@fontsource/inter": "^5.0.16",
- "@monaco-editor/react": "^4.7.0",
- "@mui/icons-material": "^6.1.9",
- "@mui/material": "^6.1.9",
- "@tanstack/react-query": "^5.81.2",
- "@types/react": "^18.2.48",
- "@types/react-dom": "^18.2.18",
- "acorn": "^8.12.1",
- "acorn-jsx": "^5.3.2",
- "acorn-typescript": "^1.4.13",
- "acorn-walk": "^8.3.4",
- "axios": "^1.6.2",
- "dotenv": "^16.4.5",
- "fs-extra": "^11.2.0",
- "prismjs": "^1.29.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-resizable": "^3.0.5",
- "source-map": "^0.7.4",
- "sucrase": "^3.35.0",
- "typedoc": "^0.25.12"
+ "@emotion/react": "11.13.5",
+ "@emotion/styled": "11.13.5",
+ "@fontsource/inter": "5.0.16",
+ "@monaco-editor/react": "4.7.0",
+ "@mui/icons-material": "6.1.9",
+ "@mui/material": "6.1.9",
+ "@tanstack/react-query": "5.81.2",
+ "@types/react": "18.2.48",
+ "@types/react-dom": "18.2.18",
+ "acorn": "8.15.0",
+ "acorn-jsx": "5.3.2",
+ "acorn-typescript": "1.4.13",
+ "acorn-walk": "8.3.4",
+ "axios": "1.9.0",
+ "dotenv": "16.4.5",
+ "fs-extra": "11.2.0",
+ "prismjs": "1.29.0",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "react-resizable": "3.0.5",
+ "source-map": "0.7.4",
+ "sucrase": "3.35.0",
+ "typedoc": "0.25.13"
}
}
diff --git a/src/designer-extension-typings/api.d.ts b/src/designer-extension-typings/api.d.ts
index a9dd0e5..1f57d47 100644
--- a/src/designer-extension-typings/api.d.ts
+++ b/src/designer-extension-typings/api.d.ts
@@ -8,10 +8,20 @@
///
///
///
+///
+///
+///
///
///
-interface WebflowApi {
+type AppModeName = 'design' | 'build' | 'preview' | 'edit' | 'comment';
+
+interface AppModeChangeEvent {
+ mode: AppModeName | null;
+ appModes: {[key in AppMode]: boolean};
+}
+
+interface SharedApi {
/**
* Get metadata about the current Site.
* @returns A Promise that resolves to a record containing information about the site that is open in the
@@ -53,78 +63,64 @@ interface WebflowApi {
}>;
}>;
/**
- * Get the currently selected element in the Webflow Designer.
- * @returns A promise that resolves to one of the following:
- * - null: If no element is currently selected in the Designer
- * - AnyElement: an object representing the selected element, which can be of any type.
+ * Renders the specified element to WHTML format.
+ * @param element - The element to render
+ * @returns A promise that resolves to an object containing the WHTML string and shortIdMap, or null
* @example
* ```ts
* const selectedElement = await webflow.getSelectedElement();
* if (selectedElement) {
- * // Handle the selected element
- * } else {
- * // No element is currently selected
- * }
- * ```
- */
- getSelectedElement(): Promise;
- /**
- * Sets the currently selected element in the Webflow Designer.
- * @returns A promise that resolves to one of the following:
- * - null: If no element is able to be currently selected in the Designer
- * - AnyElement: an object representing the selected element, which can be of any type.
- * @example
- * ```ts
- * await webflow.setSelectedElement(element);
- * ```
- */
- setSelectedElement(element: AnyElement): Promise;
-
- /**
- * Captures a screenshot of the specified element.
- * @returns A promise that resolves to a base64 string representing the screenshot of the element.
- * @example
- * ```ts
- * const selectedElement = await webflow.getSelectedElement();
- * if (selectedElement) {
- * const screenshot = await webflow.getElementSnapshot(selectedElement);
- * console.log('Screenshot:', screenshot);
- * }else{
- * console.log('No element selected');
+ * const result = await webflow.getWHTML(selectedElement);
+ * if (result) {
+ * console.log('WHTML:', result.whtml);
+ * console.log('Short ID Map:', result.shortIdMap);
+ * }
* }
* ```
*/
- getElementSnapshot(element: AnyElement): Promise;
+ getWHTML?(
+ element: AnyElement
+ ): Promise}>;
elementBuilder(elementPreset: ElementPreset): BuilderElement;
- /**
- * Get the current media query breakpoint ID.
- * @returns A Promise that resolves to a BreakpointId which is a string representing the current media query
- * breakpoint. A BreakpointId is one of 'tiny', 'small', 'medium', 'main', 'large', 'xl', 'xxl'.
- * @example
- * ```ts
- * const breakpoint = await webflow.getMediaQuery();
- * console.log('Current Media Query:', breakpoint);
- * ```
- */
- getMediaQuery(): Promise;
/**
- * Get the current pseudo mode.
- * @returns A Promise that resolves to a PseudoStateKey which is a string representing the current pseudo mode.
- * @example
- * ```ts
- * const pseudoMode = await webflow.getPseudoMode();
- * console.log('Current Pseudo Mode:', pseudoMode);
- * ```
- */
- getPseudoMode(): Promise;
+ * Parse a WHTML string and insert the resulting element as a child of the anchor element.
+ * The newly created element will be appended to the end of the anchor's children.
+ * @param whtml - The WHTML string to parse into an element
+ * @param anchor - The parent element to append the parsed WHTML element to
+ * @param position - The position relative to the anchor element where the new element will be inserted.
+ * - 'before': Insert as a sibling before the anchor element
+ * - 'after': Insert as a sibling after the anchor element
+ * - 'append': Insert as the last child of the anchor element (default)
+ * - 'prepend': Insert as the first child of the anchor element
+ * - 'replace': Replace the anchor element with the new element
+ * @returns A Promise that resolves to the newly inserted AnyElement
+ * @example
+ * ```ts
+ * const whtml = '';
+ * const body = await allElements.find((el) => el.type === 'Body');
+ * // Append as last child (default)
+ * const element = await webflow.insertElementFromWHTML(whtml, body);
+ * // Or insert before an existing element
+ * const existingElement = await webflow.getSelectedElement();
+ * const newElement = await webflow.insertElementFromWHTML(whtml, existingElement, 'before');
+ * // Or replace an existing element
+ * const replacedElement = await webflow.insertElementFromWHTML(whtml, existingElement, 'replace');
+ * ```
+ */
+ insertElementFromWHTML?(
+ whtml: string,
+ anchor: AnyElement,
+ position?: 'before' | 'after' | 'append' | 'prepend' | 'replace'
+ ): Promise;
/**
* Create a component by promoting a Root Element.
* @param name - The name of the component.
- * @param rootElement - An Element that will become the Root Element of the Component.
+ * @param root - An Element that will become the Root Element of the Component.
* @returns A Promise resolving to an object containing the newly created Component - with the id property.
+ * @deprecated Use `registerComponent(options, root)` instead to provide richer metadata.
* @example
* ```ts
* const element = webflow.createDOM('div')
@@ -138,7 +134,67 @@ interface WebflowApi {
name: string,
root: AnyElement | ElementPreset | Component
): Promise;
- getComponentByName(string): Promise;
+ /**
+ * Create a blank component.
+ * @param options - Options for creating the blank component.
+ * @returns A Promise resolving to an object containing the newly created Component - with the id property.
+ * @example
+ * ```ts
+ * const component = await webflow.registerComponent({name: 'Hero Section'})
+ *
+ * // With optional group and description
+ * const grouped = await webflow.registerComponent({
+ * name: 'Hero Section',
+ * group: 'Sections',
+ * description: 'A hero section component',
+ * })
+ * ```
+ */
+ registerComponent(options: ComponentOptions): Promise;
+ /**
+ * Duplicate an existing component.
+ * @param options - Options for the new component, including a required name.
+ * @param source - The existing Component to duplicate.
+ * @returns A Promise resolving to the newly created Component.
+ * @example
+ * ```ts
+ * const [original] = await webflow.getAllComponents()
+ * const copy = await webflow.registerComponent({name: 'Hero Copy'}, original)
+ * ```
+ */
+ registerComponent(
+ options: ComponentOptions,
+ source: Component
+ ): Promise;
+ /**
+ * Convert an element or element preset into a component. Equivalent to the
+ * "Convert selection" action in the Designer's "New component" menu.
+ * Elements do not need to be on the page. You can build the tree with
+ * `createDOM` first and pass it directly.
+ *
+ * When `root` is a canvas `AnyElement`, the source element is replaced
+ * in-place by a new component instance by default. Pass `replace: false`
+ * in `options` to skip this substitution and keep the original element.
+ * @param options - Options for the new component. `name` is required;
+ * `group`, `description`, and `replace` are optional.
+ * @param root - The element, element preset, or builder element that becomes the component root.
+ * @returns A Promise resolving to the newly created Component.
+ * @example
+ * ```ts
+ * // Convert a canvas element and replace it with a component instance (default)
+ * const el = await webflow.getSelectedElement()
+ * const card = await webflow.registerComponent({name: 'Card', group: 'UI'}, el)
+ *
+ * // Convert without replacing the original element in the canvas
+ * const card2 = await webflow.registerComponent(
+ * {name: 'Card 2', replace: false},
+ * el
+ * )
+ */
+ registerComponent(
+ options: ComponentOptions,
+ root: AnyElement | ElementPreset | BuilderElement
+ ): Promise;
/**
* Delete a component from the Designer. If there are any instances of the Component within the site, they will
* be converted to regular Elements.
@@ -170,69 +226,75 @@ interface WebflowApi {
*/
getAllComponents(): Promise>;
/**
- * Retrieve a component based on its name and optionally its group.
- * Component instance.
- * @returns A Promise resolving to the component
+ * Search site components with optional fuzzy filtering.
+ * Returns a flat array of {@link ComponentSearchResult} objects in the same order as the
+ * Components panel (insertion order). When `options.q` is provided, results are filtered
+ * using FlexSearch (`tokenize: 'full'`) — the same algorithm used by the Components panel.
+ *
+ * @param options - Optional search options.
+ * @param options.q - Search query string. Omit or leave empty to return all components.
+ * @returns A Promise resolving to an array of {@link ComponentSearchResult} objects.
+ *
* @example
* ```ts
- * // Fetch a component by name only
- * const heroSection = await webflow.getComponentByName('Hero');
- * console.log(heroSection.id);
+ * // Get all components
+ * const all = await webflow.searchComponents();
*
- * // Fetch a component scoped to a group
- * const marketingHero = await webflow.getComponentByName('Marketing', 'Hero');
- * console.log(marketingHero.id);
+ * // Filter by name
+ * const heroes = await webflow.searchComponents({ q: 'Hero' });
+ * heroes.forEach(c => {
+ * console.log(c.name, c.instances, c.canEdit, c.library);
+ * });
* ```
*/
- getComponentByName(a: string, b?: string): Promise;
+ searchComponents(
+ options?: SearchComponentsOptions
+ ): Promise;
/**
- * Retrieves the component that is currently being edited.
- * @returns A Promise that resolves to the current component, or null if no component is currently being edited.
+ * Returns a component reference when the user is editing in-context or on the component canvas, or null if no component is being edited.
+ * @returns A Promise that resolves to a Component reference or null.
* @example
* ```ts
* const component = await webflow.getCurrentComponent();
* if (component) {
* const name = await component.getName();
- * console.log(`Currently editing component: ${name}`);
- * } else {
- * console.log('Not currently editing a component.');
+ * console.log(`Currently editing: ${name}`);
* }
* ```
*/
getCurrentComponent(): Promise;
/**
- * Searches for Components by name
- * @returns A Promise that resolves to an array or objects with information about matching Components, not the `Component` objects themselves.
+ * Get a Component by its unique identifier.
+ * @param id - The unique identifier of the component.
+ * @returns A Promise that resolves to the Component with the given id.
* @example
* ```ts
- * const heroes = await webflow.searchComponents({ q: 'Hero' });
- * console.log(heroes);
+ * const componentId = '4a669354-353a-97eb-795c-4471b406e043';
+ * const component = await webflow.getComponent(componentId);
* ```
- */
- searchComponents(options: SearchComponentsOptions?): Promise>
+ */
+ getComponent(id: ComponentId): Promise;
/**
- * Gets the number of instances of a component.
- * @returns A Promise that resolves to the number of instances of the component across the entire site.
+ * Get a Component by its display name. Only returns native site components.
+ * Throws if the matching component is a code component.
+ * @param name - The display name of the component.
* @example
* ```ts
- * // Audit component usage across the site
- * const components = await webflow.getAllComponents();
- * for (const component of components) {
- * const name = await component.getName();
- * const count = await component.getInstanceCount();
- * console.log(`${name}: ${count} instances`);
- * }
- * // Guard against removing a component that's still in use
- * const hero = components[0];
- * const instanceCount = await hero.getInstanceCount();
- * if (instanceCount > 0) {
- * console.log(`Cannot safely remove — ${instanceCount} instances exist`);
- * } else {
- * await webflow.unregisterComponent(hero);
- * }
+ * const component = await webflow.getComponentByName('Hero');
+ * ```
+ */
+ getComponentByName(name: string): Promise;
+ /**
+ * Get a Component by its group and display name. Only returns native site components.
+ * Throws if the matching component is a code component.
+ * @param group - The group name the component belongs to.
+ * @param name - The display name of the component.
+ * @example
+ * ```ts
+ * const component = await webflow.getComponentByName('Marketing', 'Hero');
* ```
*/
- getInstanceCount(): Promise;
+ getComponentByName(group: string, name: string): Promise;
/**
* Focus the designer on a Component. When a component is in focus, all Globals pertain specifically to that
* Component, not the entire Site.
@@ -244,46 +306,48 @@ interface WebflowApi {
* await webflow.enterComponent(heroComponentInstance);
* ```
*/
+ enterComponent(instance: ComponentElement): Promise;
/**
- * Open a Component's canvas or a page for editing in the Designer.
- * @param target - A Component, ComponentInstance, or page object
- * @returns A Promise that resolves when the canvas or page switch is successful.
+ * Return to the broader context of the entire site or page.
+ * @returns A Promise that resolves when the page switch is successful.
* @example
* ```ts
- * `// Open a Component canvas by ID
- * await webflow.openCanvas({ componentId: 'component-id' });
- *
- * // Open a Component canvas by Component reference
- * const components = await webflow.getAllComponents();
- * const hero = components[0];
- * await webflow.openCanvas(hero);
- *
- * // Open a Component canvas via an instance reference
- * const selectedElement = await webflow.getSelectedElement();
- * if (selectedElement?.type === 'ComponentInstance') {
- * await webflow.openCanvas(selectedElement as ComponentElement);
- * }
- *
- * // Navigate to a page by ID
- * await webflow.openCanvas({ pageId: 'page-id' });
+ * await webflow.exitComponent();
+ * ```
+ */
+ exitComponent(): Promise;
+ /**
+ * Navigate the Designer to a component canvas or page.
+ * @param options - An object with either pageId or componentId.
+ * @returns A Promise that resolves when the navigation is complete.
+ * @example
+ * ```ts
+ * // Open a component canvas by component id
+ * await webflow.openCanvas({componentId: '4a669354-353a-97eb-795c-4471b406e043'});
*
- * // Navigate to a page by reference (equivalent to webflow.switchPage)
- * const pagesAndFolders = await webflow.getAllPagesAndFolders();
- * const pages = pagesAndFolders?.filter((i): i is Page => i.type === 'Page');
- * await webflow.openCanvas(pages[0]);
+ * // Open a component canvas by page id
+ * await webflow.openCanvas({pageId: '123'});
* ```
*/
- openCanvas()
- enterComponent(instance: ComponentElement): Promise;
+ openCanvas(
+ options: OpenCanvasByComponentId | OpenCanvasByPageId
+ ): Promise;
/**
- * Return to the broader context of the entire site or page.
- * @returns A Promise that resolves when the page switch is successful.
+ * Navigate the Designer to a component canvas or page using a reference.
+ * @param reference - A Component, ComponentElement, or Page reference.
+ * @returns A Promise that resolves when the navigation is complete.
* @example
* ```ts
- * await webflow.exitComponent();
+ * // Open a component canvas by component
+ * const heroComponent = await webflow.getComponent('4a669354-353a-97eb-795c-4471b406e043');
+ * await webflow.openCanvas(heroComponent);
+ *
+ * // Open a component canvas by page
+ * const myPage = await webflow.getPage('123');
+ * await webflow.openCanvas(myPage);
* ```
*/
- exitComponent(): Promise;
+ openCanvas(reference: Component | ComponentElement | Page): Promise;
/**
* Get Root element. When the designer is focused or "entered" into a Component, this method will get the
* outermost element in the Component.
@@ -321,7 +385,7 @@ interface WebflowApi {
/**
* Creates a new style with the provided name.
* @param name - The name for the new style
- * @param opts - Options for the new style. An object containing the following properties:
+ * @param options - Options for the new style. An object containing the following properties:
* - parent: A Style object representing the parent style block. Used for creating a combo class.
* @returns a Promise that resolves to the Style object representing the newly created style.
* @example
@@ -334,7 +398,6 @@ interface WebflowApi {
* ```
*/
createStyle(name: string, options?: {parent?: Style}): Promise