From 837403685e128fc9f25d2026dfc1dc22bd53fe31 Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Fri, 22 Aug 2025 18:01:44 -0400 Subject: [PATCH 1/8] alt TS samples, constants fixing/guidance, page mgmt & selectionChange how-tos --- .../guides/getting_started/hello-world.md | 4 + .../guides/learn/how_to/create_renditions.md | 4 + .../guides/learn/how_to/group_elements.md | 55 + src/pages/guides/learn/how_to/index.md | 4 + src/pages/guides/learn/how_to/manage_pages.md | 643 ++++++++ .../guides/learn/how_to/page_metadata.md | 2 + .../learn/how_to/resize_rescale_elements.md | 52 + .../guides/learn/how_to/selection_events.md | 1427 +++++++++++++++++ src/pages/guides/learn/how_to/use_color.md | 137 ++ src/pages/guides/learn/how_to/use_geometry.md | 97 +- src/pages/guides/learn/how_to/use_text.md | 97 ++ .../learn/platform_concepts/document-api.md | 11 + src/pages/guides/support/faq.md | 45 +- src/pages/references/addonsdk/addonsdk-app.md | 190 +++ .../references/addonsdk/addonsdk-constants.md | 229 ++- .../references/addonsdk/addonsdk-instance.md | 2 +- src/pages/references/addonsdk/app-document.md | 157 +- src/pages/references/addonsdk/app-oauth.md | 278 ++++ src/pages/references/addonsdk/index.md | 6 +- 19 files changed, 3424 insertions(+), 16 deletions(-) create mode 100644 src/pages/guides/learn/how_to/manage_pages.md create mode 100644 src/pages/guides/learn/how_to/selection_events.md diff --git a/src/pages/guides/getting_started/hello-world.md b/src/pages/guides/getting_started/hello-world.md index b30d911f3..e0e7e0f6a 100644 --- a/src/pages/guides/getting_started/hello-world.md +++ b/src/pages/guides/getting_started/hello-world.md @@ -266,6 +266,10 @@ Manifest updates Any changes to the `manifest.json` will _require a manual reload of your add-on_. The **Add-on Development** panel will indicate this in the log messages, and the **Refresh** button can be used to reload the add-on directly within Adobe Express. + + +**📦 Working with SDK Constants:** As you build more complex add-ons, you'll use SDK constants like `Range`, `RenditionFormat`, and `SupportedMimeTypes`. These constants use different import patterns - some require named imports while others support multiple approaches. See the [import patterns guide](../../references/addonsdk/addonsdk-constants.md#import-patterns) for complete details. +
Click to see the screenshot diff --git a/src/pages/guides/learn/how_to/create_renditions.md b/src/pages/guides/learn/how_to/create_renditions.md index d4634ec2e..028ebb1f7 100644 --- a/src/pages/guides/learn/how_to/create_renditions.md +++ b/src/pages/guides/learn/how_to/create_renditions.md @@ -69,6 +69,10 @@ Renditions are created via the [`createRendition()`](../../../references/addonsd 1. [`renditionOptions`](../../../references/addonsdk/app-document.md#renditionoptions): controls the page range that is meant to be exported and the file format (jpg, png, mp4, pdf, and pptx). 2. [`renditionIntent`](../../../references/addonsdk/addonsdk-constants.md) constant (optional): controls the intent of the exported content (preview, export, print). + + +**SDK Constants Import:** This guide uses several SDK constants like `Range`, `RenditionFormat`, and `RenditionIntent`. These constants support dual access (both named imports and `addOnUISdk.constants.*`). See the [constants import patterns guide](../../../references/addonsdk/addonsdk-constants.md#import-patterns) for details on importing SDK constants correctly. + ## Check export permissions The `exportAllowed()` method determines whether the current document can be exported based on its review status in collaborative workflows. This applies mainly to [enterprise customers using Adobe Express's review and approval features](https://business.adobe.com/products/workfront/integrations/express.html), where documents may be restricted from export until approved by designated reviewers. diff --git a/src/pages/guides/learn/how_to/group_elements.md b/src/pages/guides/learn/how_to/group_elements.md index 91d9d16d5..02fc63b85 100644 --- a/src/pages/guides/learn/how_to/group_elements.md +++ b/src/pages/guides/learn/how_to/group_elements.md @@ -55,6 +55,10 @@ To create a Group, you can use the [`editor.createGroup()`](../../../references/ ### Example + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -78,6 +82,32 @@ greetingsGroup.children.append(greeting, saluto); editor.context.insertionParent.children.append(greetingsGroup); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, StandaloneTextNode, GroupNode, ContainerNode } from "express-document-sdk"; + +// Create some Text +const greeting: StandaloneTextNode = editor.createText("Hiya!"); +greeting.translation = { x: 100, y: 50 }; + +// Create some other Text +const saluto: StandaloneTextNode = editor.createText("Ciao!"); +saluto.translation = { x: 100, y: 150 }; + +// Create a Group 👈 +const greetingsGroup: GroupNode = editor.createGroup(); +greetingsGroup.translation = { x: 100, y: 100 }; + +// Append the Text nodes to the Group 👈 +greetingsGroup.children.append(greeting, saluto); + +// Append the Group to the page 👈 +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(greetingsGroup); +``` + Group append order @@ -92,6 +122,10 @@ Groups can be nested, meaning that you can have a Group inside another Group; ju ### Example + + +#### JavaScript + ```js // sandbox/code.js @@ -111,6 +145,27 @@ outerGroup.children.append(innerGroup, salutation); editor.context.insertionParent.children.append(outerGroup); ``` +#### TypeScript + +```ts +// sandbox/code.js + +// Create three different Text nodes +const greeting: StandaloneTextNode = editor.createText("Hiya!"); +const saluto: StandaloneTextNode = editor.createText("Ciao!"); +const salutation: StandaloneTextNode = editor.createText("Salut!"); + +// Create an inner Group with the first two Text nodes +const innerGroup: GroupNode = editor.createGroup(); +innerGroup.children.append(greeting, saluto); + +// Create an outer Group with the inner Group and the third Text node +const outerGroup: GroupNode = editor.createGroup(); +outerGroup.children.append(innerGroup, salutation); + +editor.context.insertionParent.children.append(outerGroup); +``` + This code results in the following grouping: ```txt diff --git a/src/pages/guides/learn/how_to/index.md b/src/pages/guides/learn/how_to/index.md index e7fb55014..576dfd339 100644 --- a/src/pages/guides/learn/how_to/index.md +++ b/src/pages/guides/learn/how_to/index.md @@ -50,9 +50,11 @@ We're constantly adding new how-tos, so make sure to check back often. If you're - [Use PDF and PowerPoint](./use_pdf_powerpoint.md) - [Group Elements](./group_elements.md) - [Position Elements](./position_elements.md) + - [Selection Events and Methods](./selection_events.md) - Use Metadata - [Document Metadata](./document_metadata.md) - [Page Metadata](./page_metadata.md) + - [Manage Pages](./manage_pages.md) - [Element Metadata](./element_metadata.md) - Exporting & Output - [Create Renditions](./create_renditions.md) @@ -78,8 +80,10 @@ We're constantly adding new how-tos, so make sure to check back often. If you're | | [Use PDF and PowerPoint](./use_pdf_powerpoint.md) | | | [Group Elements](./group_elements.md) | | | [Position Elements](./position_elements.md) | +| | [Selection Events and Methods](./selection_events.md) | | Use Metadata | [Document Metadata](./document_metadata.md) | | | [Page Metadata](./page_metadata.md) | +| | [Manage Pages](./manage_pages.md) | | | [Element Metadata](./element_metadata.md) | | Exporting & Output | [Create Renditions](./create_renditions.md) | | | [Manage with Premium Content](./premium_content.md) | diff --git a/src/pages/guides/learn/how_to/manage_pages.md b/src/pages/guides/learn/how_to/manage_pages.md new file mode 100644 index 000000000..51249b142 --- /dev/null +++ b/src/pages/guides/learn/how_to/manage_pages.md @@ -0,0 +1,643 @@ +--- +keywords: + - Adobe Express + - Express Add-on SDK + - Express Editor + - Adobe Express + - Add-on SDK + - SDK + - JavaScript + - Extend + - Extensibility + - API + - Page + - addPage + - PageNode + - PageList + - Document + - Navigation +title: Manage Pages +description: Learn how to create, navigate, and manage pages in Adobe Express documents. +contributors: + - https://github.com/hollyschinsky +faq: + questions: + - question: "How do I add a page programmatically?" + answer: "Use `editor.addPage()` method with page dimensions. There is no `createPage()` method." + + - question: "Why doesn't createPage() work?" + answer: "The Document API uses `addPage()` for pages, not `createPage()`. Use `editor.addPage(dimensions)` instead." + + - question: "How do I get the current page?" + answer: "Use `editor.context.currentPage` to access the currently active page." + + - question: "How do I navigate between pages?" + answer: "Adding a page automatically switches to it. You can also access pages via `editor.documentRoot.pages`." + + - question: "What happens when I add a page?" + answer: "A new page with a default artboard is created and automatically becomes the active page and insertion parent." + + - question: "Can I remove pages?" + answer: "Currently, the Document API doesn't provide a direct method to remove pages programmatically." + + - question: "How do I access all pages in a document?" + answer: "Use `editor.documentRoot.pages` to access the PageList containing all pages." + + - question: "What are the minimum requirements for a page?" + answer: "Every page must have at least one artboard. The `addPage()` method automatically creates a default artboard." +--- + +# Manage Pages + +Learn how to programmatically create, access, and manage pages in Adobe Express documents using the Document API. + + + +Important: Use `addPage()`, not `createPage()` + +The Adobe Express Document API uses **`addPage()`** to create new pages, not `createPage()`. This is a common source of confusion, especially for LLMs and developers familiar with other APIs. There is no `createPage()` method in the Document API. + +Always use `editor.addPage(dimensions)` when you need to add pages programmatically. + +## Understanding Pages in Adobe Express + +In Adobe Express, documents are organized hierarchically: +- **Document** (root) + - **Pages** (timeline sequence) + - **Artboards** (scenes within a page) + - **Content** (text, shapes, media, etc.) + +Every page contains at least one artboard, and all artboards within a page share the same dimensions. + +## Add a Page + +Use the [`addPage()`](../../../references/document-sandbox/document-apis/classes/PageList.md#addpage) method to create a new page with specified dimensions. + +### Example: Add a Standard Page + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Define page dimensions (width x height in pixels) +const pageGeometry = { + width: 1080, + height: 1080 +}; + +// Add a new page with the specified dimensions +const newPage = editor.addPage(pageGeometry); + +console.log("New page created:", newPage); +console.log("Page dimensions:", newPage.width, "x", newPage.height); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, RectangleGeometry } from "express-document-sdk"; + +// Define page dimensions (width x height in pixels) +const pageGeometry: RectangleGeometry = { + width: 1080, + height: 1080 +}; + +// Add a new page with the specified dimensions +const newPage: PageNode = editor.addPage(pageGeometry); + +console.log("New page created:", newPage); +console.log("Page dimensions:", newPage.width, "x", newPage.height); +``` + + + +When you call `addPage()`, the new page automatically becomes the active page and the default insertion point for new content. The viewport also switches to display the new page's artboard. + +### Example: Add Pages with Different Dimensions + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Add an Instagram post page (square) +const instagramPage = editor.addPage({ + width: 1080, + height: 1080 +}); + +// Add a story page (vertical) +const storyPage = editor.addPage({ + width: 1080, + height: 1920 +}); + +// Add a landscape page +const landscapePage = editor.addPage({ + width: 1920, + height: 1080 +}); + +console.log("Created 3 pages with different dimensions"); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, RectangleGeometry } from "express-document-sdk"; + +// Add an Instagram post page (square) +const instagramPage: PageNode = editor.addPage({ + width: 1080, + height: 1080 +} as RectangleGeometry); + +// Add a story page (vertical) +const storyPage: PageNode = editor.addPage({ + width: 1080, + height: 1920 +} as RectangleGeometry); + +// Add a landscape page +const landscapePage: PageNode = editor.addPage({ + width: 1920, + height: 1080 +} as RectangleGeometry); + +console.log("Created 3 pages with different dimensions"); +``` + +## Access Pages + +### Get the Current Page + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get the currently active page +const currentPage = editor.context.currentPage; + +console.log("Current page dimensions:", currentPage.width, "x", currentPage.height); +console.log("Number of artboards:", currentPage.artboards.length); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode } from "express-document-sdk"; + +// Get the currently active page +const currentPage: PageNode = editor.context.currentPage; + +console.log("Current page dimensions:", currentPage.width, "x", currentPage.height); +console.log("Number of artboards:", currentPage.artboards.length); +``` + +### Access All Pages + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get all pages in the document +const allPages = editor.documentRoot.pages; + +console.log("Total pages in document:", allPages.length); + +// Iterate through all pages +for (const page of allPages) { + console.log(`Page dimensions: ${page.width} x ${page.height}`); + console.log(`Artboards in this page: ${page.artboards.length}`); +} + +// Access specific pages by index +const firstPage = allPages[0]; +const lastPage = allPages[allPages.length - 1]; +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageList, PageNode } from "express-document-sdk"; + +// Get all pages in the document +const allPages: PageList = editor.documentRoot.pages; + +console.log("Total pages in document:", allPages.length); + +// Iterate through all pages +for (const page of allPages) { + console.log(`Page dimensions: ${page.width} x ${page.height}`); + console.log(`Artboards in this page: ${page.artboards.length}`); +} + +// Access specific pages by index +const firstPage: PageNode = allPages[0]; +const lastPage: PageNode = allPages[allPages.length - 1]; +``` + +## Working with Page Content + +### Add Content to a Specific Page + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Create a new page +const newPage = editor.addPage({ + width: 1080, + height: 1080 +}); + +// The new page is automatically active, so content will be added to it +const textNode = editor.createText("Content on the new page!"); +textNode.translation = { x: 100, y: 100 }; + +// Add to the current insertion parent (the new page's artboard) +editor.context.insertionParent.children.append(textNode); + +console.log("Added text to the new page"); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, StandaloneTextNode, ContainerNode } from "express-document-sdk"; + +// Create a new page +const newPage: PageNode = editor.addPage({ + width: 1080, + height: 1080 +}); + +// The new page is automatically active, so content will be added to it +const textNode: StandaloneTextNode = editor.createText("Content on the new page!"); +textNode.translation = { x: 100, y: 100 }; + +// Add to the current insertion parent (the new page's artboard) +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(textNode); + +console.log("Added text to the new page"); +``` + +### Work with Page Artboards + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get the current page +const currentPage = editor.context.currentPage; + +// Access the page's artboards +const artboards = currentPage.artboards; +console.log("Number of artboards:", artboards.length); + +// Get the first (and typically only) artboard +const firstArtboard = artboards.first; +console.log("First artboard dimensions:", firstArtboard.width, "x", firstArtboard.height); + +// Add content directly to a specific artboard +const rect = editor.createRectangle(); +rect.width = 200; +rect.height = 200; +rect.translation = { x: 50, y: 50 }; + +firstArtboard.children.append(rect); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, ArtboardList, ArtboardNode, RectangleNode } from "express-document-sdk"; + +// Get the current page +const currentPage: PageNode = editor.context.currentPage; + +// Access the page's artboards +const artboards: ArtboardList = currentPage.artboards; +console.log("Number of artboards:", artboards.length); + +// Get the first (and typically only) artboard +const firstArtboard: ArtboardNode = artboards.first!; +console.log("First artboard dimensions:", firstArtboard.width, "x", firstArtboard.height); + +// Add content directly to a specific artboard +const rect: RectangleNode = editor.createRectangle(); +rect.width = 200; +rect.height = 200; +rect.translation = { x: 50, y: 50 }; + +firstArtboard.children.append(rect); +``` + +## Common Patterns and Best Practices + +### Page Creation Workflow + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +function createTemplatePages() { + // Define common page sizes + const pageSizes = { + instagram: { width: 1080, height: 1080 }, + story: { width: 1080, height: 1920 }, + landscape: { width: 1920, height: 1080 }, + a4: { width: 595, height: 842 } + }; + + // Create pages for each template + const pages = {}; + + for (const [name, dimensions] of Object.entries(pageSizes)) { + const page = editor.addPage(dimensions); + pages[name] = page; + + // Add a title to each page + const title = editor.createText(`${name.toUpperCase()} Template`); + title.translation = { x: 50, y: 50 }; + editor.context.insertionParent.children.append(title); + + console.log(`Created ${name} page: ${dimensions.width}x${dimensions.height}`); + } + + return pages; +} + +// Create template pages +const templatePages = createTemplatePages(); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, RectangleGeometry, StandaloneTextNode } from "express-document-sdk"; + +interface PageSizes { + [key: string]: RectangleGeometry; +} + +function createTemplatePages(): { [key: string]: PageNode } { + // Define common page sizes + const pageSizes: PageSizes = { + instagram: { width: 1080, height: 1080 }, + story: { width: 1080, height: 1920 }, + landscape: { width: 1920, height: 1080 }, + a4: { width: 595, height: 842 } + }; + + // Create pages for each template + const pages: { [key: string]: PageNode } = {}; + + for (const [name, dimensions] of Object.entries(pageSizes)) { + const page: PageNode = editor.addPage(dimensions); + pages[name] = page; + + // Add a title to each page + const title: StandaloneTextNode = editor.createText(`${name.toUpperCase()} Template`); + title.translation = { x: 50, y: 50 }; + editor.context.insertionParent.children.append(title); + + console.log(`Created ${name} page: ${dimensions.width}x${dimensions.height}`); + } + + return pages; +} + +// Create template pages +const templatePages = createTemplatePages(); +``` + +### Check Page Properties + +For detailed page information including content analysis and print readiness, see the [Page Metadata how-to guide](page_metadata.md). + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +function analyzeDocument() { + const pages = editor.documentRoot.pages; + + console.log("=== Document Analysis ==="); + console.log(`Total pages: ${pages.length}`); + + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + console.log(`\nPage ${i + 1}:`); + console.log(` Dimensions: ${page.width} x ${page.height}`); + console.log(` Artboards: ${page.artboards.length}`); + + // Count content in each artboard + for (let j = 0; j < page.artboards.length; j++) { + const artboard = page.artboards[j]; + console.log(` Artboard ${j + 1}: ${artboard.children.length} items`); + } + } +} + +// Analyze the current document +analyzeDocument(); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageList, PageNode, ArtboardNode } from "express-document-sdk"; + +function analyzeDocument(): void { + const pages: PageList = editor.documentRoot.pages; + + console.log("=== Document Analysis ==="); + console.log(`Total pages: ${pages.length}`); + + for (let i = 0; i < pages.length; i++) { + const page: PageNode = pages[i]; + console.log(`\nPage ${i + 1}:`); + console.log(` Dimensions: ${page.width} x ${page.height}`); + console.log(` Artboards: ${page.artboards.length}`); + + // Count content in each artboard + for (let j = 0; j < page.artboards.length; j++) { + const artboard: ArtboardNode = page.artboards[j]; + console.log(` Artboard ${j + 1}: ${artboard.children.length} items`); + } + } +} + +// Analyze the current document +analyzeDocument(); +``` + +## Key Concepts + +### Pages vs Artboards + +- **Pages**: Top-level containers in the document timeline +- **Artboards**: "Scenes" within a page containing the actual content +- All artboards within a page share the same dimensions +- When you add a page, it automatically gets one default artboard + +### Insertion Context + +- Adding a page automatically makes it the active page +- `editor.context.insertionParent` points to the active artboard +- New content is added to the current insertion parent +- The viewport switches to display the new page + +### Common Pitfalls + +1. **Don't use `createPage()`** - This method doesn't exist. Use `addPage()` instead. +2. **Remember the geometry parameter** - `addPage()` requires page dimensions. +3. **Understand automatic navigation** - Adding a page switches to it automatically. +4. **Page dimensions affect all artboards** - All artboards in a page share the same size. + +## Integration with Other APIs + +### Using with Metadata APIs + +Pages created with `addPage()` can be used with other Document APIs, particularly for retrieving metadata. See the [Page Metadata how-to guide](page_metadata.md) for complete examples. + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Add a page and get its metadata +const newPage = editor.addPage({ width: 1080, height: 1080 }); + +// Get the page ID for use with Add-on UI SDK metadata APIs +console.log("New page ID:", newPage.id); + +// You can use this ID with the Add-on UI SDK to get detailed metadata +// See the Page Metadata guide for complete examples: +// const pageMetadata = await addOnUISdk.app.document.getPagesMetadata({ +// pageIds: [newPage.id] +// }); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode } from "express-document-sdk"; + +// Add a page and get its metadata +const newPage: PageNode = editor.addPage({ width: 1080, height: 1080 }); + +// Get the page ID for use with Add-on UI SDK metadata APIs +console.log("New page ID:", newPage.id); + +// You can use this ID with the Add-on UI SDK to get detailed metadata +// See the Page Metadata guide for complete examples: +// const pageMetadata = await addOnUISdk.app.document.getPagesMetadata({ +// pageIds: [newPage.id] +// }); +``` + +## FAQs + +#### Q: How do I add a page programmatically? + +**A:** Use `editor.addPage(dimensions)` with page dimensions. There is no `createPage()` method. + +#### Q: Why doesn't createPage() work? + +**A:** The Document API uses `addPage()` for pages, not `createPage()`. Use `editor.addPage(dimensions)` instead. + +#### Q: How do I get the current page? + +**A:** Use `editor.context.currentPage` to access the currently active page. + +#### Q: How do I navigate between pages? + +**A:** Adding a page automatically switches to it. You can also access pages via `editor.documentRoot.pages`. + +#### Q: What happens when I add a page? + +**A:** A new page with a default artboard is created and automatically becomes the active page and insertion parent. + +#### Q: Can I remove pages? + +**A:** Currently, the Document API doesn't provide a direct method to remove pages programmatically. + +#### Q: How do I access all pages in a document? + +**A:** Use `editor.documentRoot.pages` to access the PageList containing all pages. + +#### Q: What are the minimum requirements for a page? + +**A:** Every page must have at least one artboard. The `addPage()` method automatically creates a default artboard. + +## Related Topics + +### Page Information and Metadata +- **[Page Metadata](page_metadata.md)** - Get detailed information about pages, including dimensions, content types, and selected page IDs +- **[Document Metadata](document_metadata.md)** - Access document-level information and listen for document events +- **[getSelectedPageIds() API](../../../references/addonsdk/app-document.md#getselectedpageids)** - Retrieve IDs of currently selected pages (experimental) + +### Working with Page Content +- **[Position Elements](position_elements.md)** - Position and arrange content within pages and artboards +- **[Group Elements](group_elements.md)** - Organize page content using groups +- **[Use Geometry](use_geometry.md)** - Create shapes and geometric elements for your pages +- **[Use Text](use_text.md)** - Add and style text content on pages +- **[Use Images](use_images.md)** - Import and work with images on pages + +### Document Structure and Context +- **[Document API Concepts](../../platform_concepts/document-api.md)** - Understanding the Adobe Express Document Object Model +- **[Context API Reference](../../../references/document-sandbox/document-apis/classes/Context.md)** - Current page, selection, and insertion context +- **[PageNode API Reference](../../../references/document-sandbox/document-apis/classes/PageNode.md)** - Detailed page node documentation +- **[PageList API Reference](../../../references/document-sandbox/document-apis/classes/PageList.md)** - Page list management methods + +### Advanced Topics +- **[Create Renditions](create_renditions.md)** - Export specific pages or entire documents +- **[Element Metadata](element_metadata.md)** - Store private metadata on page elements +- **[Local Data Management](local_data_management.md)** - Persist page-related state in your add-on diff --git a/src/pages/guides/learn/how_to/page_metadata.md b/src/pages/guides/learn/how_to/page_metadata.md index 72c66cf97..3664c0a6b 100644 --- a/src/pages/guides/learn/how_to/page_metadata.md +++ b/src/pages/guides/learn/how_to/page_metadata.md @@ -49,6 +49,8 @@ faq: # Page Metadata +This guide shows how to retrieve detailed information about pages in Adobe Express documents. For creating and managing pages programmatically, see the [Manage Pages how-to guide](manage_pages.md). + ## Get the Page Metadata If you want to retrieve metadata for pages in the document, use the [`getPagesMetadata()`](../../../references/addonsdk/app-document.md#getpagesmetadata) method in the `addOnUISdk.app.document` object. The method expects an object with a `range` and optional `pageIds` properties. diff --git a/src/pages/guides/learn/how_to/resize_rescale_elements.md b/src/pages/guides/learn/how_to/resize_rescale_elements.md index 1adf47020..d1b7067a2 100644 --- a/src/pages/guides/learn/how_to/resize_rescale_elements.md +++ b/src/pages/guides/learn/how_to/resize_rescale_elements.md @@ -68,6 +68,10 @@ Rescaling operations maintain the aspect ratio of elements while changing their Use `rescaleProportionalToWidth()` to adjust an element's width while maintaining its aspect ratio. The height will automatically adjust proportionally. + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -87,12 +91,38 @@ console.log(`New dimensions: ${rect.width} x ${rect.height}`); // New dimensions: 300 x 150 ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, RectangleNode, ContainerNode } from "express-document-sdk"; + +// Create a rectangle with specific dimensions +const rect: RectangleNode = editor.createRectangle(); +rect.width = 200; +rect.height = 100; +rect.fill = editor.makeColorFill(colorUtils.fromHex("#3498db")); +// Add it to the page +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(rect); + +// Rescale to 300px width - height becomes 150px automatically +rect.rescaleProportionalToWidth(300); + +console.log(`New dimensions: ${rect.width} x ${rect.height}`); +// New dimensions: 300 x 150 +``` + ![Rescale by Width](./images/rescale--rescale-proportional-width.png) ### Example: Rescale by Height Similarly, use `rescaleProportionalToHeight()` to adjust an element's height while maintaining its aspect ratio. The width will automatically adjust proportionally. + + +#### JavaScript + ```js // sandbox/code.js // import { editor } from "express-document-sdk"; @@ -113,6 +143,28 @@ console.log( // New bounds: 300 x 150 ``` +#### TypeScript + +```ts +// sandbox/code.js +// import { editor, colorUtils, EllipseNode } from "express-document-sdk"; + +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 100; // radius x = 100 (width = 200) +ellipse.ry = 50; // radius y = 50 (height = 100) +ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#F0B76C")); + +editor.context.insertionParent.children.append(ellipse); + +// Rescale to 150px height - width becomes 300px automatically +ellipse.rescaleProportionalToHeight(150); + +console.log( + `New bounds: ${ellipse.boundsLocal.width} x ${ellipse.boundsLocal.height}` +); +// New bounds: 300 x 150 +``` + ![Rescale by Height](./images/rescale--rescale-proportional-height.png) ### Example: Rescaling with Styled Elements diff --git a/src/pages/guides/learn/how_to/selection_events.md b/src/pages/guides/learn/how_to/selection_events.md new file mode 100644 index 000000000..8b203ee8f --- /dev/null +++ b/src/pages/guides/learn/how_to/selection_events.md @@ -0,0 +1,1427 @@ +--- +keywords: + - Adobe Express + - Express Add-on SDK + - Express Editor + - Adobe Express + - Add-on SDK + - SDK + - JavaScript + - Extend + - Extensibility + - API + - Selection + - selectionChange + - Events + - Context + - Node +title: Selection Events and Methods +description: Learn how to work with selections, handle selection changes, and respond to user interactions in Adobe Express documents. +contributors: + - https://github.com/hollyschinsky +faq: + questions: + - question: "How do I get the current selection?" + answer: "Use `editor.context.selection` to get an array of currently selected nodes." + + - question: "How do I listen for selection changes?" + answer: "Use `editor.context.on('selectionChange', callback)` to register a selection change handler." + + - question: "How do I programmatically select elements?" + answer: "Set `editor.context.selection = [node]` or `editor.context.selection = [node1, node2]` for multiple elements." + + - question: "What's the difference between selection and selectionIncludingNonEditable?" + answer: "`selection` only includes editable nodes, while `selectionIncludingNonEditable` also includes locked/non-editable nodes." + + - question: "Can I modify the document in a selection change callback?" + answer: "No, avoid making document changes in selection change callbacks as it may destabilize the application." + + - question: "How do I clear the selection?" + answer: "Set `editor.context.selection = []` or `editor.context.selection = undefined`." + + - question: "What are the selection rules?" + answer: "Nodes must be within the current artboard, ancestors cannot be selected with descendants, and locked nodes are filtered out." + + - question: "How do I unregister selection event handlers?" + answer: "Use `editor.context.off('selectionChange', handlerId)` with the ID returned from the `on()` method." +--- + +# Selection Events and Methods + +Learn how to work with user selections, handle selection changes, and respond to user interactions in Adobe Express documents using the Document API. + + + +Document API Context Required + +Selection methods and events are part of the Document API and require the document sandbox environment. These examples should be used in your `sandbox/code.js` file, not in the main iframe panel. + +Make sure your manifest includes `"documentSandbox": "sandbox/code.js"` in the entry points. + +## Understanding Selections + +In Adobe Express, selections represent the nodes (elements) that the user has currently selected. The selection system provides: + +- **Current selection access** - Get what's currently selected +- **Selection modification** - Programmatically change selections +- **Selection events** - React to selection changes +- **Selection filtering** - Handle locked/non-editable content + +### Selection Rules + +Adobe Express enforces several rules for selections: + +1. **Artboard constraint** - Only nodes within the current artboard can be selected +2. **Hierarchy filtering** - Cannot select both parent and child nodes simultaneously +3. **Locked node filtering** - Locked nodes are excluded from the main selection +4. **Editable-only** - Main selection only includes editable nodes + +## Getting the Current Selection + +### Basic Selection Access + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get the current selection +const selection = editor.context.selection; + +console.log("Selected nodes:", selection.length); + +// Check if anything is selected +if (editor.context.hasSelection) { + console.log("Something is selected"); + + // Process each selected node + selection.forEach((node, index) => { + console.log(`Node ${index + 1}:`, node.type); + }); +} else { + console.log("Nothing is selected"); +} +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, Node } from "express-document-sdk"; + +// Get the current selection +const selection: readonly Node[] = editor.context.selection; + +console.log("Selected nodes:", selection.length); + +// Check if anything is selected +if (editor.context.hasSelection) { + console.log("Something is selected"); + + // Process each selected node + selection.forEach((node: Node, index: number) => { + console.log(`Node ${index + 1}:`, node.type); + }); +} else { + console.log("Nothing is selected"); +} +``` + +### Including Non-Editable Selections + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get selection including locked/non-editable nodes +const fullSelection = editor.context.selectionIncludingNonEditable; +const editableSelection = editor.context.selection; + +console.log("Total selected (including locked):", fullSelection.length); +console.log("Editable selected:", editableSelection.length); + +if (fullSelection.length > editableSelection.length) { + console.log("Some locked nodes are selected"); + + // Find the locked nodes + const lockedNodes = fullSelection.filter(node => + !editableSelection.includes(node) + ); + + console.log("Locked nodes:", lockedNodes.length); +} +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, Node } from "express-document-sdk"; + +// Get selection including locked/non-editable nodes +const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; +const editableSelection: readonly Node[] = editor.context.selection; + +console.log("Total selected (including locked):", fullSelection.length); +console.log("Editable selected:", editableSelection.length); + +if (fullSelection.length > editableSelection.length) { + console.log("Some locked nodes are selected"); + + // Find the locked nodes + const lockedNodes: Node[] = fullSelection.filter((node: Node) => + !editableSelection.includes(node) + ); + + console.log("Locked nodes:", lockedNodes.length); +} +``` + +## Setting Selections Programmatically + +### Select Single Element + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Create a rectangle and select it +const rectangle = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 100; +rectangle.translation = { x: 50, y: 50 }; + +// Add to document +editor.context.insertionParent.children.append(rectangle); + +// Select the rectangle +editor.context.selection = rectangle; // Single node shortcut +// OR +editor.context.selection = [rectangle]; // Array syntax + +console.log("Rectangle is now selected"); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, RectangleNode, ContainerNode } from "express-document-sdk"; + +// Create a rectangle and select it +const rectangle: RectangleNode = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 100; +rectangle.translation = { x: 50, y: 50 }; + +// Add to document +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(rectangle); + +// Select the rectangle +editor.context.selection = rectangle; // Single node shortcut +// OR +editor.context.selection = [rectangle]; // Array syntax + +console.log("Rectangle is now selected"); +``` + +### Select Multiple Elements + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Create multiple elements +const rectangle = editor.createRectangle(); +rectangle.width = 80; +rectangle.height = 80; +rectangle.translation = { x: 50, y: 50 }; + +const ellipse = editor.createEllipse(); +ellipse.rx = 40; +ellipse.ry = 40; +ellipse.translation = { x: 200, y: 50 }; + +const text = editor.createText("Hello!"); +text.translation = { x: 50, y: 200 }; + +// Add all to document +const parent = editor.context.insertionParent; +parent.children.append(rectangle, ellipse, text); + +// Select multiple elements +editor.context.selection = [rectangle, ellipse, text]; + +console.log("Multiple elements selected:", editor.context.selection.length); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, RectangleNode, EllipseNode, StandaloneTextNode, ContainerNode } from "express-document-sdk"; + +// Create multiple elements +const rectangle: RectangleNode = editor.createRectangle(); +rectangle.width = 80; +rectangle.height = 80; +rectangle.translation = { x: 50, y: 50 }; + +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 40; +ellipse.ry = 40; +ellipse.translation = { x: 200, y: 50 }; + +const text: StandaloneTextNode = editor.createText("Hello!"); +text.translation = { x: 50, y: 200 }; + +// Add all to document +const parent: ContainerNode = editor.context.insertionParent; +parent.children.append(rectangle, ellipse, text); + +// Select multiple elements +editor.context.selection = [rectangle, ellipse, text]; + +console.log("Multiple elements selected:", editor.context.selection.length); +``` + +### Clear Selection + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Clear the selection (both methods work) +editor.context.selection = []; +// OR +editor.context.selection = undefined; + +console.log("Selection cleared"); +console.log("Has selection:", editor.context.hasSelection); // false +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Clear the selection (both methods work) +editor.context.selection = []; +// OR +editor.context.selection = undefined; + +console.log("Selection cleared"); +console.log("Has selection:", editor.context.hasSelection); // false +``` + +## Listening for Selection Changes + +### Basic Selection Change Handler + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Register selection change handler +const handlerId = editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + + console.log("Selection changed!"); + console.log("New selection count:", selection.length); + + if (selection.length > 0) { + console.log("Selected node types:", selection.map(node => node.type)); + } else { + console.log("Selection cleared"); + } +}); + +console.log("Selection change handler registered with ID:", handlerId); + +// Important: Store the handlerId if you need to unregister later +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, Node } from "express-document-sdk"; + +// Register selection change handler +const handlerId: string = editor.context.on("selectionChange", () => { + const selection: readonly Node[] = editor.context.selection; + + console.log("Selection changed!"); + console.log("New selection count:", selection.length); + + if (selection.length > 0) { + console.log("Selected node types:", selection.map((node: Node) => node.type)); + } else { + console.log("Selection cleared"); + } +}); + +console.log("Selection change handler registered with ID:", handlerId); + +// Important: Store the handlerId if you need to unregister later +``` + +### Advanced Selection Analysis + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +function analyzeSelection() { + const selection = editor.context.selection; + const fullSelection = editor.context.selectionIncludingNonEditable; + + return { + editableCount: selection.length, + totalCount: fullSelection.length, + lockedCount: fullSelection.length - selection.length, + types: selection.map(node => node.type), + hasText: selection.some(node => node.type === "Text"), + hasShapes: selection.some(node => + node.type === "Rectangle" || node.type === "Ellipse" + ), + isEmpty: !editor.context.hasSelection + }; +} + +// Register detailed selection handler +const handlerId = editor.context.on("selectionChange", () => { + const analysis = analyzeSelection(); + + console.log("=== Selection Analysis ==="); + console.log("Editable nodes:", analysis.editableCount); + console.log("Total nodes (including locked):", analysis.totalCount); + console.log("Locked nodes:", analysis.lockedCount); + console.log("Node types:", analysis.types); + console.log("Has text:", analysis.hasText); + console.log("Has shapes:", analysis.hasShapes); + console.log("Is empty:", analysis.isEmpty); +}); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, Node } from "express-document-sdk"; + +interface SelectionAnalysis { + editableCount: number; + totalCount: number; + lockedCount: number; + types: string[]; + hasText: boolean; + hasShapes: boolean; + isEmpty: boolean; +} + +function analyzeSelection(): SelectionAnalysis { + const selection: readonly Node[] = editor.context.selection; + const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; + + return { + editableCount: selection.length, + totalCount: fullSelection.length, + lockedCount: fullSelection.length - selection.length, + types: selection.map((node: Node) => node.type), + hasText: selection.some((node: Node) => node.type === "Text"), + hasShapes: selection.some((node: Node) => + node.type === "Rectangle" || node.type === "Ellipse" + ), + isEmpty: !editor.context.hasSelection + }; +} + +// Register detailed selection handler +const handlerId: string = editor.context.on("selectionChange", () => { + const analysis: SelectionAnalysis = analyzeSelection(); + + console.log("=== Selection Analysis ==="); + console.log("Editable nodes:", analysis.editableCount); + console.log("Total nodes (including locked):", analysis.totalCount); + console.log("Locked nodes:", analysis.lockedCount); + console.log("Node types:", analysis.types); + console.log("Has text:", analysis.hasText); + console.log("Has shapes:", analysis.hasShapes); + console.log("Is empty:", analysis.isEmpty); +}); +``` + +## Practical Selection Patterns + +### Selection-Based Actions + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor, colorUtils } from "express-document-sdk"; + +// Function to apply red color to selected text +function applyRedToSelectedText() { + const selection = editor.context.selection; + + // Filter for text nodes only + const textNodes = selection.filter(node => node.type === "Text"); + + if (textNodes.length === 0) { + console.log("No text nodes selected"); + return; + } + + // Apply red color to all selected text + const redColor = colorUtils.fromHex("#FF0000"); + + textNodes.forEach(textNode => { + textNode.fullContent.applyCharacterStyles({ color: redColor }); + }); + + console.log(`Applied red color to ${textNodes.length} text nodes`); +} + +// Function to group selected elements +function groupSelection() { + const selection = editor.context.selection; + + if (selection.length < 2) { + console.log("Need at least 2 elements to create a group"); + return; + } + + // Create a group + const group = editor.createGroup(); + + // Add selected elements to the group + selection.forEach(node => { + // Remove from current parent and add to group + node.removeFromParent(); + group.children.append(node); + }); + + // Add group to the document + editor.context.insertionParent.children.append(group); + + // Select the new group + editor.context.selection = group; + + console.log(`Created group with ${selection.length} elements`); +} + +// Register handlers for different actions +editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + + // Update UI or enable/disable actions based on selection + if (selection.length === 0) { + console.log("No selection - disable all actions"); + } else if (selection.length === 1) { + console.log("Single selection - enable individual actions"); + } else { + console.log("Multiple selection - enable group actions"); + } +}); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, Node, TextNode, GroupNode, ContainerNode } from "express-document-sdk"; + +// Function to apply red color to selected text +function applyRedToSelectedText(): void { + const selection: readonly Node[] = editor.context.selection; + + // Filter for text nodes only + const textNodes = selection.filter((node: Node): node is TextNode => + node.type === "Text" + ); + + if (textNodes.length === 0) { + console.log("No text nodes selected"); + return; + } + + // Apply red color to all selected text + const redColor = colorUtils.fromHex("#FF0000"); + + textNodes.forEach((textNode: TextNode) => { + textNode.fullContent.applyCharacterStyles({ color: redColor }); + }); + + console.log(`Applied red color to ${textNodes.length} text nodes`); +} + +// Function to group selected elements +function groupSelection(): void { + const selection: readonly Node[] = editor.context.selection; + + if (selection.length < 2) { + console.log("Need at least 2 elements to create a group"); + return; + } + + // Create a group + const group: GroupNode = editor.createGroup(); + + // Add selected elements to the group + selection.forEach((node: Node) => { + // Remove from current parent and add to group + node.removeFromParent(); + group.children.append(node); + }); + + // Add group to the document + const insertionParent: ContainerNode = editor.context.insertionParent; + insertionParent.children.append(group); + + // Select the new group + editor.context.selection = group; + + console.log(`Created group with ${selection.length} elements`); +} + +// Register handlers for different actions +editor.context.on("selectionChange", () => { + const selection: readonly Node[] = editor.context.selection; + + // Update UI or enable/disable actions based on selection + if (selection.length === 0) { + console.log("No selection - disable all actions"); + } else if (selection.length === 1) { + console.log("Single selection - enable individual actions"); + } else { + console.log("Multiple selection - enable group actions"); + } +}); +``` + +### Selection State Management + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +class SelectionManager { + constructor() { + this.selectionHistory = []; + this.handlerId = null; + this.startListening(); + } + + startListening() { + this.handlerId = editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + + // Store selection in history (limit to last 10) + this.selectionHistory.push([...selection]); + if (this.selectionHistory.length > 10) { + this.selectionHistory.shift(); + } + + console.log("Selection history length:", this.selectionHistory.length); + this.notifySelectionChange(selection); + }); + } + + notifySelectionChange(selection) { + // Custom logic based on selection + if (selection.length === 0) { + this.onNoSelection(); + } else if (selection.length === 1) { + this.onSingleSelection(selection[0]); + } else { + this.onMultipleSelection(selection); + } + } + + onNoSelection() { + console.log("No elements selected"); + // Disable context-sensitive UI + } + + onSingleSelection(node) { + console.log("Single element selected:", node.type); + // Enable single-element actions + } + + onMultipleSelection(selection) { + console.log("Multiple elements selected:", selection.length); + // Enable multi-element actions + } + + restorePreviousSelection() { + if (this.selectionHistory.length >= 2) { + const previousSelection = this.selectionHistory[this.selectionHistory.length - 2]; + editor.context.selection = previousSelection; + } + } + + stopListening() { + if (this.handlerId) { + editor.context.off("selectionChange", this.handlerId); + this.handlerId = null; + } + } +} + +// Usage +const selectionManager = new SelectionManager(); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, Node } from "express-document-sdk"; + +class SelectionManager { + private selectionHistory: Node[][] = []; + private handlerId: string | null = null; + + constructor() { + this.startListening(); + } + + startListening(): void { + this.handlerId = editor.context.on("selectionChange", () => { + const selection: readonly Node[] = editor.context.selection; + + // Store selection in history (limit to last 10) + this.selectionHistory.push([...selection]); + if (this.selectionHistory.length > 10) { + this.selectionHistory.shift(); + } + + console.log("Selection history length:", this.selectionHistory.length); + this.notifySelectionChange(selection); + }); + } + + private notifySelectionChange(selection: readonly Node[]): void { + // Custom logic based on selection + if (selection.length === 0) { + this.onNoSelection(); + } else if (selection.length === 1) { + this.onSingleSelection(selection[0]); + } else { + this.onMultipleSelection(selection); + } + } + + private onNoSelection(): void { + console.log("No elements selected"); + // Disable context-sensitive UI + } + + private onSingleSelection(node: Node): void { + console.log("Single element selected:", node.type); + // Enable single-element actions + } + + private onMultipleSelection(selection: readonly Node[]): void { + console.log("Multiple elements selected:", selection.length); + // Enable multi-element actions + } + + restorePreviousSelection(): void { + if (this.selectionHistory.length >= 2) { + const previousSelection = this.selectionHistory[this.selectionHistory.length - 2]; + editor.context.selection = previousSelection; + } + } + + stopListening(): void { + if (this.handlerId) { + editor.context.off("selectionChange", this.handlerId); + this.handlerId = null; + } + } +} + +// Usage +const selectionManager = new SelectionManager(); +``` + +## Cleanup and Best Practices + +### Unregistering Event Handlers + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Store handler IDs for cleanup +let selectionHandlerId = null; + +function setupSelectionHandling() { + // Register handler and store ID + selectionHandlerId = editor.context.on("selectionChange", () => { + console.log("Selection changed"); + // Handle selection change + }); + + console.log("Selection handler registered"); +} + +function cleanupSelectionHandling() { + // Unregister the handler + if (selectionHandlerId) { + editor.context.off("selectionChange", selectionHandlerId); + selectionHandlerId = null; + console.log("Selection handler unregistered"); + } +} + +// Setup +setupSelectionHandling(); + +// Cleanup when add-on is being destroyed or reset +// cleanupSelectionHandling(); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Store handler IDs for cleanup +let selectionHandlerId: string | null = null; + +function setupSelectionHandling(): void { + // Register handler and store ID + selectionHandlerId = editor.context.on("selectionChange", () => { + console.log("Selection changed"); + // Handle selection change + }); + + console.log("Selection handler registered"); +} + +function cleanupSelectionHandling(): void { + // Unregister the handler + if (selectionHandlerId) { + editor.context.off("selectionChange", selectionHandlerId); + selectionHandlerId = null; + console.log("Selection handler unregistered"); + } +} + +// Setup +setupSelectionHandling(); + +// Cleanup when add-on is being destroyed or reset +// cleanupSelectionHandling(); +``` + +## Key Concepts and Rules + +### Selection Constraints + +1. **Artboard Limitation**: Only nodes within the current artboard can be selected +2. **Hierarchy Rules**: Cannot select both a parent node and its children simultaneously +3. **Locked Node Handling**: Locked nodes are excluded from the main selection but available in `selectionIncludingNonEditable` +4. **Automatic Filtering**: The system automatically filters out invalid selections + +### Event Handler Guidelines + + + +Important: Document Modification Restrictions + +**Do not attempt to make changes to the document in response to a selection change callback** because it may destabilize the application. Selection change handlers should be used for: + +✅ **Safe operations:** Updating UI, logging, analyzing selection, enabling/disabling buttons +❌ **Avoid:** Creating/deleting nodes, modifying properties, changing the document structure + +### Performance Considerations + +1. **Minimize Handler Logic**: Keep selection change handlers lightweight +2. **Debounce Rapid Changes**: Consider debouncing if handling rapid selection changes +3. **Clean Up Handlers**: Always unregister event handlers when no longer needed +4. **Avoid Deep Analysis**: Don't perform expensive operations in selection callbacks + +## Communication Between UI and Document Sandbox + +One of the most important real-world patterns is communicating selection changes from the document sandbox to your UI panel, allowing you to update the interface based on what the user has selected. + +### Complete Communication Example + +This example shows how to set up bidirectional communication between your UI panel and document sandbox for selection-based interactions. + + + +#### JavaScript + +**UI Panel (index.js):** + +```js +// ui/index.js +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +let documentSandbox; + +addOnUISdk.ready.then(async () => { + // Get access to the document sandbox APIs + documentSandbox = await addOnUISdk.instance.runtime.apiProxy("documentSandbox"); + + // Set up UI elements + setupSelectionUI(); + + // Start listening for selection updates from sandbox + documentSandbox.registerSelectionUpdateHandler(handleSelectionUpdate); +}); + +function setupSelectionUI() { + const container = document.getElementById("selection-info"); + + container.innerHTML = ` +
Nothing selected
+
+ + + +
+
+ `; + + // Set up button handlers + document.getElementById("apply-red-btn").addEventListener("click", () => { + documentSandbox.applyRedToSelection(); + }); + + document.getElementById("group-btn").addEventListener("click", () => { + documentSandbox.groupSelection(); + }); + + document.getElementById("clear-selection-btn").addEventListener("click", () => { + documentSandbox.clearSelection(); + }); +} + +function handleSelectionUpdate(selectionInfo) { + console.log("Selection update received:", selectionInfo); + + // Update status + const statusEl = document.getElementById("selection-status"); + if (selectionInfo.count === 0) { + statusEl.textContent = "Nothing selected"; + } else if (selectionInfo.count === 1) { + statusEl.textContent = `1 ${selectionInfo.types[0]} selected`; + } else { + statusEl.textContent = `${selectionInfo.count} elements selected`; + } + + // Update action buttons + document.getElementById("apply-red-btn").disabled = !selectionInfo.hasText; + document.getElementById("group-btn").disabled = selectionInfo.count < 2; + document.getElementById("clear-selection-btn").disabled = selectionInfo.count === 0; + + // Update details + const detailsEl = document.getElementById("selection-details"); + if (selectionInfo.count > 0) { + detailsEl.innerHTML = ` +

Selection Details:

+

Types: ${selectionInfo.types.join(", ")}

+

Has Text: ${selectionInfo.hasText ? "Yes" : "No"}

+

Has Shapes: ${selectionInfo.hasShapes ? "Yes" : "No"}

+

Locked Elements: ${selectionInfo.lockedCount}

+ `; + } else { + detailsEl.innerHTML = ""; + } +} +``` + +**Document Sandbox (code.js):** + +```js +// sandbox/code.js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor, colorUtils } from "express-document-sdk"; + +const { runtime } = addOnSandboxSdk.instance; +let uiPanel; + +// Wait for UI panel to be ready +runtime.ready.then(async () => { + // Get access to the UI panel APIs + uiPanel = await runtime.apiProxy("panel"); + + // Set up selection change handler + setupSelectionHandling(); +}); + +function setupSelectionHandling() { + editor.context.on("selectionChange", () => { + const selectionInfo = analyzeCurrentSelection(); + + // Send selection info to UI panel + uiPanel.handleSelectionUpdate(selectionInfo); + }); + + // Send initial selection state + const initialSelection = analyzeCurrentSelection(); + uiPanel.handleSelectionUpdate(initialSelection); +} + +function analyzeCurrentSelection() { + const selection = editor.context.selection; + const fullSelection = editor.context.selectionIncludingNonEditable; + + return { + count: selection.length, + totalCount: fullSelection.length, + lockedCount: fullSelection.length - selection.length, + types: [...new Set(selection.map(node => node.type))], + hasText: selection.some(node => node.type === "Text"), + hasShapes: selection.some(node => + ["Rectangle", "Ellipse"].includes(node.type) + ), + isEmpty: selection.length === 0 + }; +} + +// Export functions for UI to call +function registerSelectionUpdateHandler(handler) { + // Store the handler function from UI + runtime.exposeApi({ + applyRedToSelection() { + const selection = editor.context.selection; + const textNodes = selection.filter(node => node.type === "Text"); + + if (textNodes.length > 0) { + const redColor = colorUtils.fromHex("#FF0000"); + textNodes.forEach(textNode => { + textNode.fullContent.applyCharacterStyles({ color: redColor }); + }); + + console.log(`Applied red to ${textNodes.length} text nodes`); + } + }, + + groupSelection() { + const selection = editor.context.selection; + + if (selection.length >= 2) { + const group = editor.createGroup(); + + // Move selected elements to group + selection.forEach(node => { + node.removeFromParent(); + group.children.append(node); + }); + + // Add group to document + editor.context.insertionParent.children.append(group); + + // Select the new group + editor.context.selection = group; + + console.log(`Created group with ${selection.length} elements`); + } + }, + + clearSelection() { + editor.context.selection = []; + console.log("Selection cleared"); + }, + + registerSelectionUpdateHandler: handler + }); +} + +// Expose the registration function immediately +runtime.exposeApi({ registerSelectionUpdateHandler }); +``` + +#### TypeScript + +**UI Panel (index.ts):** + +```ts +// ui/index.ts +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +interface SelectionInfo { + count: number; + totalCount: number; + lockedCount: number; + types: string[]; + hasText: boolean; + hasShapes: boolean; + isEmpty: boolean; +} + +interface DocumentSandboxAPI { + registerSelectionUpdateHandler: (handler: (info: SelectionInfo) => void) => void; + applyRedToSelection: () => void; + groupSelection: () => void; + clearSelection: () => void; +} + +let documentSandbox: DocumentSandboxAPI; + +addOnUISdk.ready.then(async () => { + // Get access to the document sandbox APIs + documentSandbox = await addOnUISdk.instance.runtime.apiProxy("documentSandbox"); + + // Set up UI elements + setupSelectionUI(); + + // Start listening for selection updates from sandbox + documentSandbox.registerSelectionUpdateHandler(handleSelectionUpdate); +}); + +function setupSelectionUI(): void { + const container = document.getElementById("selection-info"); + + if (container) { + container.innerHTML = ` +
Nothing selected
+
+ + + +
+
+ `; + + // Set up button handlers + const applyRedBtn = document.getElementById("apply-red-btn"); + const groupBtn = document.getElementById("group-btn"); + const clearBtn = document.getElementById("clear-selection-btn"); + + applyRedBtn?.addEventListener("click", () => { + documentSandbox.applyRedToSelection(); + }); + + groupBtn?.addEventListener("click", () => { + documentSandbox.groupSelection(); + }); + + clearBtn?.addEventListener("click", () => { + documentSandbox.clearSelection(); + }); + } +} + +function handleSelectionUpdate(selectionInfo: SelectionInfo): void { + console.log("Selection update received:", selectionInfo); + + // Update status + const statusEl = document.getElementById("selection-status"); + if (statusEl) { + if (selectionInfo.count === 0) { + statusEl.textContent = "Nothing selected"; + } else if (selectionInfo.count === 1) { + statusEl.textContent = `1 ${selectionInfo.types[0]} selected`; + } else { + statusEl.textContent = `${selectionInfo.count} elements selected`; + } + } + + // Update action buttons + const applyRedBtn = document.getElementById("apply-red-btn") as HTMLButtonElement; + const groupBtn = document.getElementById("group-btn") as HTMLButtonElement; + const clearBtn = document.getElementById("clear-selection-btn") as HTMLButtonElement; + + if (applyRedBtn) applyRedBtn.disabled = !selectionInfo.hasText; + if (groupBtn) groupBtn.disabled = selectionInfo.count < 2; + if (clearBtn) clearBtn.disabled = selectionInfo.count === 0; + + // Update details + const detailsEl = document.getElementById("selection-details"); + if (detailsEl) { + if (selectionInfo.count > 0) { + detailsEl.innerHTML = ` +

Selection Details:

+

Types: ${selectionInfo.types.join(", ")}

+

Has Text: ${selectionInfo.hasText ? "Yes" : "No"}

+

Has Shapes: ${selectionInfo.hasShapes ? "Yes" : "No"}

+

Locked Elements: ${selectionInfo.lockedCount}

+ `; + } else { + detailsEl.innerHTML = ""; + } + } +} +``` + +**Document Sandbox (code.ts):** + +```ts +// sandbox/code.ts +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor, colorUtils, Node, TextNode, GroupNode, ContainerNode } from "express-document-sdk"; + +interface SelectionInfo { + count: number; + totalCount: number; + lockedCount: number; + types: string[]; + hasText: boolean; + hasShapes: boolean; + isEmpty: boolean; +} + +interface UIPanelAPI { + handleSelectionUpdate: (info: SelectionInfo) => void; +} + +const { runtime } = addOnSandboxSdk.instance; +let uiPanel: UIPanelAPI; + +// Wait for UI panel to be ready +runtime.ready.then(async () => { + // Get access to the UI panel APIs + uiPanel = await runtime.apiProxy("panel"); + + // Set up selection change handler + setupSelectionHandling(); +}); + +function setupSelectionHandling(): void { + editor.context.on("selectionChange", () => { + const selectionInfo: SelectionInfo = analyzeCurrentSelection(); + + // Send selection info to UI panel + uiPanel.handleSelectionUpdate(selectionInfo); + }); + + // Send initial selection state + const initialSelection: SelectionInfo = analyzeCurrentSelection(); + uiPanel.handleSelectionUpdate(initialSelection); +} + +function analyzeCurrentSelection(): SelectionInfo { + const selection: readonly Node[] = editor.context.selection; + const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; + + return { + count: selection.length, + totalCount: fullSelection.length, + lockedCount: fullSelection.length - selection.length, + types: [...new Set(selection.map((node: Node) => node.type))], + hasText: selection.some((node: Node) => node.type === "Text"), + hasShapes: selection.some((node: Node) => + ["Rectangle", "Ellipse"].includes(node.type) + ), + isEmpty: selection.length === 0 + }; +} + +// Export functions for UI to call +function registerSelectionUpdateHandler(handler: (info: SelectionInfo) => void): void { + // Store the handler function from UI + runtime.exposeApi({ + applyRedToSelection(): void { + const selection: readonly Node[] = editor.context.selection; + const textNodes = selection.filter((node: Node): node is TextNode => + node.type === "Text" + ); + + if (textNodes.length > 0) { + const redColor = colorUtils.fromHex("#FF0000"); + textNodes.forEach((textNode: TextNode) => { + textNode.fullContent.applyCharacterStyles({ color: redColor }); + }); + + console.log(`Applied red to ${textNodes.length} text nodes`); + } + }, + + groupSelection(): void { + const selection: readonly Node[] = editor.context.selection; + + if (selection.length >= 2) { + const group: GroupNode = editor.createGroup(); + + // Move selected elements to group + selection.forEach((node: Node) => { + node.removeFromParent(); + group.children.append(node); + }); + + // Add group to document + const insertionParent: ContainerNode = editor.context.insertionParent; + insertionParent.children.append(group); + + // Select the new group + editor.context.selection = group; + + console.log(`Created group with ${selection.length} elements`); + } + }, + + clearSelection(): void { + editor.context.selection = []; + console.log("Selection cleared"); + }, + + registerSelectionUpdateHandler: handler + }); +} + +// Expose the registration function immediately +runtime.exposeApi({ registerSelectionUpdateHandler }); +``` + +### HTML Structure + +Your `index.html` should include the selection UI container: + +```html + + + + Selection Demo + + + +
+ +
+ + + + +``` + +### Key Communication Patterns + +1. **Initialization**: UI panel registers a callback with the document sandbox +2. **Event Flow**: Document sandbox listens for selection changes and sends updates to UI +3. **Action Triggers**: UI sends action requests back to document sandbox +4. **Bidirectional**: Both sides can call methods on the other + +This pattern enables rich, responsive UIs that react to document changes in real-time. + +## Common Patterns + +### Conditional Actions Based on Selection + +```js +// Enable/disable actions based on selection type +editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + + // Communicate with your UI panel + const actions = { + canGroup: selection.length >= 2, + canApplyTextStyle: selection.some(node => node.type === "Text"), + canApplyFill: selection.some(node => + ["Rectangle", "Ellipse"].includes(node.type) + ), + isEmpty: selection.length === 0 + }; + + // Send to UI panel for enabling/disabling buttons + // (Use the communication API to send this data) +}); +``` + +### Selection-Based Properties Panel + +```js +// Update properties panel based on selection +editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + + if (selection.length === 1) { + const node = selection[0]; + + // Send node properties to UI for editing + const properties = { + type: node.type, + width: node.width || null, + height: node.height || null, + x: node.translation?.x || null, + y: node.translation?.y || null, + locked: node.locked || false + }; + + // Update UI panel with these properties + console.log("Node properties:", properties); + } +}); +``` + +## FAQs + +#### Q: How do I get the current selection? + +**A:** Use `editor.context.selection` to get an array of currently selected nodes. + +#### Q: How do I listen for selection changes? + +**A:** Use `editor.context.on('selectionChange', callback)` to register a selection change handler. + +#### Q: How do I programmatically select elements? + +**A:** Set `editor.context.selection = [node]` or `editor.context.selection = [node1, node2]` for multiple elements. + +#### Q: What's the difference between selection and selectionIncludingNonEditable? + +**A:** `selection` only includes editable nodes, while `selectionIncludingNonEditable` also includes locked/non-editable nodes. + +#### Q: Can I modify the document in a selection change callback? + +**A:** No, avoid making document changes in selection change callbacks as it may destabilize the application. + +#### Q: How do I clear the selection? + +**A:** Set `editor.context.selection = []` or `editor.context.selection = undefined`. + +#### Q: What are the selection rules? + +**A:** Nodes must be within the current artboard, ancestors cannot be selected with descendants, and locked nodes are filtered out. + +#### Q: How do I unregister selection event handlers? + +**A:** Use `editor.context.off('selectionChange', handlerId)` with the ID returned from the `on()` method. diff --git a/src/pages/guides/learn/how_to/use_color.md b/src/pages/guides/learn/how_to/use_color.md index 81f75f576..2afe59998 100644 --- a/src/pages/guides/learn/how_to/use_color.md +++ b/src/pages/guides/learn/how_to/use_color.md @@ -57,6 +57,10 @@ Colors in Adobe Express are created as instances of the [`Color`](../../../refer The entrypoint for creating colors is the [`colorUtils`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md) class, imported from the `"express-document-sdk"`, so we're talking about [Document APIs](../../../references/document-sandbox/document-apis/index.md) here. Especially the static [`fromRGB()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromrgb) and [`fromHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromhex) methods. + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; @@ -70,19 +74,49 @@ const feldgrau = colorUtils.fromRGB(0.28, 0.32, 0.39, 0.5); // 50% opacity const heliotrope = colorUtils.fromHex("#C768F780"); // 50% opacity ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, Color } from "express-document-sdk"; + +// Alpha is optional, defaults to 1 +const red: Color = colorUtils.fromRGB(1, 0, 0); +const green: Color = colorUtils.fromHex("#00FF00"); + +// With alpha +const feldgrau: Color = colorUtils.fromRGB(0.28, 0.32, 0.39, 0.5); // 50% opacity +const heliotrope: Color = colorUtils.fromHex("#C768F780"); // 50% opacity +``` + In case you need it, you can also convert a color to a HEX string using the [`toHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#tohex) method. Please note that the alpha value is always included in the output string. + + +#### JavaScript + ```js const red = colorUtils.fromRGB(1, 0, 0); const redHex = colorUtils.toHex(red); // #FF0000FF ``` +#### TypeScript + +```ts +const red: Color = colorUtils.fromRGB(1, 0, 0); +const redHex: string = colorUtils.toHex(red); // #FF0000FF +``` + ## Apply colors You can directly set the `color` property of a Text node via [`applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles): ### Example: Text color + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; @@ -97,6 +131,22 @@ textNode.fullContent.applyCharacterStyles( ); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, TextNode } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const textNode = editor.context.selection[0] as TextNode; + +// Apply character styles to the first three letters +textNode.fullContent.applyCharacterStyles( + { color: colorUtils.fromHex("#E1A141") }, // 👈 + { start: 0, length: 3 } +); +``` + See the [Use Text](./use_text.md) page for more examples. ### Example: Fill and Stroke colors @@ -105,6 +155,10 @@ Colors are not directly applied, instead, to shapes; more generally, they are us If you're confused, worry not! This is the wondrous word of object oriented programming. The following example should clarify things: + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; @@ -134,10 +188,45 @@ ellipse.stroke = outerColorStroke; editor.context.insertionParent.children.append(ellipse); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, EllipseNode, Color, ColorFill, Stroke, ContainerNode } from "express-document-sdk"; + +// Create the shape +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.width = 100; +ellipse.height = 50; +ellipse.translation = { x: 50, y: 50 }; + +// Generate the needed colors +const innerColor: Color = colorUtils.fromHex("#A38AF0"); +const outerColor: Color = colorUtils.fromHex("#2ACfA9"); + +// Make the colorFill and the Stroke +const innerColorFill: ColorFill = editor.makeColorFill(innerColor); +const outerColorStroke: Stroke = editor.makeStroke({ + color: outerColor, + width: 20, +}); + +// 👇 Apply the fill and stroke +ellipse.fill = innerColorFill; +ellipse.stroke = outerColorStroke; + +// Add the shape to the document +editor.context.insertionParent.children.append(ellipse); +``` + While the `fill` property is more straightforward to create, the `color` is just one of the possible properties of a `stroke`, as you can read in the [SolidColorStroke](../../../references/document-sandbox/document-apis/interfaces/SolidColorStroke.md) interface reference. Simplifying the example above: + + +#### JavaScript + ```js // ... ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#A38AF0")); @@ -148,6 +237,18 @@ ellipse.stroke = editor.makeStroke({ // ... ``` +#### TypeScript + +```ts +// ... +ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#A38AF0")); +ellipse.stroke = editor.makeStroke({ + color: colorUtils.fromHex("#2ACfA9"), + width: 20, +}); +// ... +``` + Naming conventions @@ -238,6 +339,10 @@ Please note that the color returned by the `colorpicker-color-change` event is a You can decide to hide picker UI e.g., after a certain amount of time. + + +#### JavaScript + ```js colorPickerButton.addEventListener("click", () => { addOnUISdk.app.showColorPicker(colorPickerButton, { @@ -250,6 +355,20 @@ colorPickerButton.addEventListener("click", () => { }); ``` +#### TypeScript + +```ts +colorPickerButton.addEventListener("click", (): void => { + addOnUISdk.app.showColorPicker(colorPickerButton, { + /* ... */ + }); + setTimeout((): void => { + console.log("Hiding the Color Picker after 10 seconds"); + addOnUISdk.app.hideColorPicker(); + }, 10000); +}); +``` + ### Example: Use the color You can use any HTML element as the color picker's anchor element; in the example below, we're using a `
` element to display a color swatch. @@ -296,6 +415,10 @@ addOnUISdk.ready.then(async () => { To use the picked color in the Document Sandbox, you can use the [`colorUtils.fromHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromhex) method, which converts the HEX color string to a [`Color`](../../../references/document-sandbox/document-apis/interfaces/Color.md) object. + + +#### JavaScript + ```js // sandbox/code.js const color = colorUtils.fromHex(event.detail.color); // 👈 A Color object @@ -308,6 +431,20 @@ if (selection.length === 1 && selection[0].type === "Text") { } ``` +#### TypeScript + +```ts +// sandbox/code.js +const color: Color = colorUtils.fromHex(event.detail.color); // 👈 A Color object + +// Use the color in the Document Sandbox, for example: +let selection = editor.context.selection; +if (selection.length === 1 && selection[0].type === "Text") { + const textContentModel = (selection[0] as TextNode).fullContent; + textContentModel.applyCharacterStyles({ color }); // 👈 Using the color +} +``` + ## FAQs #### Q: How do I create colors from RGB values? diff --git a/src/pages/guides/learn/how_to/use_geometry.md b/src/pages/guides/learn/how_to/use_geometry.md index e5c5a5d8b..7a25a7a61 100644 --- a/src/pages/guides/learn/how_to/use_geometry.md +++ b/src/pages/guides/learn/how_to/use_geometry.md @@ -48,6 +48,10 @@ Adobe Express provides a set of geometric shapes that you can create and style p ### Example: Add a Rectangle + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -65,6 +69,25 @@ const currentPage = editor.context.currentPage; currentPage.artboards.first.children.append(rect); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, RectangleNode, PageNode } from "express-document-sdk"; + +const rect: RectangleNode = editor.createRectangle(); + +// Define rectangle dimensions. +rect.width = 100; +rect.height = 100; + +// The current page, where the rectangle will be placed +const currentPage: PageNode = editor.context.currentPage; + +// Append the rectangle to the page. +currentPage.artboards.first.children.append(rect); +``` + Create vs. Add to the page @@ -75,6 +98,10 @@ You usually reference the container using [`editor.context`](../../../references Please note that you can append multiple shapes at once with the `append()` method: + + +#### JavaScript + ```js const s1 = editor.createRectangle(); const s2 = editor.createEllipse(); @@ -83,6 +110,16 @@ const s2 = editor.createEllipse(); editor.context.currentPage.artboards.first.children.append(s1, s2); // 👈 ``` +#### TypeScript + +```ts +const s1: RectangleNode = editor.createRectangle(); +const s2: EllipseNode = editor.createEllipse(); +// ... set all properties ... + +editor.context.currentPage.artboards.first.children.append(s1, s2); // 👈 +``` + ### Example: Add an Ellipse Ellipses don't have a `width` and `height` properties, but a [`rx`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md#rx) and [`ry`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md#ry) (radius x, radius y) instead. @@ -91,6 +128,10 @@ Ellipses don't have a `width` and `height` properties, but a [`rx`](../../../ref An ellipse with a radius of 200 on the x-axis and 100 on the y-axis will result in a shape with 400 wide (`rx` times two) and a 200 tall (`ry` times two)! + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -106,13 +147,37 @@ console.log(ellipse.boundsLocal); const currentPage = editor.context.currentPage; // Append the rectangle to the page. -currentPage.artboards.first.children.append(rect); +currentPage.artboards.first.children.append(ellipse); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, EllipseNode, PageNode } from "express-document-sdk"; + +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 200; // radius x 👈 +ellipse.ry = 100; // radius y 👈 + +console.log(ellipse.boundsLocal); +// { x: 0, y: 0, width: 400, height: 200 } 👈 mind the actual bounds! + +// The current page, where the rectangle will be placed +const currentPage: PageNode = editor.context.currentPage; + +// Append the ellipse to the page. +currentPage.artboards.first.children.append(ellipse); ``` ### Example: Style Shapes Shapes have `fill` and `stroke` properties that you can use to style them. The following example demonstrates how to create a rectangle with a fill and a stroke. + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils, constants } from "express-document-sdk"; @@ -140,6 +205,36 @@ ellipse.stroke = stroke; editor.context.insertionParent.children.append(ellipse); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, constants, EllipseNode, Stroke, ContainerNode } from "express-document-sdk"; + +// Create the shape +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 200; +ellipse.ry = 100; + +// 👇 Apply the fill color +ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#F3D988")); + +// 👇 Create the stroke +const stroke: Stroke = editor.makeStroke({ + color: colorUtils.fromHex("#E29E4E"), + width: 20, + position: constants.StrokePosition.inside, + dashPattern: [50, 2], +}); + +// 👇 Apply the stroke +ellipse.stroke = stroke; + +// Add the shape to the document +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(ellipse); +``` + ![Ellipse with fill and stroke](./images/shapes_ellipse.jpg) diff --git a/src/pages/guides/learn/how_to/use_text.md b/src/pages/guides/learn/how_to/use_text.md index 83985ea26..5a14fa3e0 100644 --- a/src/pages/guides/learn/how_to/use_text.md +++ b/src/pages/guides/learn/how_to/use_text.md @@ -78,6 +78,10 @@ The `editor.createText()` method accepts a string as a parameter, and returns a ### Example + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -99,6 +103,29 @@ insertionParent.children.append(textNode); console.log("Text: ", textNode.fullContent.text); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, StandaloneTextNode, ContainerNode } from "express-document-sdk"; + +// Create a new TextNode +const textNode: StandaloneTextNode = editor.createText("Hello,\nWorld!"); + +// Center the text on the page +const insertionParent: ContainerNode = editor.context.insertionParent; +textNode.setPositionInParent( + { x: insertionParent.width / 2, y: insertionParent.height / 2 }, + { x: 0, y: 0 } +); + +// Add the TextNode to the document +insertionParent.children.append(textNode); + +// Get the text content +console.log("Text: ", textNode.fullContent.text); +``` + The text is created with the default styles (Source Sans 3, 100pt, black). Use `\n` or `\r` to add a line break. @@ -118,6 +145,10 @@ The text content of a `TextNode` can be replaced by setting the [`fullContent.te ### Example + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -127,6 +158,17 @@ const selectedTextNode = editor.context.selection[0]; selectedTextNode.fullContent.text = "Something else"; ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, TextNode } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const selectedTextNode = editor.context.selection[0] as TextNode; +selectedTextNode.fullContent.text = "Something else"; +``` + ## Apply Character Styles Text styles can be applied to a `TextNode` using the [`fullContent.applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles) method, which applies one or more styles to the characters in the given range, leaving any style properties that were not specified unchanged. @@ -154,6 +196,10 @@ Please note that `applyCharacterStyles()` is only one way to set styles; you can Let's change the styles for the first three characters of a TextNode. + + +#### JavaScript + ```js // sandbox/code.js import { editor, constants } from "express-document-sdk"; @@ -177,12 +223,43 @@ textNode.fullContent.applyCharacterStyles( ); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, constants, TextNode, CharacterStyles } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const textNode = editor.context.selection[0] as TextNode; + +// Apply character styles to the first three letters +const styles: CharacterStyles = { + color: { red: 0, green: 0.4, blue: 0.8, alpha: 1 }, + fontSize: 240, + letterSpacing: 10, + underline: true, + // baselineShift: constants.TextScriptStyle.superscript, +}; + +textNode.fullContent.applyCharacterStyles( + styles, + { + start: 0, + length: 3, + } +); +``` + The `applyCharacterStyles()` method is not the only one that allows you to set styles; you can also use the `characterStyleRanges` property, which supports both getting and setting styles. ### Example: Get all Styles To get the complete list of text character styles, you can use the [`fullContent.characterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) property, which returns an array of [`CharacterStylesRange`](../../../references/document-sandbox/document-apis/interfaces/CharacterStylesRange.md) elements. + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -201,6 +278,26 @@ existingStyles[0].fontSize = 10; contentModel.characterStyleRanges = existingStyles; ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, TextNode, TextContentModel, CharacterStylesRange } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const textNode = editor.context.selection[0] as TextNode; +const contentModel: TextContentModel = textNode.fullContent; + +// Get the array of character styles +const existingStyles: CharacterStylesRange[] = contentModel.characterStyleRanges; + +// Edit some properties +existingStyles[0].fontSize = 10; + +// Reassign the array to apply the style changes +contentModel.characterStyleRanges = existingStyles; +``` + ### Example: Set all Styles You can also use the [`characterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) property to set individual ranges or them all. It's always best to get the array, modify it, and then reassign it. diff --git a/src/pages/guides/learn/platform_concepts/document-api.md b/src/pages/guides/learn/platform_concepts/document-api.md index 49348dd3b..7104c26eb 100644 --- a/src/pages/guides/learn/platform_concepts/document-api.md +++ b/src/pages/guides/learn/platform_concepts/document-api.md @@ -206,6 +206,17 @@ A specific **naming convention** exists for methods used in the Adobe Express DO - `create*`: used for live document objects, like `createEllipse()` - `add*`: reserved to create complex structures like Pages and Artboards, adding them to the parent list automatically in a way that may also update the insertion point. Found in `addPage()` and `addArtboard()`. + + +Common Mistake: Use `addPage()`, not `createPage()` + +**There is no `createPage()` method** in the Adobe Express Document API. This is a frequent source of confusion for developers and LLMs. + +✅ **Correct:** `editor.addPage({ width: 1080, height: 1080 })` +❌ **Wrong:** `editor.createPage()` (doesn't exist) + +See the [Manage Pages how-to guide](../how_to/manage_pages.md) for comprehensive examples and best practices. + Speaking of colors, the `ColorUtils` module can output a proper `Color` from either a partial RGB object (where the `alpha` property is optional, defaulting to `1`) or a Hex string with or without alpha, providing the reverse function as well. ```js diff --git a/src/pages/guides/support/faq.md b/src/pages/guides/support/faq.md index 1c283a1fc..bb02f78e9 100644 --- a/src/pages/guides/support/faq.md +++ b/src/pages/guides/support/faq.md @@ -86,6 +86,7 @@ contributors: - [I'm not able to load the add-on in the browser anymore. When I click on "Connect", I get an error `ERR_CERT_AUTHORITY_INVALID`.](#im-not-able-to-load-the-add-on-in-the-browser-anymore-when-i-click-on-connect-i-get-an-error-err_cert_authority_invalid) - [Why do I receive a "No 'Access-Control-Allow-Origin' header is present on the requested resource" error?](#why-do-i-receive-a-no-access-control-allow-origin-header-is-present-on-the-requested-resource-error) - [What permissions are available for add-ons and how do I configure them?](#what-permissions-are-available-for-add-ons-and-how-do-i-configure-them) +- [How do I import SDK constants correctly?](#how-do-i-import-sdk-constants-correctly) - [Is `SharedArrayBuffer` supported?](#is-sharedarraybuffer-supported) - [Which browsers and operating systems are currently supported?](#which-browsers-and-operating-systems-are-currently-supported) @@ -100,7 +101,8 @@ contributors: - [I'm trying to use a newly released feature, but it seems to be unavailable?](#im-trying-to-use-a-newly-released-feature-but-it-seems-to-be-unavailable) - [What does it mean when an API is considered **experimental**?](#what-does-it-mean-when-an-api-is-considered-experimental) -- [How does Adobe use my add-on’s data?](#how-does-adobe-use-my-add-ons-data) +- [Why doesn't createPage() work? How do I add pages programmatically?](#why-doesnt-createpage-work-how-do-i-add-pages-programmatically) +- [How does Adobe use my add-on's data?](#how-does-adobe-use-my-add-ons-data) - [Where can I request new add-on features or suggest ideas?](#where-can-i-request-new-add-on-features-or-suggest-ideas) ### 💰 Distribution & Monetization @@ -212,6 +214,28 @@ At this time, the only way to monetize is by using a third party provider, and e Experimental APIs are those which have not been declared stable yet, and to try them, first need to set the `experimentalApis` flag to `true` in the [`requirements`](../../references/manifest/index.md#requirements) section of the [`manifest.json`](../../references/manifest/index.md). The `experimentalApis` flag is **only allowed during development** and needs to be removed during submission. Experimental APIs should never be used in any add-ons you will be distributing. +### Why doesn't createPage() work? How do I add pages programmatically? + +The Adobe Express Document API uses **`addPage()`** to create new pages, not `createPage()`. This is a common confusion point for developers and LLMs familiar with other APIs. + +**Correct usage:** +```js +// ✅ Correct - Use addPage() with dimensions +const newPage = editor.addPage({ width: 1080, height: 1080 }); +``` + +**Incorrect usage:** +```js +// ❌ Wrong - createPage() doesn't exist +const newPage = editor.createPage(); // This will throw an error +``` + +The Document API follows a specific naming convention: +- `create*` methods (like `createRectangle()`, `createText()`) are for basic document objects +- `add*` methods (like `addPage()`, `addArtboard()`) are for complex structures that need special handling + +For comprehensive examples and best practices, see our [Manage Pages how-to guide](../learn/how_to/manage_pages.md). + ### What are the supported mime types/file formats for exported content? The supported file types for exported content are: @@ -317,6 +341,25 @@ Adobe Express add-ons support several types of permissions that you configure in For complete details on all available permissions and their usage, see the [manifest permissions documentation](../../references/manifest/index.md#entrypointspermissions) and the [add-on context guide](../learn/platform_concepts/context.md#permissions). +### How do I import SDK constants correctly? + +Adobe Express SDK constants use different import patterns depending on the constant type. Some constants like `SupportedMimeTypes` require named imports, while others like `Range` support both named imports and the `addOnUISdk.constants.*` pattern. + +**Quick Examples:** +```javascript +// Named imports (required for some constants) +import addOnUISdk, { SupportedMimeTypes, Range } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Both patterns work for dual-access constants +const currentPage = Range.currentPage; +const currentPage = addOnUISdk.constants.Range.currentPage; + +// Named-only constants must be imported +const docxType = SupportedMimeTypes.docx; +``` + +See the [complete import patterns guide](../../references/addonsdk/addonsdk-constants.md#import-patterns) for detailed documentation, examples, and a comprehensive list of which constants require named imports vs. support dual access. + ### Is [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) supported? No, `SharedArrayBuffer` is not currently available to use with your add-ons. diff --git a/src/pages/references/addonsdk/addonsdk-app.md b/src/pages/references/addonsdk/addonsdk-app.md index 52c16a977..04292d45b 100644 --- a/src/pages/references/addonsdk/addonsdk-app.md +++ b/src/pages/references/addonsdk/addonsdk-app.md @@ -193,6 +193,10 @@ Returns a `Promise` [`DialogResult`](#dialogresult) object with the [button type #### Confirmation Dialog Example Usage + + +#### JavaScript + ```js import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; @@ -217,8 +221,39 @@ async function showConfirmDialog() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function showConfirmDialog(): Promise { + try { + // Confirmation Dialog Example + const dialogOptions: DialogOptions = { + variant: Variant.confirmation, + title: "Enable smart Filters", + description: + "Smart filters are nondestructive and will preserve your original images.", + buttonLabels: { primary: "Enable", cancel: "Cancel" }, + }; + + const result: DialogResult = await addOnUISdk.app.showModalDialog(dialogOptions); + console.log("Button type clicked " + result.buttonType); + } catch (error) { + console.log("Error showing modal dialog:", error); + } +} +``` + #### Input Dialog Example Usage + + +#### JavaScript + ```js import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; @@ -252,6 +287,42 @@ async function showInputDialog() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function showInputDialog(): Promise { + try { + // Input Dialog Example + const inputDialogOptions: DialogOptions = { + variant: Variant.input, + title: "Please enter your key", + description: "Your API key", + buttonLabels: { cancel: "Cancel" }, + field: { + label: "API Key", + placeholder: "Enter API key", + fieldType: FieldType.text, + }, + }; + + const inputDialogResult: DialogResult = await addOnUISdk.app.showModalDialog( + inputDialogOptions + ); + + if (inputDialogResult.buttonType === ButtonType.primary) { + console.log("Field value " + inputDialogResult.fieldValue); // returns the input the user entered if they didn't cancel + } + } catch (error) { + console.log("Error showing modal dialog:", error); + } +} +``` + See the use case implementations for an example of the [custom modal dialog](../../guides/learn/how_to/modal_dialogs.md#custom-dialog). @@ -287,6 +358,10 @@ Returns a void `Promise`. #### Example Usage + + +#### JavaScript + ```js const colorPickerButton = document.getElementById("color-picker-button"); @@ -304,6 +379,27 @@ colorPickerButton.addEventListener(ColorPickerEvent.colorChange, (event) => { }); ``` +#### TypeScript + +```ts +const colorPickerButton = document.getElementById("color-picker-button") as HTMLButtonElement; + +colorPickerButton.addEventListener("click", (): void => { + const options: ColorPickerOptions = { + title: "Add-on's Color Picker", + placement: ColorPickerPlacement.left, + eyedropperHidesPicker: true, + disableAlphaChannel: false, + }; + + addOnUISdk.app.showColorPicker(colorPickerButton, options); +}); + +colorPickerButton.addEventListener(ColorPickerEvent.colorChange, (event: CustomEvent): void => { + console.log("Color change event received!", event.detail.color); +}); +``` + ### hideColorPicker() Hides the Adobe Express color picker. @@ -458,6 +554,100 @@ Callback to undo the changes made by `enableDragToDocument`. Returns `void`. type DisableDragToDocument = () => void; ``` +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Enable drag-to-document for an image element +const imageElement = document.getElementById("my-image"); + +const dragCallbacks = { + previewCallback: (element) => { + // Return the preview image URL + return new URL(element.src); + }, + + completionCallback: async (element) => { + try { + // Fetch the image blob for drag completion + const response = await fetch(element.src); + const blob = await response.blob(); + + return [{ + blob: blob, + attributes: { + title: element.alt || "Dragged Image" + } + }]; + } catch (error) { + console.error("Failed to fetch image for drag:", error); + return []; + } + } +}; + +// Enable drag to document +const disableDrag = addOnUISdk.app.enableDragToDocument(imageElement, dragCallbacks); + +// Later, you can disable the drag functionality +// disableDrag(); +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Enable drag-to-document for an image element +const imageElement = document.getElementById("my-image") as HTMLImageElement; + +const dragCallbacks: DragCallbacks = { + previewCallback: (element: HTMLElement): URL => { + // Return the preview image URL + const imgElement = element as HTMLImageElement; + return new URL(imgElement.src); + }, + + completionCallback: async (element: HTMLElement): Promise => { + try { + // Fetch the image blob for drag completion + const imgElement = element as HTMLImageElement; + const response = await fetch(imgElement.src); + const blob: Blob = await response.blob(); + + const completionData: DragCompletionData[] = [{ + blob: blob, + attributes: { + title: imgElement.alt || "Dragged Image" + } + }]; + + return completionData; + } catch (error) { + console.error("Failed to fetch image for drag:", error); + return []; + } + } +}; + +// Enable drag to document +const disableDrag: DisableDragToDocument = addOnUISdk.app.enableDragToDocument(imageElement, dragCallbacks); + +// Later, you can disable the drag functionality +// disableDrag(); +``` + ##### `DragStartEventData` Object The payload data sent to the `dragStart` event handler. diff --git a/src/pages/references/addonsdk/addonsdk-constants.md b/src/pages/references/addonsdk/addonsdk-constants.md index ba11aab2b..21e81e4d0 100644 --- a/src/pages/references/addonsdk/addonsdk-constants.md +++ b/src/pages/references/addonsdk/addonsdk-constants.md @@ -1,7 +1,121 @@ # addOnUISdk.constants +This reference covers **all Adobe Express Add-on SDK constants**, including: +- Constants available in `addOnUISdk.constants.*` (dual access) +- Constants available only as named exports (import required) + +See the [Import Patterns](#import-patterns) section for details on how to access each type. + A set of constants used throughout the add-on SDK. These constants are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". +## Import Patterns + +Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is crucial for avoiding runtime errors. + +### Named Exports (Import Required) + +These constants are **only available as named exports** and must be imported explicitly. They are **not** available through `addOnUISdk.constants.*`: + +```javascript +import addOnUISdk, { + AppEvent, + ColorPickerEvent, + SupportedMimeTypes, + EntrypointType, + PdfReturnUrlType +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// ✅ Correct usage +const docxMimeType = SupportedMimeTypes.docx; +const colorChangeEvent = ColorPickerEvent.colorChange; + +// ❌ Will NOT work - these are not in the constants object +const docxMimeType = addOnUISdk.constants.SupportedMimeTypes.docx; // undefined +``` + +### Dual Access Constants + +These constants support **both import patterns** for flexibility. You can use either approach: + +```javascript +import addOnUISdk, { Range, RenditionFormat, Variant } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Option 1: Named import (recommended for cleaner code) +const currentPage = Range.currentPage; +const pngFormat = RenditionFormat.png; +const confirmDialog = Variant.confirmation; + +// Option 2: Constants object access (traditional pattern) +const currentPage = addOnUISdk.constants.Range.currentPage; +const pngFormat = addOnUISdk.constants.RenditionFormat.png; +const confirmDialog = addOnUISdk.constants.Variant.confirmation; +``` + +**Dual Access Constants List:** +- `Range` / `addOnUISdk.constants.Range` +- `RenditionFormat` / `addOnUISdk.constants.RenditionFormat` +- `RenditionType` / `addOnUISdk.constants.RenditionType` +- `RenditionIntent` / `addOnUISdk.constants.RenditionIntent` +- `Variant` / `addOnUISdk.constants.Variant` +- `DialogResultType` / `addOnUISdk.constants.DialogResultType` +- `ButtonType` / `addOnUISdk.constants.ButtonType` +- `RuntimeType` / `addOnUISdk.constants.RuntimeType` +- `BleedUnit` / `addOnUISdk.constants.BleedUnit` +- `EditorPanel` / `addOnUISdk.constants.EditorPanel` +- `MediaTabs` / `addOnUISdk.constants.MediaTabs` +- `ElementsTabs` / `addOnUISdk.constants.ElementsTabs` +- `PanelActionType` / `addOnUISdk.constants.PanelActionType` +- `ColorPickerPlacement` / `addOnUISdk.constants.ColorPickerPlacement` +- `AuthorizationStatus` / `addOnUISdk.constants.AuthorizationStatus` +- `FieldType` / `addOnUISdk.constants.FieldType` +- `PlatformEnvironment` / `addOnUISdk.constants.PlatformEnvironment` +- `DeviceClass` / `addOnUISdk.constants.DeviceClass` +- `PlatformType` / `addOnUISdk.constants.PlatformType` + +### Best Practices + +1. **Use named imports for cleaner code** when you know which constants you need: + ```javascript + import addOnUISdk, { Range, RenditionFormat } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + + const options = { + range: Range.currentPage, + format: RenditionFormat.png + }; + ``` + +2. **Use constants object for dynamic access** when the constant name is determined at runtime: + ```javascript + const format = addOnUISdk.constants.RenditionFormat[userSelectedFormat]; + ``` + +3. **Always import named-only exports** - there's no alternative way to access them: + ```javascript + import addOnUISdk, { SupportedMimeTypes } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + ``` + +### Quick Reference Table + +| Constant | Named Export | Constants Object | Import Required | +|----------|--------------|------------------|-----------------| +| `AppEvent` | ✅ | ❌ | **Yes** | +| `ColorPickerEvent` | ✅ | ❌ | **Yes** | +| `SupportedMimeTypes` | ✅ | ❌ | **Yes** | +| `EntrypointType` | ✅ | ❌ | **Yes** | +| `PdfReturnUrlType` | ✅ | ❌ | **Yes** | +| `Range` | ✅ | ✅ | Optional | +| `RenditionFormat` | ✅ | ✅ | Optional | +| `Variant` | ✅ | ✅ | Optional | +| `ButtonType` | ✅ | ✅ | Optional | +| `FieldType` | ✅ | ✅ | Optional | +| `PlatformEnvironment` | ✅ | ✅ | Optional | +| `DeviceClass` | ✅ | ✅ | Optional | +| `PlatformType` | ✅ | ✅ | Optional | + + + +**Important:** Attempting to access named-only exports through `addOnUISdk.constants.*` will return `undefined` and may cause runtime errors. Always check the table above or use TypeScript for compile-time validation. + ## Constants

ButtonType

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

string

-

The type of the button pressed in a dialog.

+

The type of the button pressed in a dialog. Dual access: both named export and constants object.

  • primary
  • Primary button pressed.
  • secondary
  • Secondary button pressed. @@ -59,7 +173,7 @@ A set of constants used throughout the add-on SDK. These constants are equal to

ColorPickerEvent

string

-

Custom events dispatched by the Color Picker.

+

Custom events dispatched by the Color Picker. Named export only - not available in constants object.

  • colorChange
  • "colorpicker-color-change"
  • close
  • "colorpicker-close"
    @@ -70,7 +184,7 @@ A set of constants used throughout the add-on SDK. These constants are equal to

ColorPickerPlacement

string

-

Placement of the color picker popover with respect to the anchor element.

+

Placement of the color picker popover with respect to the anchor element. Dual access: both named export and constants object.

  • top
  • "top"
  • bottom
  • "bottom"
    @@ -187,7 +301,7 @@ A set of constants used throughout the add-on SDK. These constants are equal to

Range

string

-

Rendition page range. Options:

+

Rendition page range. Dual access: both named export and constants object.

  • currentPage
  • Generate rendition for the current page
  • entireDocument
  • Generate rendition for all pages @@ -199,7 +313,7 @@ A set of constants used throughout the add-on SDK. These constants are equal to

RenditionFormat

string

-

Required output format of the rendition.

+

Required output format of the rendition. Dual access: both named export and constants object.

  • jpg
  • "image/jpeg"
  • png
  • "image/png"
    @@ -213,7 +327,7 @@ A set of constants used throughout the add-on SDK. These constants are equal to

RenditionIntent

string

-

The intent to set for creating the rendition. Options:

+

The intent to set for creating the rendition. Dual access: both named export and constants object.

  • preview
  • Intent to preview the content.
  • export
  • Intent to export/download the content (default). @@ -245,7 +359,7 @@ A set of constants used throughout the add-on SDK. These constants are equal to

Variant

string

-

Types of dialog variants supported.

+

Types of dialog variants supported. Dual access: both named export and constants object.

  • confirmation
  • Ask a user to confirm an action.
  • information
  • Share information for user to acknowledge. @@ -272,5 +386,106 @@ A set of constants used throughout the add-on SDK. These constants are equal to

AppEvent

string

+

Events dispatched by the Add-on SDK. Named export only - not available in constants object.

+
    +
  • documentIdAvailable
  • Document ID becomes available +
  • documentTitleChange
  • Document title changes +
  • documentLinkAvailable
  • Document link becomes available +
  • documentPublishedLinkAvailable
  • Published document link becomes available +
+

AuthorizationStatus

string

+

OAuth authorization status values. Dual access: both named export and constants object.

+
    +
  • authorized
  • Authorization successful +
  • cancelled
  • Authorization cancelled by user +
  • error
  • Authorization error occurred +
+

SupportedMimeTypes

string

+

MIME types for original source assets converted to PDF. Named export only - not available in constants object.

+
    +
  • docx
  • "docx"
    +
  • gdoc
  • "gdoc"
    +
+

PdfReturnUrlType

string

+

Specifies the type of URL returned for PDF rendition export. Named export only - not available in constants object.

+
    +
  • cdnUrl
  • "cdnUrl"
    +
  • jumpUrl
  • "jumpUrl"
    +
+

FieldType

string

+

The type of input field supported in Simple Dialog. Dual access: both named export and constants object.

+
    +
  • text
  • "text"
    +
+

PlatformEnvironment

string

+

Denotes the current environment where the add-on is running. Dual access: both named export and constants object.

+
    +
  • app
  • "app"
    +
  • web
  • "web"
    +
+

DeviceClass

string

+

Denotes the device class/form factor where the add-on is running. Dual access: both named export and constants object.

+
    +
  • mobile
  • "mobile"
    +
  • tablet
  • "tablet"
    +
  • desktop
  • "desktop"
    +
+

PlatformType

string

+

Denotes the specific platform/operating system where the add-on is running. Dual access: both named export and constants object.

+
    +
  • iOS
  • "ios"
    +
  • iPadOS
  • "ipad"
    +
  • chromeOS
  • "chromeOS"
    +
  • android
  • "android"
    +
  • chromeBrowser
  • "chromeBrowser"
    +
  • firefoxBrowser
  • "firefoxBrowser"
    +
  • edgeBrowser
  • "edgeBrowser"
    +
  • samsungBrowser
  • "samsumgBrowser"
    (Note: Contains typo in SDK) +
  • safariBrowser
  • "safariBrowser"
    +
  • unknown
  • "unknown"
    +
+
diff --git a/src/pages/references/addonsdk/addonsdk-instance.md b/src/pages/references/addonsdk/addonsdk-instance.md index 8b205046b..4ecfb237c 100644 --- a/src/pages/references/addonsdk/addonsdk-instance.md +++ b/src/pages/references/addonsdk/addonsdk-instance.md @@ -1,6 +1,6 @@ # addOnUISdk.instance -Represents the currently running add-on instance. This object is used to provide access to the `clientStorage` and `manifest` objects. See the [Storing and Retrieving Client Side Data](../../guides/learn/how_to/local_data_management.md) use case implementation and [Manifest](../manifest) reference for more details. +Represents the currently running add-on instance. This object is used to provide access to the `clientStorage`, `manifest`, and `runtime` objects. See the [Storing and Retrieving Client Side Data](../../guides/learn/how_to/local_data_management.md) use case implementation and [Manifest](../manifest) reference for more details. ## Objects diff --git a/src/pages/references/addonsdk/app-document.md b/src/pages/references/addonsdk/app-document.md index d18269e00..050a727b6 100644 --- a/src/pages/references/addonsdk/app-document.md +++ b/src/pages/references/addonsdk/app-document.md @@ -92,7 +92,7 @@ A resolved `Promise` containing a [`PageMetadata`](#pagemetadata) array containi #### Example Usage - + #### JavaScript @@ -135,6 +135,49 @@ async function logMetadata() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Get metadata of all the pages +async function logMetadata(): Promise { + try { + const options: PageMetadataOptions = { + range: Range.specificPages, + pageIds: [ + "7477a5e7-02b2-4b8d-9bf9-f09ef6f8b9fc", + "d45ba3fc-a3df-4a87-80a5-655e5f8f0f96" + ] + }; + + const pages: PageMetadata[] = await addOnUISdk.app.document.getPagesMetadata(options); + + for (const page of pages) { + console.log("Page id: ", page.id); + console.log("Page title: ", page.title); + console.log("Page size: ", page.size); + console.log("Page has premium content: ", page.hasPremiumContent); + console.log("Page has audio content: ", page.hasAudioContent); + console.log("Page has video content: ", page.hasVideoContent); + console.log("Page has animated content: ", page.hasAnimatedContent); + console.log("Page has timelines: ", page.hasTemporalContent); + if (page.hasTemporalContent) + console.log("Page includes temporal content with a duration of: ", page.temporalContentDuration); + console.log("Pixels per inch: ", page.pixelsPerInch); + console.log("Is page print ready: ", page.isPrintReady); + console.log("Is page blank: ", page.isBlank); + console.log("Template details: ", page.templateDetails); + } + } catch (error) { + console.log("Failed to get metadata:", error); + } +} +``` + #### Output ```bash @@ -177,7 +220,7 @@ Tells Express to run a print quality check to determine if the document is ready #### Example Usage - + #### JavaScript @@ -200,6 +243,44 @@ function runPrintQualityCheck() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Reference to the active document +const { document } = addOnUISdk.app; + +// Run Print Quality Check +function runPrintQualityCheck(): void { + try { + const options: PrintQualityCheckOptions = { + range: Range.entireDocument, + }; + + document.runPrintQualityCheck(options); + console.log("Print quality check completed successfully"); + } catch (error) { + console.log("Failed to run print quality check"); + } +} + +// Run Print Quality Check for specific pages +function runPrintQualityCheckForPages(pageIds: string[]): void { + try { + const options: PrintQualityCheckOptions = { + range: Range.specificPages, + pageIds: pageIds + }; + + document.runPrintQualityCheck(options); + console.log("Print quality check completed successfully for specific pages"); + } catch (error) { + console.log("Failed to run print quality check for specific pages"); + } +} +``` + #### Output ```bash @@ -267,7 +348,11 @@ A resolved `Promise` containing an array of `string` ids representing the curren #### Example Usage -```javascript + + +#### JavaScript + +```js import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; // Wait for the SDK to be ready @@ -316,6 +401,59 @@ getSelectedPages(); getSelectedPagesMetadata(); ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Get the currently selected page ids +async function getSelectedPages(): Promise { + try { + const selectedPageIds: string[] = await addOnUISdk.app.document.getSelectedPageIds(); + console.log("Selected page ids:", selectedPageIds); + + if (selectedPageIds.length === 0) { + console.log("No pages are currently selected"); + } else { + console.log(`${selectedPageIds.length} page(s) selected:`, selectedPageIds); + } + } catch (error) { + console.log("Failed to get selected page ids:", error); + } +} + +// Example: Get metadata for selected pages only +async function getSelectedPagesMetadata(): Promise { + try { + const selectedPageIds: string[] = await addOnUISdk.app.document.getSelectedPageIds(); + + if (selectedPageIds.length > 0) { + const options: PageMetadataOptions = { + range: Range.specificPages, + pageIds: selectedPageIds + }; + + const metadata: PageMetadata[] = await addOnUISdk.app.document.getPagesMetadata(options); + + metadata.forEach((page: PageMetadata, index: number) => { + console.log(`Selected page ${index + 1}: ${page.title} (${page.id})`); + }); + } else { + console.log("No pages selected"); + } + } catch (error) { + console.log("Failed to get selected pages metadata:", error); + } +} + +// Call the functions +getSelectedPages(); +getSelectedPagesMetadata(); +``` + ### link() Retrieves the document link. @@ -571,12 +709,21 @@ Mime type details for importing media | Name | Type | Description | | ----------------- | -------------------------- | ---------------------------------------------------------------: | -| `sourceMimeType?` | [`SupportedMimeTypes`](./) | Mime type of the original source asset that was converted to PDF | +| `sourceMimeType?` | [`SupportedMimeTypes`](#supportedmimetypes) | Mime type of the original source asset that was converted to PDF | ### SupportedMimeTypes -A constant representing the mime type of the original source asset that was converted to PDF. +A constant representing the mime type of the original source asset that was converted to PDF. **Note:** This constant is only available as a named export and must be imported explicitly. + +```javascript +import addOnUISdk, { SupportedMimeTypes } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Usage +const docxMimeType = SupportedMimeTypes.docx; +const gdocMimeType = SupportedMimeTypes.gdoc; +``` +Available values: - **docx**: `"docx"` - **gdoc**: `"gdoc"` diff --git a/src/pages/references/addonsdk/app-oauth.md b/src/pages/references/addonsdk/app-oauth.md index d090a52cc..22bbf2a4b 100644 --- a/src/pages/references/addonsdk/app-oauth.md +++ b/src/pages/references/addonsdk/app-oauth.md @@ -42,6 +42,86 @@ A resolved `Promise` with the [`AuthorizationResponse`](#authorizationresponse) | `redirectUri` | `string` | URL where the user is redirected to after authorization. This is the default URL owned by Adobe and it is this URL which needs to be used to obtain access_token. | | `result` | `string` or `object` | An [`AuthorizationResult`](#authorizationresult) payload which denotes either success or failure. In the event of a "FAILED" status reported by the OAuth provider during authorization, the value of this property is an `object`, in the form of `{[failure_title]: "failure_description"}`, and for all other statuses the value of `description` is a `string`. | +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthAuthorization() { + try { + const authRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const response = await addOnUISdk.app.oauth.authorize(authRequest); + + if (response.result.status === "SUCCESS") { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthAuthorization(): Promise { + try { + const authRequest: AuthorizationRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const response: AuthorizationResponse = await addOnUISdk.app.oauth.authorize(authRequest); + + if (response.result.status === AuthorizationStatus.authorized) { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + ### authorizeWithOwnRedirect() Initiate the OAuth 2.0 PKCE authorization workflow by opening the user sign-in window. After authorization, the user is redirected to the add-on developer provided URL. @@ -80,6 +160,204 @@ A resolved `Promise` with the [`AuthorizationResult`](#authorizationresult) obje | `status` | `AuthorizationStatus` | Status representing success or failure in the authorization workflow. | | `description` | `string` or `object` | Description about the success or failure in the authorization workflow In the event of a "FAILED" status reported by the OAuth provider during authorization, the value of this property is an `object`, in the form of `{[failure_title]: "failure_description"}`. While for all other statuses the value of this property is a `string` | +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthWithRedirect() { + try { + const authRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + redirectUri: "https://yourapp.com/oauth/callback", + state: "random-state-value", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const result = await addOnUISdk.app.oauth.authorizeWithOwnRedirect(authRequest); + + if (result.status === "POPUP_OPENED") { + console.log("Authorization popup opened successfully"); + console.log("Handle the redirect at your callback URL:", authRequest.redirectUri); + } else { + console.log("Authorization failed:", result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthWithRedirect(): Promise { + try { + const authRequest: AuthorizeWithOwnRedirectRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + redirectUri: "https://yourapp.com/oauth/callback", + state: "random-state-value", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const result: AuthorizationResult = await addOnUISdk.app.oauth.authorizeWithOwnRedirect(authRequest); + + if (result.status === AuthorizationStatus.cancelled) { + console.log("Authorization popup opened successfully"); + console.log("Handle the redirect at your callback URL:", authRequest.redirectUri); + } else { + console.log("Authorization failed:", result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +### authorizeInsideIframe() + +Authorize a user using OAuth 2.0 PKCE workflow in an iframe. This method provides an alternative to popup-based authorization for cases where popups may be blocked or when you prefer an iframe-based flow. + +#### Signature: + +`authorizeInsideIframe(request: AuthorizeInsideIframeRequest): Promise` + +#### Parameters + +| Name | Type | Description | +| --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------: | +| `request` | `object` | [`AuthorizeInsideIframeRequest`](#authorizeinsideiframerequest) object payload with parameters to be used in the authorization workflow. | + +#### `AuthorizeInsideIframeRequest` + +Extends [`AuthorizationRequest`](#authorizationrequest) with additional properties specific to iframe-based OAuth flows. + +| Name | Type | Description | +| ----------------------- | ----------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| `authorizationUrl` | `string` | OAuth provider's authorization URL. | +| `clientId` | `string` | Client identifier of the application created at the OAuth provider. | +| `scope` | `string` | Scope to control the application's access. | +| `codeChallenge` | `string` | Code challenge used in Authorization Code Exchange. | +| `additionalParameters?` | `Map` | Additional parameters, specific to an OAuth provider which are required in the Authorization URL as query string parameters. | +| `windowSize?` | `{ width: number; height: number }` | The authorization iframe size in the form of an `object` containing the desired `width` and `height` as a `number`.

**NOTE:** The **minimum** (and **default**) values for `windowSize` are 480 x 480. The **maximum** allowed values are 800 x (screen height). | +| `position?` | `{ top: number; left: number }` | Relative position of the OAuth iframe panel. | +| `offset?` | `{ right: number; bottom: number }` | Offset from the right and bottom of the iframe container when the size (`windowSize`) is not specified. | +| `showHeader?` | `boolean` | Flag to determine if the iframe panel needs to show a header. | + +#### Return Value + +A resolved `Promise` with the [`AuthorizationResponse`](#authorizationresponse) object, which contains the authorization code, redirect URI, and result status. + +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthInsideIframe() { + try { + const authRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 }, + position: { top: 100, left: 100 }, + showHeader: true + }; + + const response = await addOnUISdk.app.oauth.authorizeInsideIframe(authRequest); + + if (response.result.status === "SUCCESS") { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthInsideIframe(): Promise { + try { + const authRequest: AuthorizeInsideIframeRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 }, + position: { top: 100, left: 100 }, + showHeader: true + }; + + const response: AuthorizationResponse = await addOnUISdk.app.oauth.authorizeInsideIframe(authRequest); + + if (response.result.status === AuthorizationStatus.authorized) { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + #### `AuthorizationStatus` Each of the statuses returned below is the exact name as a string (ie: SUCCESS = "SUCCESS") diff --git a/src/pages/references/addonsdk/index.md b/src/pages/references/addonsdk/index.md index f00603c5c..f7c80a4e1 100644 --- a/src/pages/references/addonsdk/index.md +++ b/src/pages/references/addonsdk/index.md @@ -87,6 +87,10 @@ The SDK can be referenced in `.js/.jsx/.ts/.tsx` source files by adding it to th import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; ``` + + +**📦 Working with SDK Constants:** Adobe Express SDK constants use different import patterns. Some require named imports while others support both approaches. See the [complete import patterns guide](./addonsdk-constants.md#import-patterns) for details and examples. + **Note:** if you created your add-on project with the CLI based on the `typescript` or `typescript-react` templates, you will automatically get a types definition file named `add-on-ui-sdk.d.ts` generated in your project `src` for you. This file contains the following exports, and allows you to take advantage of type checking and auto-completion features while developing with the Add-on SDK in your IDE. ```ts @@ -136,7 +140,7 @@ The following properties can be accessed from the `addOnUISdk` object after it h

addOnUISdk.constants

object

-

A set of constants used throughout the add-on SDK.

+

A set of constants used throughout the add-on SDK. See the import patterns guide for proper usage.

readonly

From 87d2fae08e6c6d83a4f036563037cfc5fd640938 Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 27 Aug 2025 15:07:45 -0400 Subject: [PATCH 2/8] Continued updates, constants, runtime architecture --- .../platform_concepts/constants-guide.md | 106 +++ .../platform_concepts/runtime-architecture.md | 616 ++++++++++++++++++ .../references/addonsdk/addonsdk-constants.md | 163 ++++- 3 files changed, 880 insertions(+), 5 deletions(-) create mode 100644 src/pages/guides/learn/platform_concepts/constants-guide.md create mode 100644 src/pages/guides/learn/platform_concepts/runtime-architecture.md diff --git a/src/pages/guides/learn/platform_concepts/constants-guide.md b/src/pages/guides/learn/platform_concepts/constants-guide.md new file mode 100644 index 000000000..07ba4609b --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/constants-guide.md @@ -0,0 +1,106 @@ +## Add-on UI SDK Constants + +### **Dialog & UI Interaction Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Variant`** | `confirmation`, `information`, `warning`, `destructive`, `error`, `input`, `custom` | Dialog types for `showModalDialog()` | `addOnUISdk.app.showModalDialog({variant: Variant.error})` | +| **`ButtonType`** | `primary`, `secondary`, `cancel`, `close` | Button types in dialog responses | Check `result.buttonType === ButtonType.primary` | +| **`FieldType`** | `text` | Input field types for input dialogs | Used in dialog field configuration | +| **`DialogResultType`** | `alert`, `input`, `custom` | Types of dialog results returned | Determine result structure type | + +### **Platform & Environment Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`PlatformType`** | `iOS`, `iPadOS`, `chromeOS`, `android`, `chromeBrowser`, `firefoxBrowser`, `edgeBrowser`, `samsungBrowser`, `safariBrowser`, `unknown` | Platform identification | `platform.platformType === PlatformType.iOS` | +| **`PlatformEnvironment`** | `app`, `web` | Runtime environment type | Check if running in app vs web | +| **`DeviceClass`** | `mobile`, `tablet`, `desktop` | Device category | Responsive design decisions | +| **`RuntimeType`** | `panel`, `dialog`, `documentSandbox`, `command` | Add-on runtime types | Communication API configuration | + +### **Document Export & Rendering Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Range`** | `currentPage`, `entireDocument`, `specificPages` | Rendition page ranges | `createRenditions({range: Range.currentPage})` | +| **`RenditionFormat`** | `png`, `jpg`, `mp4`, `pdf`, `pptx` | Export file formats | Specify output format for renditions | +| **`RenditionType`** | `page` | Type of rendition | Document export configuration | +| **`RenditionIntent`** | `export`, `preview`, `print` | Purpose of rendition | Optimize rendering for use case | + +### **Video & Media Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`VideoResolution`** | `sd480p`, `hd720p`, `fhd1080p`, `qhd1440p`, `uhd2160p`, `custom` | Video export resolutions | Video rendition quality settings | +| **`FrameRate`** | `fps23_976`, `fps24`, `fps25`, `fps29_97`, `fps30`, `fps60` | Video frame rates | Video export frame rate | +| **`BitRate`** | `mbps4` through `mbps50` | Video bit rates in bps | Video compression settings | +| **`BleedUnit`** | `Inch`, `Millimeter` | Print bleed units | Print preparation | + +### **Editor Panel Navigation Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`EditorPanel`** | `search`, `yourStuff`, `templates`, `media`, `text`, `elements`, `grids`, `brands`, `addOns` | Express editor panels | `openEditorPanel(EditorPanel.media)` | +| **`MediaTabs`** | `video`, `audio`, `photos` | Media panel tabs | Navigate to specific media type | +| **`ElementsTabs`** | `designAssets`, `backgrounds`, `shapes`, `stockIcons`, `charts` | Elements panel tabs | Navigate to specific element type | +| **`PanelActionType`** | `search`, `navigate` | Panel action types | Panel interaction configuration | + +### **Color Picker Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`ColorPickerPlacement`** | `top`, `bottom`, `left`, `right` | Color picker positioning | `showColorPicker(element, {placement: ColorPickerPlacement.top})` | + +### **File Management Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`FileSizeLimitUnit`** | `KB`, `MB` | File size limit units | File upload constraints | + +### **OAuth & Authentication Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`AuthorizationStatus`** | (imported from @hz/wxp-oauth) | OAuth authorization states | Check authorization status | + +### **Usage Examples** + +```typescript +import addOnUISdk, { + Variant, + PlatformType, + RenditionFormat, + EditorPanel +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Dialog with error variant +await addOnUISdk.app.showModalDialog({ + variant: Variant.error, + title: "Upload Failed", + description: "File size exceeds limit" +}); + +// Platform-specific behavior +const platform = await addOnUISdk.app.getCurrentPlatform(); +if (platform.platformType === PlatformType.iOS) { + // iOS-specific handling +} + +// Export document as PNG +await addOnUISdk.app.document.createRenditions({ + format: RenditionFormat.png, + range: Range.currentPage +}); + +// Navigate to media panel +addOnUISdk.app.ui.openEditorPanel(EditorPanel.media); +``` + +### **Notes for Developers** + +- **All constants are available as named exports** from the Add-on UI SDK +- **Use constants instead of string literals** for type safety and future compatibility +- **Some constants are marked as `@internal`** (like `ToastVariant`, `FeatureType`, `SettingType`) and are not included as they're for internal/privileged use only +- **Platform detection is crucial** for responsive add-on design across different devices and browsers + +These constants provide type-safe ways to interact with all the major Add-on UI SDK features that third-party developers commonly use! \ No newline at end of file diff --git a/src/pages/guides/learn/platform_concepts/runtime-architecture.md b/src/pages/guides/learn/platform_concepts/runtime-architecture.md new file mode 100644 index 000000000..d6f8f808e --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/runtime-architecture.md @@ -0,0 +1,616 @@ +--- +keywords: + - Adobe Express + - Express Add-on SDK + - Express Editor + - Adobe Express + - Add-on SDK + - SDK + - JavaScript + - Extend + - Extensibility + - API + - Runtime + - Communication + - Document Sandbox + - UI Runtime + - Architecture + - Two-Runtime System +title: Runtime Architecture & Communication +description: Understanding the two-runtime architecture and communication system in Adobe Express add-ons. +contributors: + - https://github.com/hollyschinsky +--- + +# Runtime Architecture & Communication + +Understanding Adobe Express add-on architecture is crucial for building effective add-ons. This guide explains the two-runtime system and how communication works between environments. + +## Two-Runtime Architecture Overview + +Adobe Express add-ons run in **two separate JavaScript execution environments** that work together: + +1. **UI Runtime** - Your add-on's user interface (iframe) +2. **Document Sandbox** - Secure environment for document manipulation + +## Architecture Diagram + +```mermaid +graph TB + subgraph "Adobe Express Add-on Architecture" + + subgraph UI["UI Runtime Environment
(iframe)
"] + UICode["index.js/index.html
Your Add-on UI"] + UIRuntime["addOnUISdk.instance.runtime
Communication Bridge"] + UIFeatures["• User Interface
• Event Handling
• OAuth & Storage
• Modal Dialogs
"] + end + + subgraph Sandbox["Document Sandbox Environment
(Secure JavaScript Context)
"] + SandboxCode["code.js




Document Manipulation"] + SandboxRuntime["addOnSandboxSdk.instance.runtime
Communication Bridge"] + SandboxFeatures["• Document API
• Create Elements
• Modify Content
• Export Renditions
"] + end + + UIRuntime -.->|""| SandboxRuntime + SandboxRuntime -.->|"runtime.apiProxy('panel')
"| UIRuntime + + UIRuntime -->|"exposeApi()"| UICode + SandboxRuntime -->|"exposeApi()"| SandboxCode + end + + style UI fill:#ba68c8 + style Sandbox fill:#333333 + style UIRuntime fill:#000000 + style SandboxRuntime fill:##68c8ba +``` + +## What is the Runtime Object? + +The `runtime` object acts as a **communication bridge** between the two environments. Each environment has its own runtime object: + +- **UI Runtime**: `addOnUISdk.instance.runtime` +- **Document Sandbox**: `addOnSandboxSdk.instance.runtime` + +Think of it as a "phone line" - each side has their own phone to call the other side. + +## The Two Environments Explained + +### UI Runtime Environment + +**File:** `index.js` or `index.html` +**Purpose:** User interface and browser interactions +**SDK Import:** +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; +``` + +**Capabilities:** +- Render your add-on's user interface +- Handle user interactions (clicks, form inputs) +- Access browser APIs (fetch, localStorage) +- OAuth authentication +- Modal dialogs +- File uploads/downloads + +### Document Sandbox Environment + +**File:** `code.js` +**Purpose:** Document manipulation and content creation +**SDK Import:** +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +``` + +**Capabilities:** +- Access Adobe Express Document API +- Create and modify document elements +- Export renditions +- Secure, isolated execution +- Limited browser API access + +## Communication Flow + +### From UI to Document Sandbox + +When you want to manipulate the document from your UI: + +#### UI Runtime (index.js) +```js +// ui/index.js - Your add-on's interface +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +addOnUISdk.ready.then(async () => { + const { runtime } = addOnUISdk.instance; + + // Button click handler + document.getElementById("createText").addEventListener("click", async () => { + // Get a proxy to call document sandbox functions + const sandboxProxy = await runtime.apiProxy("documentSandbox"); + + // Call function exposed in code.js + await sandboxProxy.createTextElement("Hello World!"); + }); +}); +``` + +#### Document Sandbox (code.js) +```js +// sandbox/code.js - Document manipulation +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor } from "express-document-sdk"; + +const { runtime } = addOnSandboxSdk.instance; + +// Expose functions that UI can call +runtime.exposeApi({ + createTextElement: async function(text) { + await editor.queueAsyncEdit(() => { + const textNode = editor.createText(text); + editor.context.insertionParent.children.append(textNode); + }); + } +}); +``` + +### From Document Sandbox to UI + +When you want to update the UI from document operations: + +#### Document Sandbox (code.js) +```js +// sandbox/code.js - Document manipulation +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +async function processDocument() { + // Get a proxy to call UI functions + const uiProxy = await runtime.apiProxy("panel"); + + // Update UI with progress + await uiProxy.updateProgress(50); + + // Document manipulation here... + + await uiProxy.updateProgress(100); +} +``` + +#### UI Runtime (index.js) +```js +// ui/index.js - Your add-on's interface +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; + + // Expose functions that document sandbox can call + runtime.exposeApi({ + updateProgress: function(percentage) { + const progressBar = document.getElementById("progress"); + progressBar.style.width = percentage + "%"; + } + }); +}); +``` + +## Frequently Asked Questions + +### Q: Why are there two different runtime objects? + +**A:** Each environment has its own runtime object that acts as a "communication phone." The UI runtime calls the document sandbox, and the document sandbox runtime calls the UI. This separation ensures security and proper isolation. + +### Q: When do I use `addOnUISdk.instance.runtime` vs `addOnSandboxSdk.instance.runtime`? + +**A:** Use the runtime object from the environment you're currently in: +- In `index.js` (UI): Use `addOnUISdk.instance.runtime` +- In `code.js` (Sandbox): Use `addOnSandboxSdk.instance.runtime` + +### Q: What does `await runtime.apiProxy("documentSandbox")` actually do? + +**A:** It creates a proxy object that lets you call functions you've exposed in the document sandbox from your UI code. Think of it as getting a "remote control" for the other environment. + +### Q: What's the difference between `"documentSandbox"` and `"panel"` in apiProxy? + +**A:** These specify which environment you want to communicate with: +- `"documentSandbox"` - Call from UI to document sandbox +- `"panel"` - Call from document sandbox to UI + +### Q: Can I access document APIs directly from the UI? + +**A:** No, document APIs are only available in the document sandbox for security reasons. You must use the communication system to bridge between environments. + +### Q: How do I know which environment my code is running in? + +**A:** Check your file structure and imports: +- If you're importing `addOnUISdk` → You're in the UI runtime +- If you're importing `addOnSandboxSdk` → You're in the document sandbox + +### Q: Do I always need both imports in my code.js file? + +**A:** No! It depends on what your add-on does: + +- **Communication only**: Just `addOnSandboxSdk` (no document changes) +- **Document only**: Just `express-document-sdk` (no UI communication) +- **Both**: Most add-ons need both for UI-triggered document operations + +### Q: Why do some examples show one import and others show both? + +**A:** Different add-on types have different needs: +- Simple document manipulation → One import +- UI-controlled document changes → Both imports +- The example context determines which imports are shown + +### Q: What else is available in the add-on-sdk-document-sandbox package? + +**A:** Besides `runtime`, the sandbox SDK provides: +- **Web APIs**: Limited browser APIs like `console` for debugging +- **Automatic global injections**: No need to import basic APIs +- **Secure execution environment**: Isolated JavaScript context + +## Understanding the Two SDK Imports + +### When do I need both SDK imports in code.js? + +This is a common source of confusion. In your `code.js` file, you may need one or both imports depending on what your add-on does: + +#### Option 1: Communication Only +```js +// sandbox/code.js - Only communication, no document manipulation +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Only exposing APIs, not creating document content +runtime.exposeApi({ + processData: function(data) { + // Process data, return results + return data.map(item => item.toUpperCase()); + } +}); +``` + +#### Option 2: Document Manipulation Only +```js +// sandbox/code.js - Only document creation, no communication +import { editor } from "express-document-sdk"; + +// Directly manipulate document without UI communication +editor.queueAsyncEdit(() => { + const text = editor.createText("Auto-generated content"); + editor.context.insertionParent.children.append(text); +}); +``` + +#### Option 3: Both Communication AND Document Manipulation +```js +// sandbox/code.js - Most common pattern +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // For communication +import { editor } from "express-document-sdk"; // For document manipulation + +const { runtime } = addOnSandboxSdk.instance; + +// Expose APIs that manipulate the document +runtime.exposeApi({ + createTextElement: async function(text) { + await editor.queueAsyncEdit(() => { + const textNode = editor.createText(text); + editor.context.insertionParent.children.append(textNode); + }); + } +}); +``` + +### Quick Decision Guide + +**Do I need `addOnSandboxSdk`?** +- ✅ YES if your code.js needs to communicate with the UI +- ✅ YES if UI triggers document operations +- ❌ NO if code.js runs independently + +**Do I need `express-document-sdk`?** +- ✅ YES if creating/modifying document content +- ✅ YES if accessing document properties +- ❌ NO if only processing data or communicating + +### Real-World Examples + +#### Example 1: Text Generator Add-on +```js +// sandbox/code.js - Needs BOTH imports +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // UI sends text data +import { editor } from "express-document-sdk"; // Create text elements + +const { runtime } = addOnSandboxSdk.instance; + +runtime.exposeApi({ + generateText: async function(userInput) { + await editor.queueAsyncEdit(() => { + const text = editor.createText(userInput); + editor.context.insertionParent.children.append(text); + }); + } +}); +``` + +#### Example 2: Document Analytics Add-on +```js +// sandbox/code.js - Needs BOTH imports +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // Send results to UI +import { editor } from "express-document-sdk"; // Analyze document + +const { runtime } = addOnSandboxSdk.instance; + +runtime.exposeApi({ + analyzeDocument: function() { + const pageCount = editor.context.insertionParent.children.length; + return { pageCount, elements: pageCount * 5 }; // Send data back to UI + } +}); +``` + +#### Example 3: Document Analysis Utility +```js +// sandbox/code.js - Only needs express-document-sdk +import { editor } from "express-document-sdk"; + +// Analyze current document structure (no UI communication needed) +function analyzeDocument() { + const pages = editor.context.insertionParent.children; + const elementCount = pages.length; + + console.log(`Document analysis: ${elementCount} elements found`); + + // Could save analysis results to document metadata or + // trigger other document operations based on analysis + return { elementCount, timestamp: new Date().toISOString() }; +} + +// Execute analysis +const results = analyzeDocument(); +``` + +## Common Patterns + +### Pattern 1: UI-Triggered Document Operations (Most Common) + +User interactions in the UI trigger document changes: + +```js +// UI: User clicks button → Document: Create content +// Requires BOTH imports in code.js +``` + +### Pattern 2: Document-Driven UI Updates + +Document analysis sends results to update the UI: + +```js +// Document: Analyze content → UI: Show results +// Requires BOTH imports in code.js +``` + +### Pattern 3: Independent Document Operations + +Document analysis or operations that run without UI interaction: + +```js +// Document: Analyze/process content +// Only requires express-document-sdk +``` + +## Error Handling + +### Common Error: "Runtime is undefined" + +❌ **Wrong:** Trying to use runtime before SDK is ready +```js +const { runtime } = addOnUISdk.instance; // Error! SDK not ready yet +``` + +✅ **Correct:** Wait for SDK ready state +```js +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; // Now it's safe +}); +``` + +### Common Error: "Cannot read property 'apiProxy' of undefined" + +❌ **Wrong:** Using wrong SDK in wrong environment +```js +// In code.js (Document Sandbox) +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; +const { runtime } = addOnUISdk.instance; // Wrong SDK! +``` + +✅ **Correct:** Use appropriate SDK for each environment +```js +// In code.js (Document Sandbox) +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; // Correct SDK! +``` + +## Complete add-on-sdk-document-sandbox Package Overview + +The `add-on-sdk-document-sandbox` package provides more than just the `runtime` object. Here's what you get: + +### Core Features + +#### 1. Communication System (`addOnSandboxSdk.instance.runtime`) +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Expose APIs to UI +runtime.exposeApi({ /* your functions */ }); + +// Get proxy to UI APIs +const uiProxy = await runtime.apiProxy("panel"); +``` + +#### 2. Injected Web APIs (Global - No Import Needed) +The document sandbox automatically injects limited browser APIs for common tasks. See the complete [Web APIs Reference](../../../references/document-sandbox/web/index.md) for details. + +```js +// These are automatically available in your code.js +console.log("Debugging output"); +console.error("Something went wrong"); +console.warn("Be careful here"); +console.clear(); // Clear console +``` + +#### 3. Secure Execution Environment +- **Isolated JavaScript context**: Your code runs in a secure sandbox +- **Limited browser APIs**: Only essential APIs are available +- **Performance considerations**: Slower than UI runtime but secure +- **No direct DOM access**: Must communicate through UI runtime + +### What You DON'T Need to Import + +These are automatically injected and available globally in the document sandbox. For complete details, see [Web APIs Reference](../../../references/document-sandbox/web/index.md). + +```js +// ✅ Available automatically - no import needed +console.log("This works!"); +console.error("Error logging works!"); +console.warn("Warning logging works!"); +console.info("Info logging works!"); +console.debug("Debug logging works!"); +console.clear(); // Clear console +console.assert(condition, "Assertion message"); + +// Blob interface for binary data +const blob = new Blob(['data'], { type: 'text/plain' }); +blob.text().then(text => console.log(text)); +``` + +### What You DO Need to Import + +#### For Communication: +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; +``` + +#### For Document Manipulation: +```js +import { editor, colorUtils, constants, fonts } from "express-document-sdk"; +``` + +### Advanced Runtime Features + +#### Runtime Type Detection +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Check what type of runtime you're in +console.log(runtime.type); // "documentSandbox" +``` + +#### Environment Information +```js +// The SDK provides context about the execution environment +// This helps with debugging and conditional logic +if (runtime.type === "documentSandbox") { + // You're in the document sandbox + console.log("Running in secure document context"); +} +``` + +### Debugging Capabilities + +Since the document sandbox has limited debugging, use these patterns: + +```js +// Basic debugging with console +console.log("Variable value:", myVariable); + +// Object inspection +console.log("Full object:", JSON.stringify(myObject, null, 2)); + +// Error tracking +try { + // Risky operation + const result = editor.createText("Hello"); + console.log("Success:", result); +} catch (error) { + console.error("Operation failed:", error.message); +} + +// Assertion testing +console.assert(myVariable !== undefined, "Variable should be defined"); +``` + +### Manifest Configuration + +The sandbox requires proper manifest setup: + +```json +{ + "entryPoints": [ + { + "type": "panel", + "id": "panel1", + "main": "index.html", + "documentSandbox": "code.js" + } + ] +} +``` + +### Quick Reference: What's Available Where + +| Feature | UI Runtime | Document Sandbox | Import Required | +|---------|------------|------------------|-----------------| +| Full browser APIs | ✅ | ❌ | - | +| Console logging | ✅ | ✅ | ❌ (Auto-injected) | +| Document APIs | ❌ | ✅ | `express-document-sdk` | +| Communication | ✅ | ✅ | Both SDKs | +| DOM manipulation | ✅ | ❌ | - | +| File system access | ✅ | ❌ | - | +| Network requests | ✅ | ❌* | - | + +*Can be proxied through UI runtime + +## Best Practices + +1. **Clear File Organization** + - Keep UI logic in `index.js` + - Keep document logic in `code.js` + - Use consistent naming for exposed APIs + +2. **Error Handling** + - Always wrap API calls in try-catch blocks + - Provide meaningful error messages to users + - Handle communication failures gracefully + - Use `console.error()` for debugging in sandbox + +3. **Performance** + - Minimize data transfer between environments + - Batch multiple operations when possible + - Use appropriate data types (see [Communication APIs](../../../references/document-sandbox/communication/index.md#data-types)) + - Remember sandbox runs slower than UI runtime + +4. **Debugging** + - Use `console.log` in both environments + - Check browser dev tools for UI runtime + - Use sandbox console for document sandbox debugging + - Use `console.assert()` for validation checks + +## Related Topics + +- [Communication APIs Reference](../../../references/document-sandbox/communication/index.md) +- [UI SDK Reference](../../../references/addonsdk/index.md) +- [Document API Concepts](./document-api.md) +- [Modal Dialogs Tutorial](../how_to/modal_dialogs.md) + +## Next Steps + +Now that you understand the architecture, explore these tutorials: +- [Building your first add-on](../how_to/tutorials/grids-addon.md) +- [Using Communication APIs](../how_to/modal_dialogs.md) +- [Document API deep dive](./document-api.md) diff --git a/src/pages/references/addonsdk/addonsdk-constants.md b/src/pages/references/addonsdk/addonsdk-constants.md index 21e81e4d0..30f4cd600 100644 --- a/src/pages/references/addonsdk/addonsdk-constants.md +++ b/src/pages/references/addonsdk/addonsdk-constants.md @@ -1,16 +1,19 @@ # addOnUISdk.constants -This reference covers **all Adobe Express Add-on SDK constants**, including: +This reference covers set of constants used throughout the Add-on UI SDK, including: + - Constants available in `addOnUISdk.constants.*` (dual access) - Constants available only as named exports (import required) See the [Import Patterns](#import-patterns) section for details on how to access each type. -A set of constants used throughout the add-on SDK. These constants are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". + + +The constants listed in this reference are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". ## Import Patterns -Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is crucial for avoiding runtime errors. +Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is imperative for avoiding runtime errors. ### Named Exports (Import Required) @@ -52,6 +55,7 @@ const confirmDialog = addOnUISdk.constants.Variant.confirmation; ``` **Dual Access Constants List:** + - `Range` / `addOnUISdk.constants.Range` - `RenditionFormat` / `addOnUISdk.constants.RenditionFormat` - `RenditionType` / `addOnUISdk.constants.RenditionType` @@ -71,6 +75,13 @@ const confirmDialog = addOnUISdk.constants.Variant.confirmation; - `PlatformEnvironment` / `addOnUISdk.constants.PlatformEnvironment` - `DeviceClass` / `addOnUISdk.constants.DeviceClass` - `PlatformType` / `addOnUISdk.constants.PlatformType` +- `MediaType` / `addOnUISdk.constants.MediaType` +- `VideoResolution` / `addOnUISdk.constants.VideoResolution` +- `FrameRate` / `addOnUISdk.constants.FrameRate` +- `BitRate` / `addOnUISdk.constants.BitRate` +- `FileSizeLimitUnit` / `addOnUISdk.constants.FileSizeLimitUnit` +- `LinkOptions` / `addOnUISdk.constants.LinkOptions` + ### Best Practices @@ -111,12 +122,154 @@ const confirmDialog = addOnUISdk.constants.Variant.confirmation; | `PlatformEnvironment` | ✅ | ✅ | Optional | | `DeviceClass` | ✅ | ✅ | Optional | | `PlatformType` | ✅ | ✅ | Optional | +| `AuthorizationStatus` | ✅ | ✅ | Optional | +| `RenditionType` | ✅ | ✅ | Optional | +| `RenditionIntent` | ✅ | ✅ | Optional | +| `DialogResultType` | ✅ | ✅ | Optional | +| `RuntimeType` | ✅ | ✅ | Optional | +| `BleedUnit` | ✅ | ✅ | Optional | +| `EditorPanel` | ✅ | ✅ | Optional | +| `MediaTabs` | ✅ | ✅ | Optional | +| `ElementsTabs` | ✅ | ✅ | Optional | +| `PanelActionType` | ✅ | ✅ | Optional | +| `ColorPickerPlacement` | ✅ | ✅ | Optional | +| `MediaType` | ✅ | ✅ | Optional | +| `VideoResolution` | ✅ | ✅ | Optional | +| `FrameRate` | ✅ | ✅ | Optional | +| `BitRate` | ✅ | ✅ | Optional | +| `FileSizeLimitUnit` | ✅ | ✅ | Optional | +| `LinkOptions` | ✅ | ✅ | Optional | + **Important:** Attempting to access named-only exports through `addOnUISdk.constants.*` will return `undefined` and may cause runtime errors. Always check the table above or use TypeScript for compile-time validation. -## Constants +## Add-on UI SDK Constants + +### **Dialog & UI Interaction Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Variant`** | `confirmation`, `information`, `warning`, `destructive`, `error`, `input`, `custom` | Dialog types for `showModalDialog()` | `addOnUISdk.app.showModalDialog({variant: Variant.error})` | +| **`ButtonType`** | `primary`, `secondary`, `cancel`, `close` | Button types in dialog responses | Check `result.buttonType === ButtonType.primary` | +| **`FieldType`** | `text` | Input field types for input dialogs | Used in dialog field configuration | +| **`DialogResultType`** | `alert`, `input`, `custom` | Types of dialog results returned | Determine result structure type | + +### **Platform & Environment Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`PlatformType`** | `iOS`, `iPadOS`, `chromeOS`, `android`, `chromeBrowser`, `firefoxBrowser`, `edgeBrowser`, `samsungBrowser`, `safariBrowser`, `unknown` | Platform identification | `platform.platformType === PlatformType.iOS` | +| **`PlatformEnvironment`** | `app`, `web` | Runtime environment type | Check if running in app vs web | +| **`DeviceClass`** | `mobile`, `tablet`, `desktop` | Device category | Responsive design decisions | +| **`RuntimeType`** | `panel`, `dialog`, `documentSandbox`, `command` | Add-on runtime types | Communication API configuration | +| **`EntrypointType`** | `WIDGET`, `COMMAND`, `SCRIPT`, `PANEL`, `SHARE`, `CONTENT_HUB`, `MOBILE_MEDIA_AUDIO`, `MOBILE_YOUR_STUFF_FILES`, `MOBILE_MORE`, `SCHEDULE`, `CONTEXTUAL_REPLACE`, `CONTEXTUAL_UPLOAD`, `CONTEXTUAL_BULK_CREATE` | Types of add-on entrypoints | `addOn.entrypointType === EntrypointType.PANEL` | + +### **Document Export & Rendering Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Range`** | `currentPage`, `entireDocument`, `specificPages` | Rendition page ranges | `createRenditions({range: Range.currentPage})` | +| **`RenditionFormat`** | `png`, `jpg`, `mp4`, `pdf`, `pptx` | Export file formats | Specify output format for renditions | +| **`RenditionType`** | `page` | Type of rendition | Document export configuration | +| **`RenditionIntent`** | `export`, `preview`, `print` | Purpose of rendition | Optimize rendering for use case | + +### **Video & Media Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`VideoResolution`** | `sd480p`, `hd720p`, `fhd1080p`, `qhd1440p`, `uhd2160p`, `custom` | Video export resolutions | Video rendition quality settings | +| **`FrameRate`** | `fps23_976`, `fps24`, `fps25`, `fps29_97`, `fps30`, `fps60` | Video frame rates | Video export frame rate | +| **`BitRate`** | `mbps4`, `mbps8`, `mbps10`, `mbps12`, `mbps15`, `mbps25`, `mbps50` | Video bit rates in Mbps | Video compression settings | + +### **Print & Layout Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`BleedUnit`** | `Inch`, `Millimeter` | Print bleed units | Print preparation | + +### **Editor Panel Navigation Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`EditorPanel`** | `search`, `yourStuff`, `templates`, `media`, `text`, `elements`, `grids`, `brands`, `addOns` | Express editor panels | `openEditorPanel(EditorPanel.media)` | +| **`MediaTabs`** | `video`, `audio`, `photos` | Media panel tabs | Navigate to specific media type | +| **`ElementsTabs`** | `designAssets`, `backgrounds`, `shapes`, `stockIcons`, `charts` | Elements panel tabs | Navigate to specific element type | +| **`PanelActionType`** | `search`, `navigate` | Panel action types | Panel interaction configuration | + +### **Color Picker Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`ColorPickerPlacement`** | `top`, `bottom`, `left`, `right` | Color picker positioning | `showColorPicker(element, {placement: ColorPickerPlacement.top})` | + +### **Event Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`AppEvent`** | `documentIdAvailable`, `documentTitleChange`, `documentLinkAvailable`, `documentPublishedLinkAvailable`, `themechange`, `localechange`, `formatchange`, `reset`, `dragstart`, `dragend`, `dragcancel`, `documentExportAllowedChange` | Events dispatched by the Add-on SDK | `addOnUISdk.app.on(AppEvent.themechange, handler)` | +| **`ColorPickerEvent`** | `colorChange`, `close` | Custom events dispatched by the Color Picker | Listen for color picker events on anchor element | + +### **File Management Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`SupportedMimeTypes`** | `docx`, `gdoc` | MIME types for original source assets converted to PDF | File import validation | +| **`PdfReturnUrlType`** | `cdnUrl`, `jumpUrl` | Type of URL returned for PDF rendition export | PDF export configuration | +| **`MediaType`** | `Audio` | Media type identifiers | Media handling and processing | +| **`FileSizeLimitUnit`** | `KB`, `MB` | File size limit units | File upload constraints | +| **`LinkOptions`** | `document`, `published` | Types of document links available | `addOnUISdk.app.document.link(LinkOptions.published)` | + +### **OAuth & Authentication Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`AuthorizationStatus`** | `SUCCESS`, `POPUP_OPENED`, `POPUP_BLOCKED`, `POPUP_CLOSED`, `POPUP_TIMEOUT`, `FAILED`, `IFRAME_LOAD_FAILED` | OAuth authorization workflow status values | `result.status === AuthorizationStatus.SUCCESS` | + +### **Usage Examples** + +```typescript +import addOnUISdk, { + Variant, + PlatformType, + RenditionFormat, + EditorPanel +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Dialog with error variant +await addOnUISdk.app.showModalDialog({ + variant: Variant.error, + title: "Upload Failed", + description: "File size exceeds limit" +}); + +// Platform-specific behavior +const platform = await addOnUISdk.app.getCurrentPlatform(); +if (platform.platformType === PlatformType.iOS) { + // iOS-specific handling +} + +// Export document as PNG +await addOnUISdk.app.document.createRenditions({ + format: RenditionFormat.png, + range: Range.currentPage +}); + +// Navigate to media panel +addOnUISdk.app.ui.openEditorPanel(EditorPanel.media); +``` + +### **Notes for Developers** + +- **All constants are available as named exports** from the Add-on UI SDK +- **Use constants instead of string literals** for type safety and future compatibility +- **Some constants are marked as `@internal`** (like `ToastVariant`, `FeatureType`, `SettingType`) and are not included as they're for internal/privileged use only +- **Platform detection is crucial** for responsive add-on design across different devices and browsers + +These constants provide type-safe ways to interact with all the major Add-on UI SDK features that third-party developers commonly use! + + From 55bff9ef104fdee7a4d166c6965614ff5556925f Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 27 Aug 2025 16:25:26 -0400 Subject: [PATCH 3/8] Cleanup --- .../platform_concepts/runtime-architecture.md | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/pages/guides/learn/platform_concepts/runtime-architecture.md b/src/pages/guides/learn/platform_concepts/runtime-architecture.md index d6f8f808e..5344ed437 100644 --- a/src/pages/guides/learn/platform_concepts/runtime-architecture.md +++ b/src/pages/guides/learn/platform_concepts/runtime-architecture.md @@ -36,32 +36,30 @@ Adobe Express add-ons run in **two separate JavaScript execution environments** ## Architecture Diagram ```mermaid -graph TB - subgraph "Adobe Express Add-on Architecture" - - subgraph UI["UI Runtime Environment
(iframe)
"] - UICode["index.js/index.html
Your Add-on UI"] - UIRuntime["addOnUISdk.instance.runtime
Communication Bridge"] - UIFeatures["• User Interface
• Event Handling
• OAuth & Storage
• Modal Dialogs
"] - end - - subgraph Sandbox["Document Sandbox Environment
(Secure JavaScript Context)
"] - SandboxCode["code.js




Document Manipulation"] - SandboxRuntime["addOnSandboxSdk.instance.runtime
Communication Bridge"] - SandboxFeatures["• Document API
• Create Elements
• Modify Content
• Export Renditions
"] - end - - UIRuntime -.->|""| SandboxRuntime - SandboxRuntime -.->|"runtime.apiProxy('panel')
"| UIRuntime - - UIRuntime -->|"exposeApi()"| UICode - SandboxRuntime -->|"exposeApi()"| SandboxCode - end - - style UI fill:#ba68c8 - style Sandbox fill:#333333 - style UIRuntime fill:#000000 - style SandboxRuntime fill:##68c8ba +flowchart TB + subgraph UI["UI Runtime Environment
(iframe)"] + direction TB + UICode["index.js / index.html
Your Add-on UI"] + UIFeatures["• User Interface
• Event Handling
• OAuth & Storage
• Modal Dialogs"] + UIRuntime["addOnUISdk.instance.runtime
Communication Bridge"] + end + + subgraph Sandbox["Document Sandbox Environment
"] + direction TB + SandboxCode["code.js
Document Manipulation"] + SandboxFeatures["• Document API
• Create Elements
• Modify Content
• Export Renditions"] + SandboxRuntime["addOnSandboxSdk.instance.runtime
Communication Bridge"] + end + + UIRuntime -.->|" runtime.apiProxy('documentSandbox') "| SandboxRuntime + SandboxRuntime -.->|" runtime.apiProxy('panel') "| UIRuntime + UIRuntime -->|" exposeApi() "| UICode + SandboxRuntime -->|" exposeApi() "| SandboxCode + + style UI fill:#a772fc,stroke:#a455b6,color:#111 + style Sandbox fill:#564aff,stroke:#444444,color:#ffffff + style UIRuntime fill:#000,stroke:#222222,color:#ffffff + style SandboxRuntime fill:#68c8ba,stroke:#4baea2,color:#111 ``` ## What is the Runtime Object? From 58758f19b4a70629cc487aefaabb666c332ab7a5 Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 27 Aug 2025 17:24:59 -0400 Subject: [PATCH 4/8] Add addt'l WIP guides --- .../platform_concepts/communication-api.md | 193 ++++++++++++++ .../learn/platform_concepts/doc-sandbox.md | 79 ++++++ .../platform_concepts/runtime-architecture.md | 181 +++++++------ .../learn/platform_concepts/terminology.md | 149 +++++++++++ .../ui-sdk-vs-document-sdk.md | 240 ++++++++++++++++++ 5 files changed, 750 insertions(+), 92 deletions(-) create mode 100644 src/pages/guides/learn/platform_concepts/communication-api.md create mode 100644 src/pages/guides/learn/platform_concepts/doc-sandbox.md create mode 100644 src/pages/guides/learn/platform_concepts/terminology.md create mode 100644 src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md diff --git a/src/pages/guides/learn/platform_concepts/communication-api.md b/src/pages/guides/learn/platform_concepts/communication-api.md new file mode 100644 index 000000000..fd6d935b6 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/communication-api.md @@ -0,0 +1,193 @@ +# **Communication API Layer Overview** + +The Communication API layer enables **bidirectional communication** between the Document Sandbox and UI runtime environments. Here's a comprehensive breakdown: + +### **Core Communication APIs** + +#### **Required Import** +```typescript +import addOnSandboxSdk, { RuntimeType } from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; +``` + +### **1. `runtime.exposeApi(apiObject)` - Expose Document Functions to UI** + +**Purpose:** Makes Document Sandbox functions callable from the UI runtime + +**Requirements:** +- **Called once** per add-on lifecycle +- **No special flags needed** +- **Synchronous operation** + +**Usage Pattern:** +```typescript +// Document Sandbox (code.js) - Expose APIs TO the UI +const documentApis = { + createRectangle: () => { + const rect = editor.createRectangle(); + rect.width = 100; + rect.height = 100; + return "Rectangle created!"; + }, + + processImageData: (imageBlob) => { + // Process image in document context + return editor.addImageFromBlob(imageBlob); + }, + + getDocumentInfo: () => { + return { + pageCount: editor.context.document.pages.length, + selectedItems: editor.context.selection.length + }; + } +}; + +// Expose the APIs (called once) +runtime.exposeApi(documentApis); +``` + +**Key Features:** +- **Object methods** become remotely callable +- **Class instances** can be exposed +- **Return values** are automatically serialized +- **Async methods** work transparently +- **Only one API object** per runtime (calling again is no-op) + +### **2. `runtime.apiProxy(RuntimeType.panel)` - Access UI Functions from Document** + +**Purpose:** Gets access to functions exposed by the UI runtime + +**Requirements:** +- **Async operation** - requires `await` +- **Returns Promise-based proxy** +- **Must wait for UI runtime initialization** + +**Usage Pattern:** +```typescript +// Document Sandbox (code.js) - Access APIs FROM the UI +async function useUIApis() { + // Get UI API proxy (returns a Promise) + const uiApi = await runtime.apiProxy(RuntimeType.panel); + + // Call UI functions + await uiApi.showNotification("Document updated!"); + await uiApi.updateProgress(75); + + const userChoice = await uiApi.showConfirmDialog({ + title: "Confirm Action", + message: "Apply changes to all pages?" + }); + + if (userChoice) { + // Proceed with operation + } +} +``` + +### **Runtime Types** + +```typescript +export enum RuntimeType { + panel = "panel", // ✅ UI iframe runtime + documentSandbox = "documentSandbox", // ❌ Cannot proxy to self + script = "script" // ❌ Deprecated +} +``` + +**Restrictions:** +- **Cannot proxy to self**: `runtime.apiProxy(RuntimeType.documentSandbox)` throws error +- **Only `panel` is supported** for `apiProxy` calls from Document Sandbox + +### **Communication Flow Examples** + +#### **Example 1: UI Triggers Document Action** +```typescript +// UI Runtime (index.ts) +await documentApi.createRectangle(); // Calls function in Document Sandbox + +// Document Sandbox (code.ts) +function createRectangle() { + const rect = editor.createRectangle(); + // Document manipulation happens here + return "Success"; +} +``` + +#### **Example 2: Document Notifies UI of Changes** +```typescript +// Document Sandbox (code.ts) +async function onSelectionChange() { + const uiApi = await runtime.apiProxy(RuntimeType.panel); + await uiApi.updateSelectionInfo({ + count: editor.context.selection.length, + types: editor.context.selection.map(node => node.type) + }); +} + +// UI Runtime (index.ts) +function updateSelectionInfo(selectionData) { + // Update UI display + document.querySelector('#selection-count').textContent = selectionData.count; +} +``` + +### **Advanced Features** + +#### **Supported Data Types** +- **Primitives**: `string`, `number`, `boolean`, `null`, `undefined` +- **Objects**: Plain objects with serializable properties +- **Arrays**: Including nested arrays +- **Promises**: Async functions work transparently +- **Blobs**: Binary data transfer + +#### **Error Handling** +```typescript +try { + const uiApi = await runtime.apiProxy(RuntimeType.panel); + await uiApi.riskyOperation(); +} catch (error) { + console.error("UI operation failed:", error); + // Error is automatically propagated across runtime boundary +} +``` + +#### **Class Instance Exposure** +```typescript +class DocumentController { + createShape(type) { /* ... */ } + deleteShape(id) { /* ... */ } + getShapeInfo(id) { /* ... */ } +} + +const controller = new DocumentController(); +runtime.exposeApi(controller); // Entire class instance is exposed +``` + +### **Security & Performance Notes** + +1. **Automatic Serialization**: Data is automatically serialized/deserialized across runtime boundaries +2. **Type Validation**: Built-in validation prevents unsafe data transfer +3. **Promise-Based**: All cross-runtime calls are asynchronous for performance +4. **Single API Object**: Only one API object can be exposed per runtime (prevents conflicts) +5. **Error Propagation**: Errors are automatically marshaled across runtime boundaries + +### **Typical Communication Pattern** + +```typescript +// Document Sandbox Setup +const documentApis = { + // Functions UI can call + performDocumentOperation: async (params) => { /* ... */ } +}; +runtime.exposeApi(documentApis); + +// Get UI API access +const uiApi = await runtime.apiProxy(RuntimeType.panel); + +// Document can now: +// 1. Receive calls from UI via exposed APIs +// 2. Make calls to UI via proxy APIs +``` + +This creates a **bidirectional communication channel** where both runtimes can call each other's functions seamlessly. diff --git a/src/pages/guides/learn/platform_concepts/doc-sandbox.md b/src/pages/guides/learn/platform_concepts/doc-sandbox.md new file mode 100644 index 000000000..ffb5df031 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/doc-sandbox.md @@ -0,0 +1,79 @@ +# Document Sandbox: The Full Picture + +The Document Sandbox includes **three main components**: + +## 1. **Document APIs** + +- **Scenegraph manipulation**: `RectangleNode`, `TextNode`, `PageNode`, etc. +- **Editor operations**: `editor.createRectangle()`, `editor.queueAsyncEdit()` +- **Document structure**: Direct access to document elements and properties + +## 2. **Communication API Layer** + +- **`runtime.exposeApi()`**: Expose functions from Document Sandbox to UI iframe +- **`runtime.apiProxy()`**: Get proxy to call functions in UI iframe +- **Bidirectional communication**: Document Sandbox ↔ UI iframe communication + +## 3. **Global Browser API Access** (Limited Subset) + +- **Console**: `console.log()`, `console.error()`, etc. +- **Blob API**: `new Blob()`, `blob.arrayBuffer()`, `blob.text()` +- **Basic JavaScript globals**: But **not** full DOM access + +### **Example: Full Document Sandbox Usage** + +```typescript +// Document Sandbox code (runs in QuickJS/isolated environment) +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor, constants } from "express-document-sdk"; + +const { runtime } = addOnSandboxSdk.instance; + +// 1. DOCUMENT APIs - Direct scenegraph manipulation +function createLayout() { + const rect = editor.createRectangle(); + rect.width = 100; + rect.height = 50; + editor.context.insertionParent.children.append(rect); +} + +// 2. COMMUNICATION APIs - Expose functions to UI iframe +const sandboxApi = { + createLayout, + addCustomText: (text: string) => { + const textNode = editor.createText(text); + editor.context.insertionParent.children.append(textNode); + } +}; + +runtime.exposeApi(sandboxApi); + +// Get proxy to call UI iframe functions +const uiProxy = await runtime.apiProxy(RuntimeType.panel); +uiProxy.updateStatus("Layout created!"); + +// 3. GLOBAL BROWSER APIs - Limited subset available +console.log("Document sandbox initialized"); +const data = new Blob(["Hello"], { type: "text/plain" }); +const text = await data.text(); +``` + +### **Architecture: Two Runtime Communication** + +``` +┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐ +│ UI iframe │ │ Document Sandbox │ +│ (addOnUISdk) │ │ (Document Sandbox SDK) │ +├─────────────────────────────────────┤ ├─────────────────────────────────────┤ +│ • DOM access │ │ • Document APIs (editor, nodes) │ +│ • Full browser APIs │◄──►│ • Limited browser APIs (console, │ +│ • Event handling │ │ Blob) │ +│ • UI interactions │ │ • Communication APIs (runtime) │ +│ • addOnUISdk.app.document.addImage()│ │ • Direct scenegraph manipulation │ +└─────────────────────────────────────┘ └─────────────────────────────────────┘ + │ │ + │ runtime.apiProxy() │ + │ runtime.exposeApi() │ + └──────────────────────────────────────────────┘ + Communication Layer +``` diff --git a/src/pages/guides/learn/platform_concepts/runtime-architecture.md b/src/pages/guides/learn/platform_concepts/runtime-architecture.md index 5344ed437..3cd75b000 100644 --- a/src/pages/guides/learn/platform_concepts/runtime-architecture.md +++ b/src/pages/guides/learn/platform_concepts/runtime-architecture.md @@ -33,35 +33,27 @@ Adobe Express add-ons run in **two separate JavaScript execution environments** 1. **UI Runtime** - Your add-on's user interface (iframe) 2. **Document Sandbox** - Secure environment for document manipulation -## Architecture Diagram - -```mermaid -flowchart TB - subgraph UI["UI Runtime Environment
(iframe)"] - direction TB - UICode["index.js / index.html
Your Add-on UI"] - UIFeatures["• User Interface
• Event Handling
• OAuth & Storage
• Modal Dialogs"] - UIRuntime["addOnUISdk.instance.runtime
Communication Bridge"] - end - - subgraph Sandbox["Document Sandbox Environment
"] - direction TB - SandboxCode["code.js
Document Manipulation"] - SandboxFeatures["• Document API
• Create Elements
• Modify Content
• Export Renditions"] - SandboxRuntime["addOnSandboxSdk.instance.runtime
Communication Bridge"] - end - - UIRuntime -.->|" runtime.apiProxy('documentSandbox') "| SandboxRuntime - SandboxRuntime -.->|" runtime.apiProxy('panel') "| UIRuntime - UIRuntime -->|" exposeApi() "| UICode - SandboxRuntime -->|" exposeApi() "| SandboxCode - - style UI fill:#a772fc,stroke:#a455b6,color:#111 - style Sandbox fill:#564aff,stroke:#444444,color:#ffffff - style UIRuntime fill:#000,stroke:#222222,color:#ffffff - style SandboxRuntime fill:#68c8ba,stroke:#4baea2,color:#111 -``` +## Architecture Diagrams + +### **Architecture: Two Runtime Communication** +``` +┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐ +│ UI iframe │ │ Document Sandbox │ +│ (Add-on UI SDK) │ │ (Document Sandbox SDK) │ +├─────────────────────────────────────┤ ├─────────────────────────────────────┤ +│ • DOM access │ │ • Document APIs (editor, nodes) │ +│ • Full browser APIs │◄──►│ • Limited browser APIs (console, │ +│ • Event handling │ │ Blob) │ +│ • UI interactions │ │ • Communication APIs (runtime) │ +│ • addOnUISdk.app.document.addImage()│ │ • Direct scenegraph manipulation │ +└─────────────────────────────────────┘ └─────────────────────────────────────┘ + │ │ + │ runtime.apiProxy() │ + │ runtime.exposeApi() │ + └──────────────────────────────────────────────┘ + Communication Layer +``` ## What is the Runtime Object? The `runtime` object acts as a **communication bridge** between the two environments. Each environment has its own runtime object: @@ -83,6 +75,7 @@ import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; ``` **Capabilities:** + - Render your add-on's user interface - Handle user interactions (clicks, form inputs) - Access browser APIs (fetch, localStorage) @@ -192,63 +185,9 @@ addOnUISdk.ready.then(() => { }); ``` -## Frequently Asked Questions - -### Q: Why are there two different runtime objects? - -**A:** Each environment has its own runtime object that acts as a "communication phone." The UI runtime calls the document sandbox, and the document sandbox runtime calls the UI. This separation ensures security and proper isolation. - -### Q: When do I use `addOnUISdk.instance.runtime` vs `addOnSandboxSdk.instance.runtime`? - -**A:** Use the runtime object from the environment you're currently in: -- In `index.js` (UI): Use `addOnUISdk.instance.runtime` -- In `code.js` (Sandbox): Use `addOnSandboxSdk.instance.runtime` - -### Q: What does `await runtime.apiProxy("documentSandbox")` actually do? - -**A:** It creates a proxy object that lets you call functions you've exposed in the document sandbox from your UI code. Think of it as getting a "remote control" for the other environment. - -### Q: What's the difference between `"documentSandbox"` and `"panel"` in apiProxy? - -**A:** These specify which environment you want to communicate with: -- `"documentSandbox"` - Call from UI to document sandbox -- `"panel"` - Call from document sandbox to UI - -### Q: Can I access document APIs directly from the UI? - -**A:** No, document APIs are only available in the document sandbox for security reasons. You must use the communication system to bridge between environments. - -### Q: How do I know which environment my code is running in? - -**A:** Check your file structure and imports: -- If you're importing `addOnUISdk` → You're in the UI runtime -- If you're importing `addOnSandboxSdk` → You're in the document sandbox - -### Q: Do I always need both imports in my code.js file? - -**A:** No! It depends on what your add-on does: - -- **Communication only**: Just `addOnSandboxSdk` (no document changes) -- **Document only**: Just `express-document-sdk` (no UI communication) -- **Both**: Most add-ons need both for UI-triggered document operations - -### Q: Why do some examples show one import and others show both? - -**A:** Different add-on types have different needs: -- Simple document manipulation → One import -- UI-controlled document changes → Both imports -- The example context determines which imports are shown +## Understanding the Document Sandbox SDK Imports -### Q: What else is available in the add-on-sdk-document-sandbox package? - -**A:** Besides `runtime`, the sandbox SDK provides: -- **Web APIs**: Limited browser APIs like `console` for debugging -- **Automatic global injections**: No need to import basic APIs -- **Secure execution environment**: Isolated JavaScript context - -## Understanding the Two SDK Imports - -### When do I need both SDK imports in code.js? +### When do I need both SDK imports in `code.js`? This is a common source of confusion. In your `code.js` file, you may need one or both imports depending on what your add-on does: @@ -302,18 +241,21 @@ runtime.exposeApi({ ### Quick Decision Guide **Do I need `addOnSandboxSdk`?** -- ✅ YES if your code.js needs to communicate with the UI + +- ✅ YES if your `code.js` needs to communicate with the UI - ✅ YES if UI triggers document operations -- ❌ NO if code.js runs independently +- ❌ NO if `code.js` runs independently **Do I need `express-document-sdk`?** + - ✅ YES if creating/modifying document content - ✅ YES if accessing document properties - ❌ NO if only processing data or communicating -### Real-World Examples +### Examples #### Example 1: Text Generator Add-on + ```js // sandbox/code.js - Needs BOTH imports import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // UI sends text data @@ -332,6 +274,7 @@ runtime.exposeApi({ ``` #### Example 2: Document Analytics Add-on + ```js // sandbox/code.js - Needs BOTH imports import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // Send results to UI @@ -348,6 +291,7 @@ runtime.exposeApi({ ``` #### Example 3: Document Analysis Utility + ```js // sandbox/code.js - Only needs express-document-sdk import { editor } from "express-document-sdk"; @@ -572,7 +516,7 @@ The sandbox requires proper manifest setup: | File system access | ✅ | ❌ | - | | Network requests | ✅ | ❌* | - | -*Can be proxied through UI runtime +* Can be proxied through UI runtime ## Best Practices @@ -599,16 +543,69 @@ The sandbox requires proper manifest setup: - Use sandbox console for document sandbox debugging - Use `console.assert()` for validation checks +## Frequently Asked Questions + +### Q: Why are there two different runtime objects? + +**A:** Each environment has its own runtime object that acts as a "communication phone." The UI runtime calls the document sandbox, and the document sandbox runtime calls the UI. This separation ensures security and proper isolation. + +### Q: When do I use `addOnUISdk.instance.runtime` vs `addOnSandboxSdk.instance.runtime`? + +**A:** Use the runtime object from the environment you're currently in: +- In `index.js` (UI): Use `addOnUISdk.instance.runtime` +- In `code.js` (Sandbox): Use `addOnSandboxSdk.instance.runtime` + +### Q: What does `await runtime.apiProxy("documentSandbox")` actually do? + +**A:** It creates a proxy object that lets you call functions you've exposed in the document sandbox from your UI code. Think of it as getting a "remote control" for the other environment. + +### Q: What's the difference between `"documentSandbox"` and `"panel"` in apiProxy? + +**A:** These specify which environment you want to communicate with: +- `"documentSandbox"` - Call from UI to document sandbox +- `"panel"` - Call from document sandbox to UI + +### Q: Can I access document APIs directly from the UI? + +**A:** No, document APIs are only available in the document sandbox for security reasons. You must use the communication system to bridge between environments. + +### Q: How do I know which environment my code is running in? + +**A:** Check your file structure and imports: +- If you're importing `addOnUISdk` → You're in the UI runtime +- If you're importing `addOnSandboxSdk` → You're in the document sandbox + +### Q: Do I always need both imports in my code.js file? + +**A:** No! It depends on what your add-on does: + +- **Communication only**: Just `addOnSandboxSdk` (no document changes) +- **Document only**: Just `express-document-sdk` (no UI communication) +- **Both**: Most add-ons need both for UI-triggered document operations + +### Q: Why do some examples show one import and others show both? + +**A:** Different add-on types have different needs: +- Simple document manipulation → One import +- UI-controlled document changes → Both imports +- The example context determines which imports are shown + +### Q: What else is available in the add-on-sdk-document-sandbox package? + +**A:** Besides `runtime`, the sandbox SDK provides: +- **Web APIs**: Limited browser APIs like `console` for debugging +- **Automatic global injections**: No need to import basic APIs +- **Secure execution environment**: Isolated JavaScript context + ## Related Topics - [Communication APIs Reference](../../../references/document-sandbox/communication/index.md) - [UI SDK Reference](../../../references/addonsdk/index.md) - [Document API Concepts](./document-api.md) -- [Modal Dialogs Tutorial](../how_to/modal_dialogs.md) ## Next Steps -Now that you understand the architecture, explore these tutorials: +Now that you understand the architecture, explore these guides: + +- [Document API deep dive](./document-api.md): Learn how to use the Document API to create and modify document content. - [Building your first add-on](../how_to/tutorials/grids-addon.md) -- [Using Communication APIs](../how_to/modal_dialogs.md) -- [Document API deep dive](./document-api.md) diff --git a/src/pages/guides/learn/platform_concepts/terminology.md b/src/pages/guides/learn/platform_concepts/terminology.md new file mode 100644 index 000000000..718dc9086 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/terminology.md @@ -0,0 +1,149 @@ +# Terminology + +## **Terminology Summary: Add-on UI SDK vs Document SDK vs Document APIs** + +### **1. Add-on UI SDK** +**Developer Import:** `"https://new.express.adobe.com/static/add-on-sdk/sdk.js"` + +**What it is:** The UI runtime environment for add-ons +- **Purpose:** Application interaction, UI management, document-level operations +- **Runtime:** Iframe-based UI environment +- **API Style:** Method-based, Promise-driven +- **Example Usage:** `addOnUISdk.app.document.addImage()`, `addOnUISdk.app.showModalDialog()` + +### **2. Document SDK** +**Developer Import:** `"express-document-sdk"` + +**What it is:** The complete document manipulation environment +- **Purpose:** Direct document content creation and manipulation +- **Runtime:** Document sandbox environment +- **API Style:** Object-oriented scenegraph manipulation +- **Example Usage:** `editor.createRectangle()`, `new Color()`, `textNode.text = "Hello"` + +### **3. Document APIs** +**Technical Clarification:** This is a **subset** of the Document SDK + +**What it includes:** Just the core document manipulation APIs from `"express-document-sdk"` +- **Core APIs:** `editor`, `colorUtils`, `fonts`, `viewport` +- **Node Classes:** `RectangleNode`, `TextNode`, `GroupNode`, etc. +- **Utilities:** `Color`, `constants`, geometry classes + +## **Key Distinction for Documentation** + +| Term | Scope | Developer Perspective | +|------|-------|---------------------| +| **Add-on UI SDK** | Complete UI runtime | `import addOnUISdk from "sdk.js"` | +| **Document SDK** | Complete document environment | `import {...} from "express-document-sdk"` | +| **Document APIs** | Core document manipulation only | Subset of Document SDK imports | + +## **Recommended Usage in Documentation** + +- **Use "Add-on UI SDK"** when referring to UI interactions, dialogs, app-level operations +- **Use "Document SDK"** when referring to the complete document manipulation environment +- **Use "Document APIs"** when specifically discussing just the core manipulation methods (not the full environment) + +## Add-on UI SDK vs Document API + +The **Add-on UI SDK** provides methods for application-level operations: +- `addOnUISdk.app.document.addImage(blob)` +- `addOnUISdk.app.showModalDialog(options)` + +The **Document API** provides direct scenegraph manipulation: +- `editor.createRectangle()` +- `rectangle.width = 100` + +## **Document SDK APIs (aka Document APIs) vs Add-on UI SDK** + +### **Document SDK** +**What developers import:** `"express-document-sdk"` + +```typescript +// Document SDK imports - for document manipulation +import { colorUtils, constants, editor, fonts, viewport } from "express-document-sdk"; +import type { + Color, GroupNode, RectangleNode, TextNode, + BitmapImage, MediaContainerNode +} from "express-document-sdk"; +``` + +**What it provides:** + +- **Document manipulation APIs**: `editor.createRectangle()`, `editor.createText()` +- **Node classes**: `RectangleNode`, `TextNode`, `GroupNode`, etc. +- **Utilities**: `colorUtils`, `fonts`, `viewport` +- **Constants**: Document-related enums and constants + +### **Add-on UI SDK** + +**What developers import:** `"https://new.express.adobe.com/static/add-on-sdk/sdk.js"` + +```typescript +// Add-on UI SDK import - for UI and app interaction +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; +``` + +**What it provides:** + +- **Application APIs**: `addOnUISdk.app.document.addImage()` +- **UI APIs**: `addOnUISdk.app.showModalDialog()` +- **Event handling**: `addOnUISdk.app.on("themechange")` + +### **Document Sandbox Runtime** +**What developers import:** `"add-on-sdk-document-sandbox"` + +```typescript +// Document Sandbox Runtime - for communication between UI and Document +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const runtime = addOnSandboxSdk.instance.runtime; +``` + +**What it provides:** + +- **Communication APIs**: `runtime.exposeApi()`, `runtime.apiProxy()` +- **Runtime management**: Handles UI ↔ Document communication + +## Imports & Purpose + +| Term | Developer Import | Purpose | +|------|------------------|---------| +| **Document SDK** | `"express-document-sdk"` | Direct document manipulation | +| **Add-on UI SDK** | `"sdk.js"` | UI and application interaction | +| **Document Sandbox Runtime** | `"add-on-sdk-document-sandbox"` | Communication layer | + + + + + + + + + +## Add-on Development SDKs + +- **Add-on UI SDK** vs **Document API** +- **UI SDK** vs **Document API** +- **UI Runtime APIs** vs **Document Sandbox APIs** +- **Iframe Runtime** vs **Document Sandbox Runtime** + +| Aspect | **Add-on UI SDK** | **Document API** | +|--------|-------------------|------------------| +| **Runtime Environment** | UI iframe (full browser) | Document Sandbox (QuickJS) | +| **Primary Purpose** | UI interactions, app-level operations | Direct document manipulation | +| **API Style** | Method-based, async | Object-oriented, mostly sync | +| **What You Get** | `addOnUISdk.app.document.addImage()` | `editor.createRectangle()` | + +## Add-on UI SDK vs Document API + +The **Add-on UI SDK** provides methods for application-level operations: + +```js +- `addOnUISdk.app.document.addImage(blob)` +- `addOnUISdk.app.showModalDialog(options)` +``` + +The **Document API** provides direct scenegraph manipulation: + +```js +- `editor.createRectangle()` +- `rectangle.width = 100` +``` diff --git a/src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md b/src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md new file mode 100644 index 000000000..fdcb316ae --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md @@ -0,0 +1,240 @@ +# Add-on UI SDK APIs vs Document Sandbox SDK APIs + +The Add-on UI SDK APIs and Document APIs in the Document Sandbox SDK provide two different ways to interact with the Adobe Express platform from your add-on, each designed for distinct use cases and interaction patterns. + + + +This guide covers the differences between the UI SDK and the Document API specifically. For more information about the other features available from the Document Sandbox (ie: Communication and Browser APIs), see the [Runtime Architecture](runtime-architecture.md) guide. + +## Overview + +- **UI SDK (addOnUISdk) APIs**: Method-based API for performing actions on documents from your add-on's UI panel +- **Document APIs**: Object-oriented API for creating and manipulating document elements in the document sandbox + +## Key Architectural Differences + +| Aspect | UI SDK (addOnUISdk) | Document API | +|--------|---------------------|--------------| +| **Architecture** | Method-based interfaces | Object-oriented classes | +| **Usage Pattern** | `await document.addImage(blob)` | `rectangle.width = 100` | +| **Purpose** | Perform actions on existing documents | Create and manipulate document elements | +| **Execution Context** | UI iframe | Document sandbox | +| **API Style** | Event-driven, async method calls | Direct property manipulation | +| **Content Control** | Add content to existing document | Create and modify individual elements | + +## Fundamental Differences + +### 1. UI SDK: Remote Control Pattern + +The UI SDK acts like a **remote control** for the document - you send commands to Express: + +```typescript +// Method-based: Call methods to perform actions +await addOnUISdk.app.document.addImage(blob); +await addOnUISdk.app.document.addVideo(blob); +await addOnUISdk.app.showModalDialog(dialogOptions); + +const title = await addOnUISdk.app.document.title(); +const renditions = await addOnUISdk.app.document.createRenditions(options); +``` + +**Mental Model:** *"Hey Express, add this image to the document"* + +### 2. Document API: Building Blocks Pattern + +The Document API gives you **direct control** over document elements like building blocks: + +```typescript +// Object-oriented: Create objects and set their properties +const rectangle = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 50; +rectangle.fill = solidColor; + +const text = editor.createText("Hello World"); +text.translation = { x: 10, y: 20 }; + +// Add objects to the document +editor.context.insertionParent.children.append(rectangle); +``` + +**Mental Model:** *"Create a rectangle, make it red, put it here"* + +## Use Case Comparison + +| Task | UI SDK (addOnUISdk) | Document API | +|------|---------------------|--------------| +| **Add content** | `await document.addImage(blob)` | `const img = editor.createImageContainer(bitmap)`
`parent.children.append(img)` | +| **Set properties** | ❌ *Can't modify after adding* | `rectangle.width = 100`
`rectangle.height = 50` | +| **Get document info** | `await document.title()` | `const title = document.title` | +| **Apply styling** | ❌ *No direct styling* | `rectangle.fill = redColor`
`text.textRuns[0].fontSize = 24` | +| **Create layouts** | ❌ *Limited to adding content* | ✅ *Full layout control* | +| **Export/Share** | ✅ *Built-in export methods* | ❌ *No export capabilities* | + +## Content Control Approaches + +### UI SDK: Works with Existing Documents +The UI SDK **adds content TO** existing documents but doesn't provide direct control over individual elements: + +```typescript +// Adding content to whatever document is currently open +await addOnUISdk.app.document.addImage(blob); // Adds to current page +await addOnUISdk.app.document.addVideo(blob); // Adds to current page + +// Working with the document as a whole +const metadata = await addOnUISdk.app.document.getPagesMetadata(); +const title = await addOnUISdk.app.document.title(); +const renditions = await addOnUISdk.app.document.createRenditions(options); +``` + +**Key limitation:** You can add content, but you can't directly manipulate individual shapes, text, or existing elements. + +### Document API: Creates and Manipulates Elements +The Document API lets you **create new elements** and **directly control** the document structure: + +```typescript +// Creating new document elements from scratch +const rectangle = editor.createRectangle(); +const ellipse = editor.createEllipse(); +const text = editor.createText("Hello"); + +// Modifying properties of individual elements +rectangle.width = 200; +rectangle.topLeftRadius = 10; +rectangle.fill = { type: "solid", color: { red: 1, green: 0, blue: 0 } }; + +// Controlling document structure directly +const artboard = editor.context.insertionParent; +artboard.children.append(rectangle); +artboard.children.append(text); + +// Accessing and modifying existing elements +const existingNodes = artboard.children; +existingNodes.forEach(node => { + if (node instanceof RectangleNode) { + node.width *= 2; // Double the width of all rectangles + } +}); +``` + +## Event Handling + +### UI SDK: Application-Level Events + +The UI SDK provides **application and document-level events** for monitoring broad system changes: + +```typescript +// React to application-wide changes +addOnUISdk.app.on("themechange", (data) => { + console.log("App theme changed:", data.theme); +}); + +addOnUISdk.app.on("documentLinkAvailable", (data) => { + console.log("Document link available:", data.documentLink); +}); + +addOnUISdk.app.on("documentTitleChange", (data) => { + console.log("Document title changed:", data.documentTitle); +}); +``` + +### Document API: Editor-Level Events + +The Document API provides **editor and selection-level events** for monitoring editing interactions: + +```typescript +// React to user editing actions +const handlerId = editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + console.log("Selection changed, now selected:", selection.length, "items"); + + // Inspect and modify selected elements + selection.forEach(node => { + if (node instanceof RectangleNode) { + node.fill = redColor; // Change selected rectangle color + } + }); +}); + +// Unregister event when done +editor.context.off("selectionChange", handlerId); +``` + +## Synchronous vs Asynchronous Patterns + +### UI SDK: Everything is Async +The UI SDK treats **all operations** as asynchronous because it communicates across process boundaries (iframe ↔ host app): + +```typescript +// ALL operations return Promises - even simple getters +const title = await addOnUISdk.app.document.title(); +const id = await addOnUISdk.app.document.id(); +const allowed = await addOnUISdk.app.document.exportAllowed(); + +// Actions are async +await addOnUISdk.app.document.addImage(blob); +await addOnUISdk.app.document.addVideo(blob); + +// Metadata retrieval is async +const pages = await addOnUISdk.app.document.getPagesMetadata(options); +const renditions = await addOnUISdk.app.document.createRenditions(options); +``` + +**Why async?** Every call goes through RPC (Remote Procedure Call) to the host application. + +### Document API: Mostly Sync with Strategic Async +The Document API uses **synchronous property access** for immediate operations and async only when necessary: + +```typescript +// Property access is synchronous - immediate response +const rectangle = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 50; +const currentWidth = rectangle.width; + +// Most operations are immediate +rectangle.fill = { type: "solid", color: redColor }; +text.translation = { x: 10, y: 20 }; +artboard.children.append(rectangle); + +// Async ONLY for resource loading +const bitmap = await editor.loadBitmapImage(blob); + +// Async edit queuing after resource loading +await editor.queueAsyncEdit(() => { + const container = editor.createImageContainer(bitmap); + editor.context.insertionParent.children.append(container); +}); +``` + +**Why mostly sync?** Direct object access within the same process, async only for resource loading and edit scheduling. + +## Summary + +| Aspect | UI SDK (addOnUISdk) | Document API | +|--------|---------------------|--------------| +| **Purpose** | Import/export, add media, get metadata | Create layouts, modify shapes, build designs | +| **Architecture** | Method-based interfaces | Object-oriented classes | +| **Control Level** | Add content to existing documents | Create and modify individual elements | +| **Execution Context** | UI iframe (cross-process) | Document sandbox (same-process) | +| **Async Pattern** | Everything is async (RPC calls) | Mostly sync, async for resource loading | +| **Event Scope** | Application-wide changes | Editor/selection changes | +| **Best For** | Content import, document export, UI interactions | Layout creation, element manipulation, design tools | + +### When to Use Each + +**Use UI SDK when you need to:** +- Import images, videos, or audio into documents +- Export documents in various formats (PNG, PDF, PPTX, etc.) +- Get document metadata and information +- Show modal dialogs and UI interactions +- Work with existing document content + +**Use Document API when you need to:** +- Create new visual elements (rectangles, text, images) +- Build complex layouts and designs +- Modify properties of individual elements +- Respond to user selection changes +- Create design tools and content generators + +The UI SDK is like a **remote control** for Express documents, while the Document API provides **direct building blocks** for creating and manipulating visual content. \ No newline at end of file From fd10b549a6cc6985880d67cd889609b853cf6b9c Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 27 Aug 2025 18:17:05 -0400 Subject: [PATCH 5/8] Reorder sections --- .../platform_concepts/runtime-architecture.md | 190 +++++++++++------- 1 file changed, 118 insertions(+), 72 deletions(-) diff --git a/src/pages/guides/learn/platform_concepts/runtime-architecture.md b/src/pages/guides/learn/platform_concepts/runtime-architecture.md index 3cd75b000..c66e33f20 100644 --- a/src/pages/guides/learn/platform_concepts/runtime-architecture.md +++ b/src/pages/guides/learn/platform_concepts/runtime-architecture.md @@ -1,11 +1,9 @@ --- keywords: - - Adobe Express - - Express Add-on SDK - - Express Editor - Adobe Express - Add-on SDK - - SDK + - Adobe Express Editor + - SDK (Software Development Kit) - JavaScript - Extend - Extensibility @@ -13,6 +11,8 @@ keywords: - Runtime - Communication - Document Sandbox + - Document Sandbox SDK + - Document API - UI Runtime - Architecture - Two-Runtime System @@ -61,7 +61,7 @@ The `runtime` object acts as a **communication bridge** between the two environm - **UI Runtime**: `addOnUISdk.instance.runtime` - **Document Sandbox**: `addOnSandboxSdk.instance.runtime` -Think of it as a "phone line" - each side has their own phone to call the other side. +The communication bridge can be thought of like a phone line - each side has their own phone to call the other side. ## The Two Environments Explained @@ -93,6 +93,7 @@ import addOnSandboxSdk from "add-on-sdk-document-sandbox"; ``` **Capabilities:** + - Access Adobe Express Document API - Create and modify document elements - Export renditions @@ -106,6 +107,7 @@ import addOnSandboxSdk from "add-on-sdk-document-sandbox"; When you want to manipulate the document from your UI: #### UI Runtime (index.js) + ```js // ui/index.js - Your add-on's interface import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; @@ -125,6 +127,7 @@ addOnUISdk.ready.then(async () => { ``` #### Document Sandbox (code.js) + ```js // sandbox/code.js - Document manipulation import addOnSandboxSdk from "add-on-sdk-document-sandbox"; @@ -185,6 +188,31 @@ addOnUISdk.ready.then(() => { }); ``` +### Runtime Type Detection + +#### UI Runtime (index.js) +```js +// ui/index.js - Your add-on's interface +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; + // Check what type of runtime you're in + console.log(runtime.type); // "panel" +}); +``` + +#### Document Sandbox (code.js) + +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Check what type of runtime you're in +console.log(runtime.type); // "documentSandbox" +``` + ## Understanding the Document Sandbox SDK Imports ### When do I need both SDK imports in `code.js`? @@ -341,38 +369,6 @@ Document analysis or operations that run without UI interaction: // Only requires express-document-sdk ``` -## Error Handling - -### Common Error: "Runtime is undefined" - -❌ **Wrong:** Trying to use runtime before SDK is ready -```js -const { runtime } = addOnUISdk.instance; // Error! SDK not ready yet -``` - -✅ **Correct:** Wait for SDK ready state -```js -addOnUISdk.ready.then(() => { - const { runtime } = addOnUISdk.instance; // Now it's safe -}); -``` - -### Common Error: "Cannot read property 'apiProxy' of undefined" - -❌ **Wrong:** Using wrong SDK in wrong environment -```js -// In code.js (Document Sandbox) -import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; -const { runtime } = addOnUISdk.instance; // Wrong SDK! -``` - -✅ **Correct:** Use appropriate SDK for each environment -```js -// In code.js (Document Sandbox) -import addOnSandboxSdk from "add-on-sdk-document-sandbox"; -const { runtime } = addOnSandboxSdk.instance; // Correct SDK! -``` - ## Complete add-on-sdk-document-sandbox Package Overview The `add-on-sdk-document-sandbox` package provides more than just the `runtime` object. Here's what you get: @@ -441,28 +437,6 @@ const { runtime } = addOnSandboxSdk.instance; import { editor, colorUtils, constants, fonts } from "express-document-sdk"; ``` -### Advanced Runtime Features - -#### Runtime Type Detection -```js -import addOnSandboxSdk from "add-on-sdk-document-sandbox"; - -const { runtime } = addOnSandboxSdk.instance; - -// Check what type of runtime you're in -console.log(runtime.type); // "documentSandbox" -``` - -#### Environment Information -```js -// The SDK provides context about the execution environment -// This helps with debugging and conditional logic -if (runtime.type === "documentSandbox") { - // You're in the document sandbox - console.log("Running in secure document context"); -} -``` - ### Debugging Capabilities Since the document sandbox has limited debugging, use these patterns: @@ -487,6 +461,26 @@ try { console.assert(myVariable !== undefined, "Variable should be defined"); ``` +### Quick Reference: All SDK Imports + +| Feature | UI Runtime
`addOnUISdk` | Document Sandbox
`addOnSandboxSdk` | Document Sandbox
`express-document-sdk` | +|---------|------------|------------------|-----------------| +| **Import Statement** | `import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"` | `import addOnSandboxSdk from "add-on-sdk-document-sandbox"` | `import { editor } from "express-document-sdk"` | +| **File Location** | `index.js/index.html` | `code.js` | `code.js` | +| **Primary Purpose** | User interface & browser interactions | Communication between environments | Document manipulation | +| **Runtime Communication** | ✅ `instance.runtime` | ✅ `instance.runtime` | ❌ No communication | +| **Document Creation** | ❌ No direct access | ❌ No direct access | ✅ `editor.createText()`, etc. | +| **Document Reading** | ❌ No direct access | ❌ No direct access | ✅ `editor.context` | +| **Export Renditions** | ✅ `app.document.createRenditions()` | ❌ No direct access | ❌ No direct access | +| **Modal Dialogs** | ✅ `app.showModalDialog()` | ❌ No direct access | ❌ No direct access | +| **OAuth** | ✅ `app.oauth` | ❌ No direct access | ❌ No direct access | +| **File Upload/Download** | ✅ Full browser APIs | ❌ No direct access | ❌ No direct access | +| **DOM Manipulation** | ✅ Full DOM access | ❌ No DOM access | ❌ No DOM access | +| **Browser APIs** | ✅ All browser APIs | ❌ Limited (console, Blob) | ❌ Limited (console, Blob) | +| **Platform Detection** | ✅ `app.getCurrentPlatform()` | ❌ No direct access | ❌ No direct access | +| **Client Storage** | ✅ `instance.clientStorage` | ❌ No direct access | ❌ No direct access | +| **Constants** | ✅ `constants.*` | ❌ No constants | ✅ Document constants | + ### Manifest Configuration The sandbox requires proper manifest setup: @@ -504,19 +498,38 @@ The sandbox requires proper manifest setup: } ``` -### Quick Reference: What's Available Where +### SDK Import Decision Matrix -| Feature | UI Runtime | Document Sandbox | Import Required | -|---------|------------|------------------|-----------------| -| Full browser APIs | ✅ | ❌ | - | -| Console logging | ✅ | ✅ | ❌ (Auto-injected) | -| Document APIs | ❌ | ✅ | `express-document-sdk` | -| Communication | ✅ | ✅ | Both SDKs | -| DOM manipulation | ✅ | ❌ | - | -| File system access | ✅ | ❌ | - | -| Network requests | ✅ | ❌* | - | +| Your Add-on Needs | UI Runtime | Document Sandbox | Required Imports | +|-------------------|------------|------------------|------------------| +| **UI only** (no document changes) | ✅ | ❌ | `addOnUISdk` | +| **Document manipulation only** | ❌ | ✅ | `express-document-sdk` | +| **UI + Document communication** | ✅ | ✅ | `addOnUISdk` + `addOnSandboxSdk` | +| **UI + Document creation** | ✅ | ✅ | `addOnUISdk` + `addOnSandboxSdk` + `express-document-sdk` | +| **Data processing only** | ❌ | ✅ | `addOnSandboxSdk` (for communication) | +| **Export/Import only** | ✅ | ❌ | `addOnUISdk` | + +### Common Import Patterns + +```js +// Pattern 1: UI-only add-on (no document changes) +// index.js +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; +// No code.js file needed + +// Pattern 2: Document manipulation only +// code.js +import { editor } from "express-document-sdk"; +// No index.js communication needed + +// Pattern 3: Full communication + document manipulation (most common) +// index.js +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; -* Can be proxied through UI runtime +// code.js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // For communication +import { editor } from "express-document-sdk"; // For document APIs +``` ## Best Practices @@ -543,6 +556,38 @@ The sandbox requires proper manifest setup: - Use sandbox console for document sandbox debugging - Use `console.assert()` for validation checks +## Error Handling + +### Common Error: "Runtime is undefined" + +❌ **Wrong:** Trying to use runtime before SDK is ready +```js +const { runtime } = addOnUISdk.instance; // Error! SDK not ready yet +``` + +✅ **Correct:** Wait for SDK ready state +```js +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; // Now it's safe +}); +``` + +### Common Error: "Cannot read property 'apiProxy' of undefined" + +❌ **Wrong:** Using wrong SDK in wrong environment +```js +// In code.js (Document Sandbox) +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; +const { runtime } = addOnUISdk.instance; // Wrong SDK! +``` + +✅ **Correct:** Use appropriate SDK for each environment +```js +// In code.js (Document Sandbox) +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; // Correct SDK! +``` + ## Frequently Asked Questions ### Q: Why are there two different runtime objects? @@ -605,7 +650,8 @@ The sandbox requires proper manifest setup: ## Next Steps -Now that you understand the architecture, explore these guides: +Now that you understand the architecture, explore these guides and tutorials: - [Document API deep dive](./document-api.md): Learn how to use the Document API to create and modify document content. -- [Building your first add-on](../how_to/tutorials/grids-addon.md) +- [Building your first add-on](../how_to/tutorials/grids-addon.md): Use the Document API to create a simple add-on that adds a grid to the document. +- [Using the Communication APIs](../how_to/tutorials/stats-addon.md): Build an add-on to gather statistics on the active document using the Communication APIs. From 259e0ed4be4adf0cb5397e0a321c60ca11a04191 Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 27 Aug 2025 18:37:38 -0400 Subject: [PATCH 6/8] Added import patterns --- .../platform_concepts/constants-guide.md | 169 ++++++++++++++++-- 1 file changed, 154 insertions(+), 15 deletions(-) diff --git a/src/pages/guides/learn/platform_concepts/constants-guide.md b/src/pages/guides/learn/platform_concepts/constants-guide.md index 07ba4609b..0c3d415c4 100644 --- a/src/pages/guides/learn/platform_concepts/constants-guide.md +++ b/src/pages/guides/learn/platform_concepts/constants-guide.md @@ -1,6 +1,17 @@ -## Add-on UI SDK Constants +# Add-on UI SDK Constants -### **Dialog & UI Interaction Constants** +The Add-on UI SDK constants provide type-safe ways to interact with all the major Add-on UI SDK features developers commonly use. This includes: + +- Constants available in `addOnUISdk.constants.*` +- Constants available only as named exports (specific import required) + +See the [Import Patterns](#import-patterns) section for details on how to access each type. + + + +The constants listed in this reference are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". + +## **Dialog & UI Interaction Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| @@ -9,7 +20,7 @@ | **`FieldType`** | `text` | Input field types for input dialogs | Used in dialog field configuration | | **`DialogResultType`** | `alert`, `input`, `custom` | Types of dialog results returned | Determine result structure type | -### **Platform & Environment Constants** +## **Platform & Environment Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| @@ -18,7 +29,7 @@ | **`DeviceClass`** | `mobile`, `tablet`, `desktop` | Device category | Responsive design decisions | | **`RuntimeType`** | `panel`, `dialog`, `documentSandbox`, `command` | Add-on runtime types | Communication API configuration | -### **Document Export & Rendering Constants** +## **Document Export & Rendering Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| @@ -27,7 +38,7 @@ | **`RenditionType`** | `page` | Type of rendition | Document export configuration | | **`RenditionIntent`** | `export`, `preview`, `print` | Purpose of rendition | Optimize rendering for use case | -### **Video & Media Constants** +## **Video & Media Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| @@ -36,7 +47,7 @@ | **`BitRate`** | `mbps4` through `mbps50` | Video bit rates in bps | Video compression settings | | **`BleedUnit`** | `Inch`, `Millimeter` | Print bleed units | Print preparation | -### **Editor Panel Navigation Constants** +## **Editor Panel Navigation Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| @@ -45,25 +56,25 @@ | **`ElementsTabs`** | `designAssets`, `backgrounds`, `shapes`, `stockIcons`, `charts` | Elements panel tabs | Navigate to specific element type | | **`PanelActionType`** | `search`, `navigate` | Panel action types | Panel interaction configuration | -### **Color Picker Constants** +## **Color Picker Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| | **`ColorPickerPlacement`** | `top`, `bottom`, `left`, `right` | Color picker positioning | `showColorPicker(element, {placement: ColorPickerPlacement.top})` | -### **File Management Constants** +## **File Management Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| | **`FileSizeLimitUnit`** | `KB`, `MB` | File size limit units | File upload constraints | -### **OAuth & Authentication Constants** +## **OAuth & Authentication Constants** | Constant | Values | Description | Usage | |----------|--------|-------------|-------| | **`AuthorizationStatus`** | (imported from @hz/wxp-oauth) | OAuth authorization states | Check authorization status | -### **Usage Examples** +## **Usage Examples** ```typescript import addOnUISdk, { @@ -96,11 +107,139 @@ await addOnUISdk.app.document.createRenditions({ addOnUISdk.app.ui.openEditorPanel(EditorPanel.media); ``` -### **Notes for Developers** +## Notes for Developers -- **All constants are available as named exports** from the Add-on UI SDK +- **Some constants are available as named exports** from the Add-on UI SDK - **Use constants instead of string literals** for type safety and future compatibility -- **Some constants are marked as `@internal`** (like `ToastVariant`, `FeatureType`, `SettingType`) and are not included as they're for internal/privileged use only -- **Platform detection is crucial** for responsive add-on design across different devices and browsers -These constants provide type-safe ways to interact with all the major Add-on UI SDK features that third-party developers commonly use! \ No newline at end of file +### Import Patterns + +Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is imperative for avoiding runtime errors. + +#### Named Exports (Import Required) + +These constants are **only available as named exports** and must be imported explicitly. They are **not** available through `addOnUISdk.constants.*`: + +```javascript +import addOnUISdk, { + AppEvent, + ColorPickerEvent, + SupportedMimeTypes, + EntrypointType, + PdfReturnUrlType +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// ✅ Correct usage +const docxMimeType = SupportedMimeTypes.docx; +const colorChangeEvent = ColorPickerEvent.colorChange; + +// ❌ Will NOT work - these are not in the constants object +const docxMimeType = addOnUISdk.constants.SupportedMimeTypes.docx; // undefined +``` + +#### Dual Access Constants + +These constants support **both import patterns** for flexibility. You can use either approach: + +```javascript +import addOnUISdk, { Range, RenditionFormat, Variant } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Option 1: Named import (recommended for cleaner code) +const currentPage = Range.currentPage; +const pngFormat = RenditionFormat.png; +const confirmDialog = Variant.confirmation; + +// Option 2: Constants object access (traditional pattern) +const currentPage = addOnUISdk.constants.Range.currentPage; +const pngFormat = addOnUISdk.constants.RenditionFormat.png; +const confirmDialog = addOnUISdk.constants.Variant.confirmation; +``` + +**Dual Access Constants List:** + +- `Range` / `addOnUISdk.constants.Range` +- `RenditionFormat` / `addOnUISdk.constants.RenditionFormat` +- `RenditionType` / `addOnUISdk.constants.RenditionType` +- `RenditionIntent` / `addOnUISdk.constants.RenditionIntent` +- `Variant` / `addOnUISdk.constants.Variant` +- `DialogResultType` / `addOnUISdk.constants.DialogResultType` +- `ButtonType` / `addOnUISdk.constants.ButtonType` +- `RuntimeType` / `addOnUISdk.constants.RuntimeType` +- `BleedUnit` / `addOnUISdk.constants.BleedUnit` +- `EditorPanel` / `addOnUISdk.constants.EditorPanel` +- `MediaTabs` / `addOnUISdk.constants.MediaTabs` +- `ElementsTabs` / `addOnUISdk.constants.ElementsTabs` +- `PanelActionType` / `addOnUISdk.constants.PanelActionType` +- `ColorPickerPlacement` / `addOnUISdk.constants.ColorPickerPlacement` +- `AuthorizationStatus` / `addOnUISdk.constants.AuthorizationStatus` +- `FieldType` / `addOnUISdk.constants.FieldType` +- `PlatformEnvironment` / `addOnUISdk.constants.PlatformEnvironment` +- `DeviceClass` / `addOnUISdk.constants.DeviceClass` +- `PlatformType` / `addOnUISdk.constants.PlatformType` +- `MediaType` / `addOnUISdk.constants.MediaType` +- `VideoResolution` / `addOnUISdk.constants.VideoResolution` +- `FrameRate` / `addOnUISdk.constants.FrameRate` +- `BitRate` / `addOnUISdk.constants.BitRate` +- `FileSizeLimitUnit` / `addOnUISdk.constants.FileSizeLimitUnit` +- `LinkOptions` / `addOnUISdk.constants.LinkOptions` + +### Best Practices + +1. **Use named imports for cleaner code** when you know which constants you need: + ```javascript + import addOnUISdk, { Range, RenditionFormat } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + + const options = { + range: Range.currentPage, + format: RenditionFormat.png + }; + ``` + +2. **Use constants object for dynamic access** when the constant name is determined at runtime: + ```javascript + const format = addOnUISdk.constants.RenditionFormat[userSelectedFormat]; + ``` + +3. **Always import named-only exports** - there's no alternative way to access them: + ```javascript + import addOnUISdk, { SupportedMimeTypes } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + ``` + +### Quick Reference Table + +| Constant | Named Export | Constants Object | Import Required | +|----------|--------------|------------------|-----------------| +| `AppEvent` | ✅ | ❌ | **Yes** | +| `ColorPickerEvent` | ✅ | ❌ | **Yes** | +| `SupportedMimeTypes` | ✅ | ❌ | **Yes** | +| `EntrypointType` | ✅ | ❌ | **Yes** | +| `PdfReturnUrlType` | ✅ | ❌ | **Yes** | +| `Range` | ✅ | ✅ | Optional | +| `RenditionFormat` | ✅ | ✅ | Optional | +| `Variant` | ✅ | ✅ | Optional | +| `ButtonType` | ✅ | ✅ | Optional | +| `FieldType` | ✅ | ✅ | Optional | +| `PlatformEnvironment` | ✅ | ✅ | Optional | +| `DeviceClass` | ✅ | ✅ | Optional | +| `PlatformType` | ✅ | ✅ | Optional | +| `AuthorizationStatus` | ✅ | ✅ | Optional | +| `RenditionType` | ✅ | ✅ | Optional | +| `RenditionIntent` | ✅ | ✅ | Optional | +| `DialogResultType` | ✅ | ✅ | Optional | +| `RuntimeType` | ✅ | ✅ | Optional | +| `BleedUnit` | ✅ | ✅ | Optional | +| `EditorPanel` | ✅ | ✅ | Optional | +| `MediaTabs` | ✅ | ✅ | Optional | +| `ElementsTabs` | ✅ | ✅ | Optional | +| `PanelActionType` | ✅ | ✅ | Optional | +| `ColorPickerPlacement` | ✅ | ✅ | Optional | +| `MediaType` | ✅ | ✅ | Optional | +| `VideoResolution` | ✅ | ✅ | Optional | +| `FrameRate` | ✅ | ✅ | Optional | +| `BitRate` | ✅ | ✅ | Optional | +| `FileSizeLimitUnit` | ✅ | ✅ | Optional | +| `LinkOptions` | ✅ | ✅ | Optional | + + + +**Important:** Attempting to access named-only exports through `addOnUISdk.constants.*` will return `undefined` and may cause runtime errors. Always check the table above or use TypeScript for compile-time validation. From c20548c396433587f35d4706dca548718d31b574 Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 27 Aug 2025 18:39:41 -0400 Subject: [PATCH 7/8] Rewording --- src/pages/guides/learn/platform_concepts/constants-guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/guides/learn/platform_concepts/constants-guide.md b/src/pages/guides/learn/platform_concepts/constants-guide.md index 0c3d415c4..8aa846646 100644 --- a/src/pages/guides/learn/platform_concepts/constants-guide.md +++ b/src/pages/guides/learn/platform_concepts/constants-guide.md @@ -114,11 +114,11 @@ addOnUISdk.app.ui.openEditorPanel(EditorPanel.media); ### Import Patterns -Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is imperative for avoiding runtime errors. +Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is essential for avoiding runtime errors. #### Named Exports (Import Required) -These constants are **only available as named exports** and must be imported explicitly. They are **not** available through `addOnUISdk.constants.*`: +These constants are **only available as named exports** and must be imported explicitly. They are **NOT** available through `addOnUISdk.constants.*`: ```javascript import addOnUISdk, { From 0b6d31e98c07e9c5e850463adcec6a4adf886d5d Mon Sep 17 00:00:00 2001 From: Holly Schinsky Date: Wed, 1 Oct 2025 14:00:05 -0400 Subject: [PATCH 8/8] Change alert type for bullets --- .../guides/learn/how_to/selection_events.md | 1167 +++++++---------- 1 file changed, 480 insertions(+), 687 deletions(-) diff --git a/src/pages/guides/learn/how_to/selection_events.md b/src/pages/guides/learn/how_to/selection_events.md index 8b203ee8f..d3a5c9fba 100644 --- a/src/pages/guides/learn/how_to/selection_events.md +++ b/src/pages/guides/learn/how_to/selection_events.md @@ -2,104 +2,89 @@ keywords: - Adobe Express - Express Add-on SDK - - Express Editor - - Adobe Express - - Add-on SDK - - SDK - - JavaScript - - Extend - - Extensibility - - API + - Document API - Selection - selectionChange - Events - - Context - - Node -title: Selection Events and Methods -description: Learn how to work with selections, handle selection changes, and respond to user interactions in Adobe Express documents. + - Node selection + - UI integration + - Document sandbox + - Event handlers + - Selection filtering + - Locked nodes + - Properties panel + - Real-time updates +title: Handle Element Selection +description: Complete guide to working with element selections in Adobe Express documents - from basic selection operations to advanced event handling and UI integration patterns. contributors: - https://github.com/hollyschinsky faq: questions: - question: "How do I get the current selection?" - answer: "Use `editor.context.selection` to get an array of currently selected nodes." + answer: "Use `editor.context.selection` to get an array of currently selected editable nodes, or `editor.context.hasSelection` to check if anything is selected." - question: "How do I listen for selection changes?" - answer: "Use `editor.context.on('selectionChange', callback)` to register a selection change handler." + answer: "Use `editor.context.on(EditorEvent.selectionChange, callback)` to register a handler. Always store the returned ID for cleanup." - question: "How do I programmatically select elements?" - answer: "Set `editor.context.selection = [node]` or `editor.context.selection = [node1, node2]` for multiple elements." + answer: "Set `editor.context.selection = node` for single elements or `editor.context.selection = [node1, node2]` for multiple elements." - question: "What's the difference between selection and selectionIncludingNonEditable?" - answer: "`selection` only includes editable nodes, while `selectionIncludingNonEditable` also includes locked/non-editable nodes." + answer: "`selection` only includes editable nodes, while `selectionIncludingNonEditable` includes locked/non-editable nodes that users can see but not modify." - question: "Can I modify the document in a selection change callback?" - answer: "No, avoid making document changes in selection change callbacks as it may destabilize the application." + answer: "Never modify the document in selection change handlers - this can crash the application. Only update UI, analyze data, or communicate with your panel." - question: "How do I clear the selection?" - answer: "Set `editor.context.selection = []` or `editor.context.selection = undefined`." + answer: "Set `editor.context.selection = []` or `editor.context.selection = undefined` to clear all selections." - - question: "What are the selection rules?" - answer: "Nodes must be within the current artboard, ancestors cannot be selected with descendants, and locked nodes are filtered out." + - question: "What selection rules should I know?" + answer: "Only nodes within the current artboard can be selected, you cannot select both parent and child nodes simultaneously, and locked nodes are automatically filtered from the main selection." - - question: "How do I unregister selection event handlers?" - answer: "Use `editor.context.off('selectionChange', handlerId)` with the ID returned from the `on()` method." ---- + - question: "How do I clean up selection event handlers?" + answer: "Always call `editor.context.off(EditorEvent.selectionChange, handlerId)` using the ID returned from `on()` to prevent memory leaks." -# Selection Events and Methods + - question: "How do I communicate selection changes to my UI?" + answer: "Use the runtime API to send selection data from the document sandbox to your UI panel, enabling real-time interface updates." -Learn how to work with user selections, handle selection changes, and respond to user interactions in Adobe Express documents using the Document API. + - question: "What are common selection-based actions?" + answer: "Typical actions include updating properties panels, enabling/disabling tools, applying formatting to selected text, grouping elements, and showing context-appropriate options." +--- - +# Handle Element Selection -Document API Context Required +Learn how to work with user selections, handle selection changes, and create responsive interfaces that react to what users select in their Adobe Express documents. -Selection methods and events are part of the Document API and require the document sandbox environment. These examples should be used in your `sandbox/code.js` file, not in the main iframe panel. +## Getting Started with Selections -Make sure your manifest includes `"documentSandbox": "sandbox/code.js"` in the entry points. +Selections in Adobe Express represent the elements (nodes) that users have currently selected in their document. The selection system provides access to what's selected, the ability to change selections programmatically, and events to respond to selection changes. -## Understanding Selections +All selection operations use the **Document API** and run in the **document sandbox environment**. This means your selection code should be placed in your `code.js` file, not in your main iframe panel code. -In Adobe Express, selections represent the nodes (elements) that the user has currently selected. The selection system provides: + -- **Current selection access** - Get what's currently selected -- **Selection modification** - Programmatically change selections -- **Selection events** - React to selection changes -- **Selection filtering** - Handle locked/non-editable content +### Document Sandbox Setup -### Selection Rules +Make sure your `manifest.json` includes `"documentSandbox": "code.js"` in the entry points to set up the document sandbox environment. -Adobe Express enforces several rules for selections: - -1. **Artboard constraint** - Only nodes within the current artboard can be selected -2. **Hierarchy filtering** - Cannot select both parent and child nodes simultaneously -3. **Locked node filtering** - Locked nodes are excluded from the main selection -4. **Editable-only** - Main selection only includes editable nodes - -## Getting the Current Selection - -### Basic Selection Access +### Check Current Selection #### JavaScript ```js -// sandbox/code.js +// code.js import { editor } from "express-document-sdk"; -// Get the current selection -const selection = editor.context.selection; - -console.log("Selected nodes:", selection.length); - // Check if anything is selected if (editor.context.hasSelection) { - console.log("Something is selected"); + const selection = editor.context.selection; + console.log(`Selected ${selection.length} item(s)`); // Process each selected node selection.forEach((node, index) => { - console.log(`Node ${index + 1}:`, node.type); + console.log(`Node ${index + 1}: ${node.type}`); }); } else { console.log("Nothing is selected"); @@ -109,94 +94,102 @@ if (editor.context.hasSelection) { #### TypeScript ```ts -// sandbox/code.js -import { editor, Node } from "express-document-sdk"; - -// Get the current selection -const selection: readonly Node[] = editor.context.selection; - -console.log("Selected nodes:", selection.length); +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; // Check if anything is selected if (editor.context.hasSelection) { - console.log("Something is selected"); + const selection: readonly Node[] = editor.context.selection; + console.log(`Selected ${selection.length} item(s)`); // Process each selected node selection.forEach((node: Node, index: number) => { - console.log(`Node ${index + 1}:`, node.type); + console.log(`Node ${index + 1}: ${node.type}`); }); } else { console.log("Nothing is selected"); } ``` -### Including Non-Editable Selections +## Understanding Selections + +In Adobe Express, the selection system provides: + +- **Current selection access** - Get what's currently selected +- **Selection modification** - Programmatically change selections +- **Selection events** - React to selection changes +- **Selection filtering** - Handle locked/non-editable content + +### Selection Rules + +Adobe Express enforces these constraints: + +1. **Artboard constraint** - Only nodes within the current artboard can be selected +2. **Hierarchy filtering** - Cannot select both parent and child nodes simultaneously +3. **Locked node filtering** - Locked nodes are excluded from the main selection +4. **Editable-only** - Main selection only includes editable nodes + +## Basic Selection Operations + +Core operations for working with selections. + +### Getting the Current Selection #### JavaScript ```js -// sandbox/code.js +// code.js import { editor } from "express-document-sdk"; -// Get selection including locked/non-editable nodes -const fullSelection = editor.context.selectionIncludingNonEditable; -const editableSelection = editor.context.selection; +// Get the current selection +const selection = editor.context.selection; -console.log("Total selected (including locked):", fullSelection.length); -console.log("Editable selected:", editableSelection.length); +console.log("Selected nodes:", selection.length); -if (fullSelection.length > editableSelection.length) { - console.log("Some locked nodes are selected"); +// Process each selected node +selection.forEach((node, index) => { + console.log(`Node ${index + 1}: ${node.type}`); - // Find the locked nodes - const lockedNodes = fullSelection.filter(node => - !editableSelection.includes(node) - ); - - console.log("Locked nodes:", lockedNodes.length); -} + // Common node properties you can access + console.log(" Position:", node.translation); + console.log(" Size:", { width: node.width, height: node.height }); +}); ``` #### TypeScript ```ts -// sandbox/code.js -import { editor, Node } from "express-document-sdk"; +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; -// Get selection including locked/non-editable nodes -const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; -const editableSelection: readonly Node[] = editor.context.selection; +// Get the current selection +const selection: readonly Node[] = editor.context.selection; -console.log("Total selected (including locked):", fullSelection.length); -console.log("Editable selected:", editableSelection.length); +console.log("Selected nodes:", selection.length); -if (fullSelection.length > editableSelection.length) { - console.log("Some locked nodes are selected"); - - // Find the locked nodes - const lockedNodes: Node[] = fullSelection.filter((node: Node) => - !editableSelection.includes(node) - ); +// Process each selected node +selection.forEach((node: Node, index: number) => { + console.log(`Node ${index + 1}: ${node.type}`); - console.log("Locked nodes:", lockedNodes.length); -} + // Common node properties you can access + console.log(" Position:", node.translation); + console.log(" Size:", { width: node.width, height: node.height }); +}); ``` -## Setting Selections Programmatically - -### Select Single Element +### Programmatic Selection #### JavaScript ```js -// sandbox/code.js +// code.js import { editor } from "express-document-sdk"; -// Create a rectangle and select it +// Create and select a single element const rectangle = editor.createRectangle(); rectangle.width = 100; rectangle.height = 100; @@ -205,10 +198,9 @@ rectangle.translation = { x: 50, y: 50 }; // Add to document editor.context.insertionParent.children.append(rectangle); -// Select the rectangle -editor.context.selection = rectangle; // Single node shortcut -// OR -editor.context.selection = [rectangle]; // Array syntax +// Select the rectangle (single element) +editor.context.selection = rectangle; +// OR using array syntax: editor.context.selection = [rectangle]; console.log("Rectangle is now selected"); ``` @@ -216,10 +208,10 @@ console.log("Rectangle is now selected"); #### TypeScript ```ts -// sandbox/code.js +// code.ts import { editor, RectangleNode, ContainerNode } from "express-document-sdk"; -// Create a rectangle and select it +// Create a simple rectangle to demonstrate selection const rectangle: RectangleNode = editor.createRectangle(); rectangle.width = 100; rectangle.height = 100; @@ -229,22 +221,21 @@ rectangle.translation = { x: 50, y: 50 }; const insertionParent: ContainerNode = editor.context.insertionParent; insertionParent.children.append(rectangle); -// Select the rectangle -editor.context.selection = rectangle; // Single node shortcut -// OR -editor.context.selection = [rectangle]; // Array syntax +// Select the rectangle (single element) +editor.context.selection = rectangle; +// OR using array syntax: editor.context.selection = [rectangle]; console.log("Rectangle is now selected"); ``` -### Select Multiple Elements +### Multiple Selection #### JavaScript ```js -// sandbox/code.js +// code.js import { editor } from "express-document-sdk"; // Create multiple elements @@ -258,15 +249,12 @@ ellipse.rx = 40; ellipse.ry = 40; ellipse.translation = { x: 200, y: 50 }; -const text = editor.createText("Hello!"); -text.translation = { x: 50, y: 200 }; - -// Add all to document +// Add both to document const parent = editor.context.insertionParent; -parent.children.append(rectangle, ellipse, text); +parent.children.append(rectangle, ellipse); -// Select multiple elements -editor.context.selection = [rectangle, ellipse, text]; +// Select both elements at once +editor.context.selection = [rectangle, ellipse]; console.log("Multiple elements selected:", editor.context.selection.length); ``` @@ -274,13 +262,13 @@ console.log("Multiple elements selected:", editor.context.selection.length); #### TypeScript ```ts -// sandbox/code.js -import { editor, RectangleNode, EllipseNode, StandaloneTextNode, ContainerNode } from "express-document-sdk"; +// code.ts +import { editor, RectangleNode, EllipseNode, ContainerNode } from "express-document-sdk"; -// Create multiple elements +// Create multiple simple elements const rectangle: RectangleNode = editor.createRectangle(); rectangle.width = 80; -rectangle.height = 80; +rectangle.height = 80; rectangle.translation = { x: 50, y: 50 }; const ellipse: EllipseNode = editor.createEllipse(); @@ -288,33 +276,29 @@ ellipse.rx = 40; ellipse.ry = 40; ellipse.translation = { x: 200, y: 50 }; -const text: StandaloneTextNode = editor.createText("Hello!"); -text.translation = { x: 50, y: 200 }; - -// Add all to document +// Add both to document const parent: ContainerNode = editor.context.insertionParent; -parent.children.append(rectangle, ellipse, text); +parent.children.append(rectangle, ellipse); -// Select multiple elements -editor.context.selection = [rectangle, ellipse, text]; +// Select both elements at once +editor.context.selection = [rectangle, ellipse]; console.log("Multiple elements selected:", editor.context.selection.length); ``` -### Clear Selection +### Clearing the Selection #### JavaScript ```js -// sandbox/code.js +// code.js import { editor } from "express-document-sdk"; -// Clear the selection (both methods work) +// Clear the selection - both ways work editor.context.selection = []; -// OR -editor.context.selection = undefined; +// OR: editor.context.selection = undefined; console.log("Selection cleared"); console.log("Has selection:", editor.context.hasSelection); // false @@ -323,19 +307,20 @@ console.log("Has selection:", editor.context.hasSelection); // false #### TypeScript ```ts -// sandbox/code.js +// code.ts import { editor } from "express-document-sdk"; -// Clear the selection (both methods work) +// Clear the selection - both ways work editor.context.selection = []; -// OR -editor.context.selection = undefined; +// OR: editor.context.selection = undefined; console.log("Selection cleared"); console.log("Has selection:", editor.context.hasSelection); // false ``` -## Listening for Selection Changes +## Selection Events + +Respond to selection changes to create dynamic UIs that update based on what's selected. ### Basic Selection Change Handler @@ -344,64 +329,246 @@ console.log("Has selection:", editor.context.hasSelection); // false #### JavaScript ```js -// sandbox/code.js -import { editor } from "express-document-sdk"; +// code.js +import { editor, EditorEvent } from "express-document-sdk"; -// Register selection change handler -const handlerId = editor.context.on("selectionChange", () => { +// Listen for selection changes +const handlerId = editor.context.on(EditorEvent.selectionChange, () => { const selection = editor.context.selection; console.log("Selection changed!"); console.log("New selection count:", selection.length); - if (selection.length > 0) { - console.log("Selected node types:", selection.map(node => node.type)); + if (selection.length === 0) { + console.log("Nothing selected"); + } else if (selection.length === 1) { + console.log("One item selected:", selection[0].type); } else { - console.log("Selection cleared"); + console.log("Multiple items selected"); } }); -console.log("Selection change handler registered with ID:", handlerId); - -// Important: Store the handlerId if you need to unregister later +// Store handlerId if you need to unregister later +console.log("Selection handler registered:", handlerId); ``` #### TypeScript ```ts -// sandbox/code.js -import { editor, Node } from "express-document-sdk"; +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; -// Register selection change handler -const handlerId: string = editor.context.on("selectionChange", () => { +// Listen for selection changes +const handlerId: string = editor.context.on(EditorEvent.selectionChange, () => { const selection: readonly Node[] = editor.context.selection; console.log("Selection changed!"); console.log("New selection count:", selection.length); - if (selection.length > 0) { - console.log("Selected node types:", selection.map((node: Node) => node.type)); + if (selection.length === 0) { + console.log("Nothing selected"); + } else if (selection.length === 1) { + console.log("One item selected:", selection[0].type); } else { - console.log("Selection cleared"); + console.log("Multiple items selected"); } }); -console.log("Selection change handler registered with ID:", handlerId); +// Store handlerId if you need to unregister later +console.log("Selection handler registered:", handlerId); +``` + +### Properties Panel Example + +Dynamic properties panel based on selection: + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +function updatePropertiesPanel() { + const selection = editor.context.selection; + + if (selection.length === 0) { + console.log("Properties Panel: Show 'Nothing Selected' state"); + return; + } + + if (selection.length === 1) { + const node = selection[0]; + console.log("Properties Panel: Show properties for", node.type); + + // Show different properties based on node type + if (node.type === "Text") { + console.log(" - Show font controls"); + console.log(" - Show text color picker"); + } else if (node.type === "Rectangle" || node.type === "Ellipse") { + console.log(" - Show fill color picker"); + console.log(" - Show stroke controls"); + } + + // Common properties for all nodes + console.log(" - Show position controls"); + console.log(" - Show size controls"); + + } else { + console.log("Properties Panel: Show multi-selection options"); + console.log(` - ${selection.length} items selected`); + console.log(" - Show alignment tools"); + console.log(" - Show group option"); + } +} + +// Register the handler +editor.context.on(EditorEvent.selectionChange, updatePropertiesPanel); + +// Call once on startup to initialize +updatePropertiesPanel(); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, TextNode } from "express-document-sdk"; + +function updatePropertiesPanel(): void { + const selection: readonly Node[] = editor.context.selection; + + if (selection.length === 0) { + console.log("Properties Panel: Show 'Nothing Selected' state"); + return; + } + + if (selection.length === 1) { + const node: Node = selection[0]; + console.log("Properties Panel: Show properties for", node.type); + + // Show different properties based on node type + if (node.type === "Text") { + console.log(" - Show font controls"); + console.log(" - Show text color picker"); + } else if (node.type === "Rectangle" || node.type === "Ellipse") { + console.log(" - Show fill color picker"); + console.log(" - Show stroke controls"); + } + + // Common properties for all nodes + console.log(" - Show position controls"); + console.log(" - Show size controls"); + + } else { + console.log("Properties Panel: Show multi-selection options"); + console.log(` - ${selection.length} items selected`); + console.log(" - Show alignment tools"); + console.log(" - Show group option"); + } +} + +// Register the handler +editor.context.on(EditorEvent.selectionChange, updatePropertiesPanel); -// Important: Store the handlerId if you need to unregister later +// Call once on startup to initialize +updatePropertiesPanel(); ``` -### Advanced Selection Analysis +### Event Handler Cleanup + +⚠️ **Important**: Always clean up event handlers to prevent memory leaks. #### JavaScript ```js -// sandbox/code.js -import { editor } from "express-document-sdk"; +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +// Store handler IDs so you can unregister them later +let selectionHandlerId = null; + +function startListening() { + // Register handler and store the ID + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { + console.log("Selection changed!"); + // Handle selection change + }); + + console.log("✅ Selection handler registered"); +} + +function stopListening() { + // Clean up the handler + if (selectionHandlerId) { + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); + selectionHandlerId = null; + console.log("✅ Selection handler cleaned up"); + } +} + +// Start listening +startListening(); + +// Clean up when your add-on is being destroyed or reset +// stopListening(); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, EditorEvent } from "express-document-sdk"; + +// Store handler IDs so you can unregister them later +let selectionHandlerId: string | null = null; + +function startListening(): void { + // Register handler and store the ID + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { + console.log("Selection changed!"); + // Handle selection change + }); + + console.log("✅ Selection handler registered"); +} + +function stopListening(): void { + // Clean up the handler + if (selectionHandlerId) { + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); + selectionHandlerId = null; + console.log("✅ Selection handler cleaned up"); + } +} + +// Start listening +startListening(); + +// Clean up when your add-on is being destroyed or reset +// stopListening(); +``` + +## Advanced Selection Techniques + +Advanced patterns for complex add-ons. + +### Working with Locked/Non-Editable Elements + +Handle selections that include locked or non-editable content: + + + +#### JavaScript -function analyzeSelection() { +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +function analyzeCompleteSelection() { const selection = editor.context.selection; const fullSelection = editor.context.selectionIncludingNonEditable; @@ -409,7 +576,7 @@ function analyzeSelection() { editableCount: selection.length, totalCount: fullSelection.length, lockedCount: fullSelection.length - selection.length, - types: selection.map(node => node.type), + types: [...new Set(selection.map(node => node.type))], // Unique types hasText: selection.some(node => node.type === "Text"), hasShapes: selection.some(node => node.type === "Rectangle" || node.type === "Ellipse" @@ -418,28 +585,37 @@ function analyzeSelection() { }; } -// Register detailed selection handler -const handlerId = editor.context.on("selectionChange", () => { - const analysis = analyzeSelection(); - - console.log("=== Selection Analysis ==="); - console.log("Editable nodes:", analysis.editableCount); - console.log("Total nodes (including locked):", analysis.totalCount); - console.log("Locked nodes:", analysis.lockedCount); - console.log("Node types:", analysis.types); - console.log("Has text:", analysis.hasText); - console.log("Has shapes:", analysis.hasShapes); - console.log("Is empty:", analysis.isEmpty); +// Example: Dynamic UI updates based on detailed analysis +editor.context.on(EditorEvent.selectionChange, () => { + const analysis = analyzeCompleteSelection(); + + console.log("📊 Detailed Selection Info:"); + console.log(` Editable: ${analysis.editableCount}`); + if (analysis.lockedCount > 0) { + console.log(` Locked: ${analysis.lockedCount}`); + } + console.log(` Types: ${analysis.types.join(", ")}`); + + // Enable specific tools based on content + if (analysis.hasText) { + console.log("🔤 Text formatting tools available"); + } + if (analysis.hasShapes) { + console.log("🔷 Shape styling tools available"); + } + if (analysis.editableCount > 1) { + console.log("📐 Alignment tools available"); + } }); ``` #### TypeScript ```ts -// sandbox/code.js -import { editor, Node } from "express-document-sdk"; +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; -interface SelectionAnalysis { +interface DetailedSelectionAnalysis { editableCount: number; totalCount: number; lockedCount: number; @@ -449,7 +625,7 @@ interface SelectionAnalysis { isEmpty: boolean; } -function analyzeSelection(): SelectionAnalysis { +function analyzeSelection(): DetailedSelectionAnalysis { const selection: readonly Node[] = editor.context.selection; const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; @@ -457,7 +633,7 @@ function analyzeSelection(): SelectionAnalysis { editableCount: selection.length, totalCount: fullSelection.length, lockedCount: fullSelection.length - selection.length, - types: selection.map((node: Node) => node.type), + types: [...new Set(selection.map((node: Node) => node.type))], // Unique types hasText: selection.some((node: Node) => node.type === "Text"), hasShapes: selection.some((node: Node) => node.type === "Rectangle" || node.type === "Ellipse" @@ -466,31 +642,44 @@ function analyzeSelection(): SelectionAnalysis { }; } -// Register detailed selection handler -const handlerId: string = editor.context.on("selectionChange", () => { - const analysis: SelectionAnalysis = analyzeSelection(); - - console.log("=== Selection Analysis ==="); - console.log("Editable nodes:", analysis.editableCount); - console.log("Total nodes (including locked):", analysis.totalCount); - console.log("Locked nodes:", analysis.lockedCount); - console.log("Node types:", analysis.types); - console.log("Has text:", analysis.hasText); - console.log("Has shapes:", analysis.hasShapes); - console.log("Is empty:", analysis.isEmpty); +// Example: Dynamic UI updates based on detailed analysis +editor.context.on(EditorEvent.selectionChange, () => { + const analysis: DetailedSelectionAnalysis = analyzeSelection(); + + console.log("📊 Detailed Selection Info:"); + console.log(` Editable: ${analysis.editableCount}`); + if (analysis.lockedCount > 0) { + console.log(` Locked: ${analysis.lockedCount}`); + } + console.log(` Types: ${analysis.types.join(", ")}`); + + // Enable specific tools based on content + if (analysis.hasText) { + console.log("🔤 Text formatting tools available"); + } + if (analysis.hasShapes) { + console.log("🔷 Shape styling tools available"); + } + if (analysis.editableCount > 1) { + console.log("📐 Alignment tools available"); + } }); ``` -## Practical Selection Patterns +## UI Integration + +Communicate selection changes between the document sandbox and your UI panel to create responsive interfaces. ### Selection-Based Actions +Common patterns for performing actions on selected elements: + #### JavaScript ```js -// sandbox/code.js +// code.js import { editor, colorUtils } from "express-document-sdk"; // Function to apply red color to selected text @@ -544,7 +733,7 @@ function groupSelection() { } // Register handlers for different actions -editor.context.on("selectionChange", () => { +editor.context.on(EditorEvent.selectionChange, () => { const selection = editor.context.selection; // Update UI or enable/disable actions based on selection @@ -561,7 +750,7 @@ editor.context.on("selectionChange", () => { #### TypeScript ```ts -// sandbox/code.js +// code.ts import { editor, colorUtils, Node, TextNode, GroupNode, ContainerNode } from "express-document-sdk"; // Function to apply red color to selected text @@ -618,7 +807,7 @@ function groupSelection(): void { } // Register handlers for different actions -editor.context.on("selectionChange", () => { +editor.context.on(EditorEvent.selectionChange, () => { const selection: readonly Node[] = editor.context.selection; // Update UI or enable/disable actions based on selection @@ -639,8 +828,8 @@ editor.context.on("selectionChange", () => { #### JavaScript ```js -// sandbox/code.js -import { editor } from "express-document-sdk"; +// code.js +import { editor, EditorEvent } from "express-document-sdk"; class SelectionManager { constructor() { @@ -650,7 +839,7 @@ class SelectionManager { } startListening() { - this.handlerId = editor.context.on("selectionChange", () => { + this.handlerId = editor.context.on(EditorEvent.selectionChange, () => { const selection = editor.context.selection; // Store selection in history (limit to last 10) @@ -699,7 +888,7 @@ class SelectionManager { stopListening() { if (this.handlerId) { - editor.context.off("selectionChange", this.handlerId); + editor.context.off(EditorEvent.selectionChange, this.handlerId); this.handlerId = null; } } @@ -712,8 +901,8 @@ const selectionManager = new SelectionManager(); #### TypeScript ```ts -// sandbox/code.js -import { editor, Node } from "express-document-sdk"; +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; class SelectionManager { private selectionHistory: Node[][] = []; @@ -724,7 +913,7 @@ class SelectionManager { } startListening(): void { - this.handlerId = editor.context.on("selectionChange", () => { + this.handlerId = editor.context.on(EditorEvent.selectionChange, () => { const selection: readonly Node[] = editor.context.selection; // Store selection in history (limit to last 10) @@ -773,7 +962,7 @@ class SelectionManager { stopListening(): void { if (this.handlerId) { - editor.context.off("selectionChange", this.handlerId); + editor.context.off(EditorEvent.selectionChange, this.handlerId); this.handlerId = null; } } @@ -783,24 +972,26 @@ class SelectionManager { const selectionManager = new SelectionManager(); ``` -## Cleanup and Best Practices +## Best Practices & Guidelines + +### Event Handler Cleanup -### Unregistering Event Handlers +⚠️ **Important**: Always clean up event handlers to prevent memory leaks. #### JavaScript ```js -// sandbox/code.js -import { editor } from "express-document-sdk"; +// code.js +import { editor, EditorEvent } from "express-document-sdk"; // Store handler IDs for cleanup let selectionHandlerId = null; function setupSelectionHandling() { // Register handler and store ID - selectionHandlerId = editor.context.on("selectionChange", () => { + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { console.log("Selection changed"); // Handle selection change }); @@ -811,7 +1002,7 @@ function setupSelectionHandling() { function cleanupSelectionHandling() { // Unregister the handler if (selectionHandlerId) { - editor.context.off("selectionChange", selectionHandlerId); + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); selectionHandlerId = null; console.log("Selection handler unregistered"); } @@ -827,15 +1018,15 @@ setupSelectionHandling(); #### TypeScript ```ts -// sandbox/code.js -import { editor } from "express-document-sdk"; +// code.ts +import { editor, EditorEvent } from "express-document-sdk"; // Store handler IDs for cleanup let selectionHandlerId: string | null = null; function setupSelectionHandling(): void { // Register handler and store ID - selectionHandlerId = editor.context.on("selectionChange", () => { + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { console.log("Selection changed"); // Handle selection change }); @@ -846,7 +1037,7 @@ function setupSelectionHandling(): void { function cleanupSelectionHandling(): void { // Unregister the handler if (selectionHandlerId) { - editor.context.off("selectionChange", selectionHandlerId); + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); selectionHandlerId = null; console.log("Selection handler unregistered"); } @@ -859,496 +1050,66 @@ setupSelectionHandling(); // cleanupSelectionHandling(); ``` -## Key Concepts and Rules +### Selection System Rules -### Selection Constraints +1. **Artboard constraint**: Only nodes within the current artboard can be selected +2. **Hierarchy filtering**: Cannot select both parent and child nodes simultaneously +3. **Locked node handling**: Locked nodes are excluded from main selection but available in `selectionIncludingNonEditable` +4. **Automatic filtering**: System automatically filters out invalid selections -1. **Artboard Limitation**: Only nodes within the current artboard can be selected -2. **Hierarchy Rules**: Cannot select both a parent node and its children simultaneously -3. **Locked Node Handling**: Locked nodes are excluded from the main selection but available in `selectionIncludingNonEditable` -4. **Automatic Filtering**: The system automatically filters out invalid selections +### Important: Selection Handler Restrictions -### Event Handler Guidelines + - +**Document Modification Restrictions** -Important: Document Modification Restrictions +**Never modify the document inside selection change handlers!** This can crash the application. -**Do not attempt to make changes to the document in response to a selection change callback** because it may destabilize the application. Selection change handlers should be used for: +**✅ Safe in selection handlers:** -✅ **Safe operations:** Updating UI, logging, analyzing selection, enabling/disabling buttons -❌ **Avoid:** Creating/deleting nodes, modifying properties, changing the document structure +- Update UI panels +- Log information +- Analyze selection +- Enable/disable buttons +- Send data to UI panel -### Performance Considerations +**❌ Never do in selection handlers:** -1. **Minimize Handler Logic**: Keep selection change handlers lightweight -2. **Debounce Rapid Changes**: Consider debouncing if handling rapid selection changes -3. **Clean Up Handlers**: Always unregister event handlers when no longer needed -4. **Avoid Deep Analysis**: Don't perform expensive operations in selection callbacks +- Create, delete, or modify nodes +- Change document structure +- Set properties on selected elements -## Communication Between UI and Document Sandbox + -One of the most important real-world patterns is communicating selection changes from the document sandbox to your UI panel, allowing you to update the interface based on what the user has selected. - -### Complete Communication Example - -This example shows how to set up bidirectional communication between your UI panel and document sandbox for selection-based interactions. - - - -#### JavaScript - -**UI Panel (index.js):** - -```js -// ui/index.js -import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; - -let documentSandbox; - -addOnUISdk.ready.then(async () => { - // Get access to the document sandbox APIs - documentSandbox = await addOnUISdk.instance.runtime.apiProxy("documentSandbox"); - - // Set up UI elements - setupSelectionUI(); - - // Start listening for selection updates from sandbox - documentSandbox.registerSelectionUpdateHandler(handleSelectionUpdate); -}); - -function setupSelectionUI() { - const container = document.getElementById("selection-info"); - - container.innerHTML = ` -
Nothing selected
-
- - - -
-
- `; - - // Set up button handlers - document.getElementById("apply-red-btn").addEventListener("click", () => { - documentSandbox.applyRedToSelection(); - }); - - document.getElementById("group-btn").addEventListener("click", () => { - documentSandbox.groupSelection(); - }); - - document.getElementById("clear-selection-btn").addEventListener("click", () => { - documentSandbox.clearSelection(); - }); -} - -function handleSelectionUpdate(selectionInfo) { - console.log("Selection update received:", selectionInfo); - - // Update status - const statusEl = document.getElementById("selection-status"); - if (selectionInfo.count === 0) { - statusEl.textContent = "Nothing selected"; - } else if (selectionInfo.count === 1) { - statusEl.textContent = `1 ${selectionInfo.types[0]} selected`; - } else { - statusEl.textContent = `${selectionInfo.count} elements selected`; - } - - // Update action buttons - document.getElementById("apply-red-btn").disabled = !selectionInfo.hasText; - document.getElementById("group-btn").disabled = selectionInfo.count < 2; - document.getElementById("clear-selection-btn").disabled = selectionInfo.count === 0; - - // Update details - const detailsEl = document.getElementById("selection-details"); - if (selectionInfo.count > 0) { - detailsEl.innerHTML = ` -

Selection Details:

-

Types: ${selectionInfo.types.join(", ")}

-

Has Text: ${selectionInfo.hasText ? "Yes" : "No"}

-

Has Shapes: ${selectionInfo.hasShapes ? "Yes" : "No"}

-

Locked Elements: ${selectionInfo.lockedCount}

- `; - } else { - detailsEl.innerHTML = ""; - } -} -``` - -**Document Sandbox (code.js):** - -```js -// sandbox/code.js -import addOnSandboxSdk from "add-on-sdk-document-sandbox"; -import { editor, colorUtils } from "express-document-sdk"; - -const { runtime } = addOnSandboxSdk.instance; -let uiPanel; - -// Wait for UI panel to be ready -runtime.ready.then(async () => { - // Get access to the UI panel APIs - uiPanel = await runtime.apiProxy("panel"); - - // Set up selection change handler - setupSelectionHandling(); -}); - -function setupSelectionHandling() { - editor.context.on("selectionChange", () => { - const selectionInfo = analyzeCurrentSelection(); - - // Send selection info to UI panel - uiPanel.handleSelectionUpdate(selectionInfo); - }); - - // Send initial selection state - const initialSelection = analyzeCurrentSelection(); - uiPanel.handleSelectionUpdate(initialSelection); -} - -function analyzeCurrentSelection() { - const selection = editor.context.selection; - const fullSelection = editor.context.selectionIncludingNonEditable; - - return { - count: selection.length, - totalCount: fullSelection.length, - lockedCount: fullSelection.length - selection.length, - types: [...new Set(selection.map(node => node.type))], - hasText: selection.some(node => node.type === "Text"), - hasShapes: selection.some(node => - ["Rectangle", "Ellipse"].includes(node.type) - ), - isEmpty: selection.length === 0 - }; -} - -// Export functions for UI to call -function registerSelectionUpdateHandler(handler) { - // Store the handler function from UI - runtime.exposeApi({ - applyRedToSelection() { - const selection = editor.context.selection; - const textNodes = selection.filter(node => node.type === "Text"); - - if (textNodes.length > 0) { - const redColor = colorUtils.fromHex("#FF0000"); - textNodes.forEach(textNode => { - textNode.fullContent.applyCharacterStyles({ color: redColor }); - }); - - console.log(`Applied red to ${textNodes.length} text nodes`); - } - }, - - groupSelection() { - const selection = editor.context.selection; - - if (selection.length >= 2) { - const group = editor.createGroup(); - - // Move selected elements to group - selection.forEach(node => { - node.removeFromParent(); - group.children.append(node); - }); - - // Add group to document - editor.context.insertionParent.children.append(group); - - // Select the new group - editor.context.selection = group; - - console.log(`Created group with ${selection.length} elements`); - } - }, - - clearSelection() { - editor.context.selection = []; - console.log("Selection cleared"); - }, - - registerSelectionUpdateHandler: handler - }); -} - -// Expose the registration function immediately -runtime.exposeApi({ registerSelectionUpdateHandler }); -``` - -#### TypeScript - -**UI Panel (index.ts):** - -```ts -// ui/index.ts -import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; - -interface SelectionInfo { - count: number; - totalCount: number; - lockedCount: number; - types: string[]; - hasText: boolean; - hasShapes: boolean; - isEmpty: boolean; -} - -interface DocumentSandboxAPI { - registerSelectionUpdateHandler: (handler: (info: SelectionInfo) => void) => void; - applyRedToSelection: () => void; - groupSelection: () => void; - clearSelection: () => void; -} - -let documentSandbox: DocumentSandboxAPI; - -addOnUISdk.ready.then(async () => { - // Get access to the document sandbox APIs - documentSandbox = await addOnUISdk.instance.runtime.apiProxy("documentSandbox"); - - // Set up UI elements - setupSelectionUI(); - - // Start listening for selection updates from sandbox - documentSandbox.registerSelectionUpdateHandler(handleSelectionUpdate); -}); - -function setupSelectionUI(): void { - const container = document.getElementById("selection-info"); - - if (container) { - container.innerHTML = ` -
Nothing selected
-
- - - -
-
- `; - - // Set up button handlers - const applyRedBtn = document.getElementById("apply-red-btn"); - const groupBtn = document.getElementById("group-btn"); - const clearBtn = document.getElementById("clear-selection-btn"); - - applyRedBtn?.addEventListener("click", () => { - documentSandbox.applyRedToSelection(); - }); - - groupBtn?.addEventListener("click", () => { - documentSandbox.groupSelection(); - }); - - clearBtn?.addEventListener("click", () => { - documentSandbox.clearSelection(); - }); - } -} - -function handleSelectionUpdate(selectionInfo: SelectionInfo): void { - console.log("Selection update received:", selectionInfo); - - // Update status - const statusEl = document.getElementById("selection-status"); - if (statusEl) { - if (selectionInfo.count === 0) { - statusEl.textContent = "Nothing selected"; - } else if (selectionInfo.count === 1) { - statusEl.textContent = `1 ${selectionInfo.types[0]} selected`; - } else { - statusEl.textContent = `${selectionInfo.count} elements selected`; - } - } - - // Update action buttons - const applyRedBtn = document.getElementById("apply-red-btn") as HTMLButtonElement; - const groupBtn = document.getElementById("group-btn") as HTMLButtonElement; - const clearBtn = document.getElementById("clear-selection-btn") as HTMLButtonElement; - - if (applyRedBtn) applyRedBtn.disabled = !selectionInfo.hasText; - if (groupBtn) groupBtn.disabled = selectionInfo.count < 2; - if (clearBtn) clearBtn.disabled = selectionInfo.count === 0; - - // Update details - const detailsEl = document.getElementById("selection-details"); - if (detailsEl) { - if (selectionInfo.count > 0) { - detailsEl.innerHTML = ` -

Selection Details:

-

Types: ${selectionInfo.types.join(", ")}

-

Has Text: ${selectionInfo.hasText ? "Yes" : "No"}

-

Has Shapes: ${selectionInfo.hasShapes ? "Yes" : "No"}

-

Locked Elements: ${selectionInfo.lockedCount}

- `; - } else { - detailsEl.innerHTML = ""; - } - } -} -``` - -**Document Sandbox (code.ts):** - -```ts -// sandbox/code.ts -import addOnSandboxSdk from "add-on-sdk-document-sandbox"; -import { editor, colorUtils, Node, TextNode, GroupNode, ContainerNode } from "express-document-sdk"; - -interface SelectionInfo { - count: number; - totalCount: number; - lockedCount: number; - types: string[]; - hasText: boolean; - hasShapes: boolean; - isEmpty: boolean; -} - -interface UIPanelAPI { - handleSelectionUpdate: (info: SelectionInfo) => void; -} - -const { runtime } = addOnSandboxSdk.instance; -let uiPanel: UIPanelAPI; - -// Wait for UI panel to be ready -runtime.ready.then(async () => { - // Get access to the UI panel APIs - uiPanel = await runtime.apiProxy("panel"); - - // Set up selection change handler - setupSelectionHandling(); -}); - -function setupSelectionHandling(): void { - editor.context.on("selectionChange", () => { - const selectionInfo: SelectionInfo = analyzeCurrentSelection(); - - // Send selection info to UI panel - uiPanel.handleSelectionUpdate(selectionInfo); - }); - - // Send initial selection state - const initialSelection: SelectionInfo = analyzeCurrentSelection(); - uiPanel.handleSelectionUpdate(initialSelection); -} +### Performance Guidelines -function analyzeCurrentSelection(): SelectionInfo { - const selection: readonly Node[] = editor.context.selection; - const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; - - return { - count: selection.length, - totalCount: fullSelection.length, - lockedCount: fullSelection.length - selection.length, - types: [...new Set(selection.map((node: Node) => node.type))], - hasText: selection.some((node: Node) => node.type === "Text"), - hasShapes: selection.some((node: Node) => - ["Rectangle", "Ellipse"].includes(node.type) - ), - isEmpty: selection.length === 0 - }; -} +1. **Keep handlers fast**: Minimize processing time +2. **Essential work only**: Avoid heavy computations +3. **Clean Up**: Always unregister handlers when done (`editor.context.off()`) +4. **Avoid Heavy Work**: Don't do complex calculations in selection callbacks -// Export functions for UI to call -function registerSelectionUpdateHandler(handler: (info: SelectionInfo) => void): void { - // Store the handler function from UI - runtime.exposeApi({ - applyRedToSelection(): void { - const selection: readonly Node[] = editor.context.selection; - const textNodes = selection.filter((node: Node): node is TextNode => - node.type === "Text" - ); - - if (textNodes.length > 0) { - const redColor = colorUtils.fromHex("#FF0000"); - textNodes.forEach((textNode: TextNode) => { - textNode.fullContent.applyCharacterStyles({ color: redColor }); - }); - - console.log(`Applied red to ${textNodes.length} text nodes`); - } - }, - - groupSelection(): void { - const selection: readonly Node[] = editor.context.selection; - - if (selection.length >= 2) { - const group: GroupNode = editor.createGroup(); - - // Move selected elements to group - selection.forEach((node: Node) => { - node.removeFromParent(); - group.children.append(node); - }); - - // Add group to document - const insertionParent: ContainerNode = editor.context.insertionParent; - insertionParent.children.append(group); - - // Select the new group - editor.context.selection = group; - - console.log(`Created group with ${selection.length} elements`); - } - }, - - clearSelection(): void { - editor.context.selection = []; - console.log("Selection cleared"); - }, - - registerSelectionUpdateHandler: handler - }); -} +### Communication Between UI and Document Sandbox -// Expose the registration function immediately -runtime.exposeApi({ registerSelectionUpdateHandler }); -``` +One of the most important real-world patterns is communicating selection changes from the document sandbox to your UI panel, allowing you to update the interface based on what the user has selected. -### HTML Structure - -Your `index.html` should include the selection UI container: - -```html - - - - Selection Demo - - - -
- -
- - - - -``` +For detailed information on the communication APIs, see the [Communication API reference](../../../references/document-sandbox/communication/). -### Key Communication Patterns +#### Complete Communication Example -1. **Initialization**: UI panel registers a callback with the document sandbox -2. **Event Flow**: Document sandbox listens for selection changes and sends updates to UI -3. **Action Triggers**: UI sends action requests back to document sandbox -4. **Bidirectional**: Both sides can call methods on the other +This example shows how to set up bidirectional communication between your UI panel and document sandbox for selection-based interactions. -This pattern enables rich, responsive UIs that react to document changes in real-time. +## Quick Reference & Common Patterns -## Common Patterns +Here are some frequently used patterns you can copy and adapt: ### Conditional Actions Based on Selection ```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + // Enable/disable actions based on selection type -editor.context.on("selectionChange", () => { +editor.context.on(EditorEvent.selectionChange, () => { const selection = editor.context.selection; // Communicate with your UI panel @@ -1369,12 +1130,15 @@ editor.context.on("selectionChange", () => { ### Selection-Based Properties Panel ```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + // Update properties panel based on selection -editor.context.on("selectionChange", () => { +editor.context.on(EditorEvent.selectionChange, () => { const selection = editor.context.selection; if (selection.length === 1) { - const node = selection[0]; + const node = selection[0]; // Common pattern: access first selected element // Send node properties to UI for editing const properties = { @@ -1392,6 +1156,25 @@ editor.context.on("selectionChange", () => { }); ``` +### Working with Single Selection + +Many add-ons focus on single-element operations. Here's a common pattern used throughout the documentation: + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Safe access to first selected element (used in use_text.md and other guides) +if (editor.context.hasSelection) { + const selectedNode = editor.context.selection[0]; + + // Perform operations on the selected node + if (selectedNode.type === "Text") { + // Handle text-specific operations + } +} +``` + ## FAQs #### Q: How do I get the current selection? @@ -1400,11 +1183,11 @@ editor.context.on("selectionChange", () => { #### Q: How do I listen for selection changes? -**A:** Use `editor.context.on('selectionChange', callback)` to register a selection change handler. +**A:** Use `editor.context.on(EditorEvent.selectionChange, callback)` to register a selection change handler. #### Q: How do I programmatically select elements? -**A:** Set `editor.context.selection = [node]` or `editor.context.selection = [node1, node2]` for multiple elements. +**A:** Set `editor.context.selection = node` for single elements or `editor.context.selection = [node1, node2]` for multiple elements. #### Q: What's the difference between selection and selectionIncludingNonEditable? @@ -1424,4 +1207,14 @@ editor.context.on("selectionChange", () => { #### Q: How do I unregister selection event handlers? -**A:** Use `editor.context.off('selectionChange', handlerId)` with the ID returned from the `on()` method. +**A:** Use `editor.context.off(EditorEvent.selectionChange, handlerId)` with the ID returned from the `on()` method. + +## Related Topics + +- **[Context API Reference](../../../references/document-sandbox/document-apis/classes/Context.md)** - Complete API documentation for the Context class +- **[Communication APIs](../../../references/document-sandbox/communication/)** - Learn how to communicate between document sandbox and UI panel +- **[Group Elements](./group_elements.md)** - Working with selections to create and manage groups +- **[Position Elements](./position_elements.md)** - Positioning and transforming selected elements +- **[Use Text](./use_text.md)** - Examples of working with text selections using `editor.context.selection[0]` +- **[EditorEvent Enumeration](../../../references/document-sandbox/document-apis/enumerations/EditorEvent.md)** - All available editor events +- **[Node API Reference](../../../references/document-sandbox/document-apis/classes/Node.md)** - Understanding the Node class used in selections