diff --git a/package-lock.json b/package-lock.json index 1140d69..bc81fbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "@types/node": "^22.13.5", "@types/prismjs": "^1.26.3", "@vitejs/plugin-react": "^4.0.0", - "@webflow/designer-extension-typings": "^2.0.25", + "@webflow/designer-extension-typings": "^2.0.29", "@webflow/webflow-cli": "^1.8.6", "@xatom/wf-app-hot-reload": "^1.0.5", "concurrently": "^6.3.0", @@ -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.29", + "resolved": "https://registry.npmjs.org/@webflow/designer-extension-typings/-/designer-extension-typings-2.0.29.tgz", + "integrity": "sha512-gq3nGE4yv3W/kHi8/9mpw5OFJT7w5GV5E/ycwQ3ltViQzCzzDJC5ee4iRRg+Bg+D5PT1Z+MOzGkrTVx7XGeuwg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 43860f1..cc93b01 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@types/node": "^22.13.5", "@types/prismjs": "^1.26.3", "@vitejs/plugin-react": "^4.0.0", - "@webflow/designer-extension-typings": "^2.0.25", + "@webflow/designer-extension-typings": "^2.0.29", "@webflow/webflow-cli": "^1.8.6", "@xatom/wf-app-hot-reload": "^1.0.5", "concurrently": "^6.3.0", diff --git a/src/components/PermissionsMap.tsx b/src/components/PermissionsMap.tsx index 837beba..a4fb974 100644 --- a/src/components/PermissionsMap.tsx +++ b/src/components/PermissionsMap.tsx @@ -49,6 +49,9 @@ export const permissionsMap: PermissionsMap = { setName: { permissions: ['canModifyComponents'] }, getRootElement: { permissions: ['canAccessCanvas'] }, registerComponent: { permissions: ['canCreateComponents'] }, + createComponentWithoutRoot: { permissions: ['canCreateComponents'] }, + createComponentFromElement: { permissions: ['canCreateComponents'] }, + duplicateComponent: { permissions: ['canCreateComponents'] }, unregisterComponent: { permissions: ['canCreateComponents'] }, getAllComponents: { permissions: ['canAccessCanvas'] }, enterComponent: { permissions: ['canModifyComponents'] }, diff --git a/src/designer-extension-typings/api.d.ts b/src/designer-extension-typings/api.d.ts index 0e1c2ce..a78569b 100644 --- a/src/designer-extension-typings/api.d.ts +++ b/src/designer-extension-typings/api.d.ts @@ -123,7 +123,7 @@ interface WebflowApi { /** * 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. * @example * ```ts @@ -136,8 +136,33 @@ interface WebflowApi { */ registerComponent( name: string, - root: AnyElement | ElementPreset | Component + root?: AnyElement | ElementPreset | Component | ComponentId ): Promise; + + /** + * Create a component by converting an element into a component or duplicating a component. + * @param options - The options for the new 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. + * @example + * ```ts + * // Convert an existing element into a component + * const selectedElement = await webflow.getSelectedElement() + * const heroComponent = await webflow.registerComponent( + * { + * name: 'Hero Section', + * group: 'Sections', + * description: 'Main hero with heading and CTA' + * }, + * selectedElement + * ) + * ``` + */ + registerComponent( + options: ComponentOptions, + root?: AnyElement | ElementPreset | Component | ComponentId + ): 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. diff --git a/src/designer-extension-typings/components.d.ts b/src/designer-extension-typings/components.d.ts index ffd5917..e2657c2 100644 --- a/src/designer-extension-typings/components.d.ts +++ b/src/designer-extension-typings/components.d.ts @@ -35,3 +35,14 @@ interface Component { } type ComponentId = string; + +interface ComponentOptions { + /** The name of the component (required) */ + name: string; + /** The group/folder to place the component in (optional) */ + group?: string; + /** A description for the component (optional) */ + description?: string; + /** Whether to replace the existing element (optional) */ + replace?: Boolean; +} diff --git a/src/examples/components.ts b/src/examples/components.ts index 6a64903..f8fc2e9 100644 --- a/src/examples/components.ts +++ b/src/examples/components.ts @@ -70,6 +70,37 @@ export const Components = { } }, + createComponentWithoutRoot: async () => { + // Create a hero component in the Sections group that is not within an existing element + const hero = await webflow.registerComponent({ + name: 'Hero Section', + group: 'Sections', + description: 'A reusable hero section with heading and CTA', + }); + console.log(`Component registered with ID: ${hero.id}`) + }, + + createComponentFromElement: async () => { + // Convert an existing element into a component, by default replacing the element with the new component + const selectedElement = await webflow.getSelectedElement() + if (selectedElement) { + const heroComponent = await webflow.registerComponent( + { + name: 'Hero Section', + group: 'Sections', + description: 'Main hero with heading and CTA' + }, + selectedElement + ) + } + }, + + duplicateComponent: async () => { + // Duplicate a component + const [original] = await webflow.getAllComponents() + const copy = await webflow.registerComponent({ name: 'Card Copy' }, original) + }, + deleteComponent: async () => { // Get selected element const selectedElement = await webflow.getSelectedElement()