{"_id":"@ecl/wc-puck-editor","_rev":"2-8018eaebd277c632047089c44a9e163f","name":"@ecl/wc-puck-editor","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1":{"name":"@ecl/wc-puck-editor","version":"0.0.1","_id":"@ecl/wc-puck-editor@0.0.1","maintainers":[{"name":"yhuard","email":"huard.yan@gmail.com"},{"name":"kalin.chernev","email":"kalin.chernev@gmail.com"},{"name":"weslito","email":"wesley.deglise@gmail.com"},{"name":"emeryro","email":"emery.romain@gmail.com"},{"name":"planctus","email":"davidepuntoferrante@gmail.com"},{"name":"papegaill","email":"a@bypopcorn.com"}],"dist":{"shasum":"70c47827d306ac57e272d3b58b723b3b41b75263","tarball":"https://registry.npmjs.org/@ecl/wc-puck-editor/-/wc-puck-editor-0.0.1.tgz","fileCount":23,"integrity":"sha512-m8RRQ03kc0xB3H2OickG/JrW5G1elL5O3h9M08YCrTopsdWHGlAmN+agWCSj/EzTSby6DS1Ya09x8ebXIwUKRA==","signatures":[{"sig":"MEQCIA6dF0WYmFhnsTch0rhEh4dgid4xKnnKMAOBmV2RFoCsAiB5MV8kDW9e89JOVpibZ87sRa5uR/1ALI20Y5Ccuh7Xww==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":102085},"type":"module","gitHead":"a6992918566f5e2a60e241a084eca3e502f51f52","scripts":{"lint":"eslint .","stop":"pkill -f 'vite.*poc-puck' || true","build":"npm run clean && tsc -b && vite build && npm-run-all copy:*","clean":"rimraf dist","start":"npm run copy:scripts && vite","preview":"vite preview","copy:styles":"ncp node_modules/@ecl/ecl-webcomponents/dist/collection/build dist/build","copy:scripts":"ncp node_modules/@ecl/ecl-webcomponents/dist/collection/ecl-webcomponents.bridge.js public/libs/"},"_npmUser":{"name":"planctus","email":"davidepuntoferrante@gmail.com"},"_npmVersion":"10.9.0","description":"Proof of concept for using Puck as a page builder with [ECL Web Components](https://github.com/ec-europa/ecl-webcomponents).","directories":{},"_nodeVersion":"22.12.0","dependencies":{"react":"19.2.0","react-dom":"19.2.0","@puckeditor/core":"0.21.1","@ecl/ecl-webcomponents":"0.23.0","@tiptap/extension-link":"3.20.0","@ecl/ecl-webcomponents-react":"0.23.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.9.0","devDependencies":{"ncp":"2.0.0","vite":"7.2.4","eslint":"9.39.1","rimraf":"6.1.3","globals":"16.5.0","@eslint/js":"9.39.1","typescript":"5.9.3","@types/node":"24.10.1","npm-run-all":"4.1.5","@types/react":"19.2.5","@types/react-dom":"19.2.3","typescript-eslint":"8.46.4","@vitejs/plugin-react":"5.1.1","eslint-plugin-react-hooks":"7.0.1","eslint-plugin-react-refresh":"0.4.24"},"_npmOperationalInternal":{"tmp":"tmp/wc-puck-editor_0.0.1_1772463677111_0.24370517917370305","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@ecl/wc-puck-editor","version":"0.0.2","type":"module","publishConfig":{"access":"public"},"main":"dist-lib/index.js","module":"dist-lib/index.js","scripts":{"clean":"rimraf dist-dev dist-lib dist-npm","copy:scripts":"ncp node_modules/@ecl/ecl-webcomponents/dist/collection/ecl-webcomponents.bridge.js public/libs/","copy:dev":"npm run copy:scripts && ncp node_modules/@ecl/ecl-webcomponents/dist/collection/build dist-dev/build","copy:npm":"npm run copy:scripts && ncp node_modules/@ecl/ecl-webcomponents/dist/collection/build dist-npm/build && npm run copy:lib","copy:lib":"ncp node_modules/@ecl/ecl-webcomponents/dist/collection/build dist-lib/build","start":"npm run copy:scripts && vite","stop":"pkill -f 'vite.*poc-puck' || true","build:dev":"npm run clean && tsc -b && vite build --mode dev && npm run copy:dev","build:dev-base":"vite build --mode dev-base","build:lib":"vite build --mode lib","build:puck":"npm run clean && npm run build:dev-base && npm run build:lib && npm-run-all copy:npm","lint":"eslint .","preview":"vite preview"},"dependencies":{"@ecl/ecl-webcomponents":"0.23.0","@ecl/ecl-webcomponents-react":"0.23.0","@puckeditor/core":"0.21.1","@tiptap/extension-link":"3.20.0","react":"19.2.0","react-dom":"19.2.0"},"devDependencies":{"@eslint/js":"9.39.1","@types/node":"24.10.1","@types/react":"19.2.5","@types/react-dom":"19.2.3","@vitejs/plugin-react":"5.1.1","eslint":"9.39.1","eslint-plugin-react-hooks":"7.0.1","eslint-plugin-react-refresh":"0.4.24","globals":"16.5.0","ncp":"2.0.0","npm-run-all":"4.1.5","rimraf":"6.1.3","typescript":"5.9.3","typescript-eslint":"8.46.4","vite":"7.2.4"},"packageManager":"pnpm@10.9.0","_id":"@ecl/wc-puck-editor@0.0.2","gitHead":"b933664bf16d9782241c56c201307f47342e0aa5","description":"Proof of concept for using Puck as a page builder with [ECL Web Components](https://github.com/ec-europa/ecl-webcomponents).","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-TFleP9UZc49Su6t/43wAdXSrdogWoFTWQuPgdaZivvmt9u8PBBINHfv6h9f0obCd9FgyeXdnqHv10TD3pQQHGg==","shasum":"c00713bb9629c57c993a927f420708fbf1b05b90","tarball":"https://registry.npmjs.org/@ecl/wc-puck-editor/-/wc-puck-editor-0.0.2.tgz","fileCount":225,"unpackedSize":22102641,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCROrXGQYOViBIu6Fw52IMQsKyTwcTf+LqJRGFbQw0eWgIhANxNELPnG3c22PdZ6hDLdRiPrYeJnCD4zUYXkqdeIIRO"}]},"_npmUser":{"name":"planctus","email":"davidepuntoferrante@gmail.com"},"directories":{},"maintainers":[{"name":"yhuard","email":"huard.yan@gmail.com"},{"name":"kalin.chernev","email":"kalin.chernev@gmail.com"},{"name":"weslito","email":"wesley.deglise@gmail.com"},{"name":"emeryro","email":"emery.romain@gmail.com"},{"name":"planctus","email":"davidepuntoferrante@gmail.com"},{"name":"papegaill","email":"a@bypopcorn.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/wc-puck-editor_0.0.2_1772477372336_0.9512463449352881"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-02T15:01:17.027Z","modified":"2026-03-02T18:49:32.835Z","0.0.1":"2026-03-02T15:01:17.258Z","0.0.2":"2026-03-02T18:49:32.666Z"},"description":"Proof of concept for using Puck as a page builder with [ECL Web Components](https://github.com/ec-europa/ecl-webcomponents).","maintainers":[{"name":"yhuard","email":"huard.yan@gmail.com"},{"name":"kalin.chernev","email":"kalin.chernev@gmail.com"},{"name":"weslito","email":"wesley.deglise@gmail.com"},{"name":"emeryro","email":"emery.romain@gmail.com"},{"name":"planctus","email":"davidepuntoferrante@gmail.com"},{"name":"papegaill","email":"a@bypopcorn.com"}],"readme":"# Puck PoC\n\nProof of concept for using Puck as a page builder with [ECL Web Components](https://github.com/ec-europa/ecl-webcomponents).\n\n## Overview\n\nPuck is a React-based visual editor for building pages. It provides a complete drag-and-drop interface with component library, property editor, and publish functionality out of the box.\n\n### Layout\n\n- **Section** - Nestable section with background/padding\n- **ECL Container** - ECL max-width container wrapper\n- **Row** - Layout component with preset column configurations\n\n### Presentation\n\n- **SlideContainer** - Full-screen slide-screen wrapper (uses [Pageable.js](https://github.com/nicka/pageable) in export)\n- **Slide** - Individual full-viewport slide with background color/image support\n\n### Components\n\n- **ECL Text** - Editable text with tag, type, size, weight, color, and alignment options\n- **ECL Banner** - Hero banner with image, title, description, CTA button, and alignment options\n- **ECL Content Block** - Title with optional link and description\n- **ECL Blockquote** - With author and language options\n- **ECL Button** - With variant, size, and icon support\n- **ECL Accordion** - Container for accordion items\n- **ECL Accordion Item** - Individual accordion item with label and content slot\n\n### Status\n\n- [x] Basic setup\n- [x] Basic components (Text, Container)\n- [x] ECL Blockquote\n- [x] ECL Button with icons\n- [x] Layout components (Row with columns)\n- [x] Migrated to slot fields\n- [x] ECL Accordion (container + item pattern)\n- [x] HTML export\n- [x] Save/Load JSON\n- [x] Slot restrictions (restrictToParents)\n- [x] Component spacing (margin-top, margin-bottom)\n- [x] ECL Banner\n- [x] ECL Text (config-driven, replacing default Text)\n- [x] ECL Content Block (with innerSlots pattern)\n- [x] Canvas scroll preservation (MutationObserver fix)\n- [x] Slide-screen layout (SlideContainer + Slide with Pageable.js)\n- [ ] Fix icons in export\n- [ ] More complex components\n- [ ] Multi-page support\n\n---\n\n## Getting Started\n\n### Prerequisites\n\n- [Node.js](https://nodejs.org/) >= 18\n- npm\n\n### Quick Start\n\n```bash\nnpm install\nnpm run dev\n```\n\n### Using the Editor\n\n- Drag & drop components from the component panel\n- Click to select, edit properties in the side panel\n- **Load JSON** - Load a previously saved page\n- **Save JSON** - Download page data as JSON\n- **Export HTML** - Export as standalone HTML page\n\n### Save & Load JSON\n\nSave and load page data as JSON files for editing later.\n\n- **Save JSON**: Downloads the current page structure as `page.json`\n- **Load JSON**: Opens a file picker to load a previously saved JSON file\n\nThe JSON contains the full Puck data structure including all component props and nested content.\n\n### HTML Export\n\nExport pages as standalone HTML files with ECL dependencies from CDN.\n\nClick the **Export HTML** button in the editor header to download the page.\n\nThe exported HTML includes:\n- ECL web components JS from CDN\n- ECL CSS (custom properties + grid)\n- `data-ecl-asset-path` meta tag for icons\n\nWhen the page contains a **SlideContainer**, the export also includes Pageable.js and the slide-screen CSS/JS automatically.\n\n### Slide-Screen Mode\n\nCreate full-screen presentation-style pages with snap scrolling between slides.\n\n1. Drag a **SlideContainer** onto the page\n2. Drag **Slide** components into the container\n3. Fill each slide with content (ECL components, rows, etc.)\n4. Configure each slide's properties (background color, background image, anchor name, aria label)\n5. Export HTML - the exported page uses [Pageable.js](https://github.com/nicka/pageable) for full-viewport snap scrolling with navigation dots\n\n**Note:** In the editor, slides appear as stacked sections for easy editing. The full-screen snap-scrolling behavior only activates in the exported HTML. On mobile (<768px), the export falls back to normal scrolling.\n\n---\n\n## Technical Documentation\n\n### Project Structure\n\n```\npoc-puck/\n├── index.html                    # HTML entry point (ECL asset path meta tag)\n├── vite.config.ts\n├── src/\n│   ├── index.css                 # Base styles\n│   ├── main.tsx\n│   ├── App.tsx                   # Editor configuration\n│   ├── components/\n│   │   └── ecl/                  # ECL component integration\n│   │       ├── config.ts\n│   │       ├── createEclPuckComponent.tsx\n│   │       └── index.ts\n│   └── utils/\n│       ├── applySlotRestrictions.ts  # Slot restriction utility\n│       ├── htmlExport.tsx            # HTML export functionality\n│       └── withSpacing.tsx           # Component spacing utility\n```\n\n### ECL Web Components Integration\n\n#### Required Configuration\n\n**1. Vite config** - Exclude ECL from optimization:\n\n```typescript\n// vite.config.ts\nexport default defineConfig({\n  plugins: [react()],\n  optimizeDeps: {\n    exclude: ['@ecl/ecl-webcomponents'],\n  },\n})\n```\n\n**2. HTML meta tag** - ECL asset path:\n\n```html\n<!-- index.html -->\n<meta data-ecl-asset-path=\"/\" />\n```\n\n**3. Disable StrictMode** - Prevents double-mounting issues:\n\n```typescript\n// main.tsx\ncreateRoot(document.getElementById('root')!).render(<App />)\n```\n\n**4. Disable iframe** - ECL styles don't apply inside Puck's default iframe:\n\n```typescript\n<Puck\n  config={config}\n  data={initialData}\n  iframe={{ enabled: false }}\n/>\n```\n\n#### The Slot Content Problem\n\nECL Web Components use slots for content. React's reconciliation conflicts with slot updates.\n\n#### Solution: Key Workaround\n\nForce React to remount the component when slot content changes:\n\n```typescript\nconst keyParts = config.keyProps.map(k => String(props[k] ?? ''));\nconst contentKey = keyParts.join('-');\nreturn createElement(EclComponent, { key: contentKey, ...attributeProps }, slotContent);\n```\n\n#### Scroll Preservation\n\nThe key workaround causes a side effect: when Puck runs without an iframe, remounting components resets the `#puck-canvas-root` scroll container to the top. This is fixed with a `MutationObserver` that:\n\n1. Continuously tracks the scroll position of `#puck-canvas-root`\n2. Detects DOM mutations (caused by component remounts)\n3. Restores the scroll position in `requestAnimationFrame` when a sudden jump (>50px) is detected\n4. Skips restoration during drag operations (`[data-dnd-dragging]`) to avoid interfering with auto-scroll\n\n### Adding ECL Components\n\n#### Simple Components (Config-Driven)\n\nFor components without nested content slots (e.g., blockquote, button):\n\n**1. Define in config.ts:**\n\n```typescript\nexport const eclComponentsConfig = {\n  blockquote: {\n    component: 'EclBlockquote',\n    displayName: 'ECL Blockquote',\n    keyProps: ['quote'],\n    props: {\n      quote: { type: 'string', default: 'Quote text', label: 'Quote', isSlot: true },\n      author: { type: 'string', default: 'Author', label: 'Author' },\n      language: { type: 'select', default: 'en', label: 'Language', options: [...] },\n    },\n  },\n};\n```\n\n**2. Create component in App.tsx:**\n\n```typescript\nconst EclBlockquote = createEclPuckComponent(eclComponentsConfig.blockquote);\n```\n\n#### Container Components (With Content Slots)\n\nFor components that contain other components (e.g., accordion, tabs):\n\n**1. Define in config.ts:**\n\n```typescript\nexport const eclContainerComponentsConfig = {\n  accordion: {\n    component: 'EclAccordion',\n    displayName: 'ECL Accordion',\n    keyProps: [],\n    props: {},\n    hasSlot: true,\n  },\n  accordionItem: {\n    component: 'EclAccordionItem',\n    displayName: 'ECL Accordion Item',\n    keyProps: ['label'],\n    props: {\n      label: { type: 'string', default: 'Accordion item title', label: 'Label' },\n      expanded: { type: 'boolean', default: false, label: 'Initially Expanded' },\n    },\n    hasSlot: true,\n    autoGenerateId: true, // Uses Puck's component id as itemId\n  },\n};\n```\n\n**2. Create components in App.tsx:**\n\n```typescript\nconst EclAccordion = createEclContainerComponent(eclContainerComponentsConfig.accordion);\nconst EclAccordionItem = createEclContainerComponent(eclContainerComponentsConfig.accordionItem);\n```\n\n**3. Usage in editor:**\n- Drag an ECL Accordion onto the page\n- Drag ECL Accordion Items into the accordion's content slot\n- Each item can contain any components in its own content slot\n\n#### Components with Inner Slots\n\nFor components that have named slots for specific content (e.g., content-block with title and description slots):\n\n**1. Define in config.ts with `innerSlots`:**\n\n```typescript\ncontentBlock: {\n  component: 'EclContentBlock',\n  displayName: 'ECL Content Block',\n  keyProps: ['title', 'description'],\n  props: {\n    title: { type: 'string', default: 'Title', label: 'Title' },\n    titleLink: { type: 'string', default: '', label: 'Title Link (optional)' },\n    description: { type: 'string', default: 'Description text', label: 'Description' },\n  },\n  componentProps: { hasTitle: true, hasDescription: true }, // Static props for the component\n  innerSlots: [\n    {\n      slotName: 'title',           // The slot attribute value\n      contentProp: 'title',        // Which prop provides the content\n      wrapper: 'EclLink',          // ECL component to wrap content\n      wrapperProps: { variant: 'standalone' },\n      wrapperCondition: { prop: 'titleLink', mapTo: 'path' }, // Only wrap if titleLink has value\n      fallbackTag: 'span',         // HTML tag when condition not met\n    },\n    {\n      slotName: 'description',\n      contentProp: 'description',\n      wrapper: 'EclText',\n      wrapperProps: { tag: 'div' },\n    },\n  ],\n}\n```\n\n**2. Create component (same as simple components):**\n\n```typescript\nconst EclContentBlock = createEclPuckComponent(eclComponentsConfig.contentBlock);\n```\n\n#### Inner Slot Configuration\n\n| Property | Description |\n|----------|-------------|\n| `slotName` | The slot attribute value (e.g., 'title', 'description') |\n| `contentProp` | Which prop provides the content |\n| `wrapper` | ECL component name to wrap content (optional) |\n| `wrapperProps` | Static props for the wrapper component |\n| `wrapperCondition` | Only use wrapper if specified prop has a value |\n| `fallbackTag` | HTML tag to use when wrapper condition not met (default: 'span') |\n\n#### Prop Types\n\n| Type | UI Control | Example |\n|------|-----------|---------|\n| `string` | Text input | `{ type: 'string', default: 'value' }` |\n| `string` + `isSlot` | Textarea | `{ type: 'string', isSlot: true }` |\n| `select` | Dropdown | `{ type: 'select', options: [...] }` |\n| `boolean` | Radio buttons | `{ type: 'boolean', default: false }` |\n| `number` | Number input | `{ type: 'number', default: 0 }` |\n\n### HTML Export (Programmatic)\n\n```typescript\nimport { exportToHtml } from './utils/htmlExport';\n\n// Export current page data\nexportToHtml(config, data, {\n  title: 'My Page',\n  filename: 'page.html',\n});\n```\n\n### Slot Restrictions\n\nComponents can be restricted to only be placed inside specific parent containers using `restrictToParents`.\n\n#### Configuration\n\n```typescript\n// In config.ts\naccordionItem: {\n  // ...\n  restrictToParents: ['EclAccordion'], // Can only be placed inside EclAccordion\n}\n```\n\n#### How It Works\n\nThe `applySlotRestrictions` utility automatically processes all slots and adds restricted components to the `disallow` list of slots where they're not allowed. This includes:\n\n- **Root content area** (main page drop zone)\n- **All component slots** (Container, Row columns, etc.)\n\n```typescript\n// In App.tsx\nimport { applySlotRestrictions, buildRestrictionMap } from './utils/applySlotRestrictions';\n\nconst restrictionMap = buildRestrictionMap(eclContainerComponentsConfig);\nconst config = applySlotRestrictions(baseConfig, restrictionMap);\n```\n\n#### Key Points\n\n- **Declarative**: Define `restrictToParents` once on the child component\n- **Automatic**: No need to manually update restrictions when adding new components\n- **Uses `disallow` only**: Blacklist approach ensures other components remain unrestricted\n- **Handles root**: Configures root with a content slot to enforce restrictions at the page level\n\n### Component Spacing\n\nAll components have `Margin Top` and `Margin Bottom` fields for controlling spacing between components.\n\n#### Available Values\n\n| Value | Pixels |\n|-------|--------|\n| 0 | 0px (default) |\n| 8px | 8px |\n| 16px | 16px |\n| 24px | 24px |\n| 32px | 32px |\n| 48px | 48px |\n| 64px | 64px |\n\n#### How It Works\n\nThe `applySpacingToConfig` utility wraps all components to add margin fields:\n\n```typescript\n// In App.tsx\nimport { applySpacingToConfig } from './utils/withSpacing';\n\nconst config = applySpacingToConfig(baseConfig);\n```\n\nMargins are only applied when non-zero, keeping the exported HTML clean.\n\n### Layout Components\n\nECL uses a 12-column grid system. The Row component provides preset column layouts.\n\n#### Row Component\n\nUses slot fields for each column:\n\n```typescript\nRow: {\n  fields: {\n    layout: {\n      type: 'select',\n      label: 'Layout',\n      options: [\n        { label: '1 column (12)', value: '12' },\n        { label: '2 columns (6+6)', value: '6-6' },\n        { label: '3 columns (4+4+4)', value: '4-4-4' },\n        // etc.\n      ],\n    },\n    column0: { type: 'slot' },\n    column1: { type: 'slot' },\n    column2: { type: 'slot' },\n    column3: { type: 'slot' },\n  },\n  render: ({ layout, column0: Col0, column1: Col1, ... }) => {\n    const widths = layout.split('-');\n    return (\n      <div className=\"ecl-row\">\n        {widths.map((width, index) => (\n          <div key={index} className={`ecl-col-${width}`}>\n            <ColContent />\n          </div>\n        ))}\n      </div>\n    );\n  },\n}\n```\n\n#### Available Layouts\n\n| Layout | Description |\n|--------|-------------|\n| `12` | Full width |\n| `6-6` | Two equal columns |\n| `4-8` / `8-4` | 1/3 + 2/3 |\n| `3-9` / `9-3` | 1/4 + 3/4 |\n| `4-4-4` | Three equal columns |\n| `3-6-3` | Centered content |\n| `3-3-3-3` | Four equal columns |\n\n#### Why Preset Layouts?\n\nNested drop zones make it difficult to drag multiple columns into a row. Preset layouts solve this by generating all columns upfront, each with its own slot.\n\n### Slot Fields\n\nPuck uses slot fields for nested content (replacing deprecated DropZones):\n\n```typescript\nContainer: {\n  fields: {\n    background: { type: 'text', label: 'Background' },\n    content: { type: 'slot' },\n  },\n  defaultProps: {\n    background: '#f5f5f5',\n    content: [],\n  },\n  render: ({ background, content: Content }) => (\n    <div style={{ background }}>\n      <Content />\n    </div>\n  ),\n}\n```\n\n### Slide-Screen Layout\n\nThe slide-screen feature uses a **SlideContainer + Slide** pattern (similar to Accordion/AccordionItem).\n\n#### Components\n\n- **SlideContainer** - Wrapper with configurable animation duration and navigation dots toggle. Renders `data-*` attributes read by the export script.\n- **Slide** - Full-viewport section with `data-anchor` (auto-generated from Puck's component ID, or user-defined for meaningful URL hashes). Restricted to SlideContainer via `restrictToParents`.\n\n#### How It Works\n\n- **In the editor**: Slides are stacked sections with visual boundaries. No Pageable.js behavior.\n- **In export**: The `htmlExport.tsx` detects if the data contains a SlideContainer. If so, it includes:\n  - [Pageable.js](https://github.com/nicka/pageable) v0.6.8 (JS + CSS) from CDN\n  - Slide-screen CSS (full-viewport sections, navigation pips styling, mobile fallback)\n  - An initialization script that flattens the DOM (Puck wraps components in `<div>`s, but Pageable.js needs sections as direct children), then initializes Pageable.js with the container's settings\n- **Mobile (<768px)**: Pageable.js is disabled, sections use normal scrolling, navigation dots are hidden\n\n#### Slide Properties\n\n| Property | Description |\n|----------|-------------|\n| `anchor` | Anchor name for URL hash navigation (auto-generated if empty) |\n| `ariaLabel` | Accessibility label for the section |\n| `background` | CSS background color |\n| `backgroundImage` | URL for a background image (cover, centered) |\n\n#### SlideContainer Properties\n\n| Property | Description |\n|----------|-------------|\n| `animation` | Animation duration in ms (default: 600) |\n| `pips` | Show navigation dots (default: yes) |\n\n### Known Issues\n\n1. **ECL carousel bug** - Const reassignment error. Workaround: exclude from Vite optimization.\n2. **iframe and styles** - ECL styles don't apply in iframe. Workaround: disable iframe.\n3. **Slot content updates** - Requires key workaround to force remount.\n4. **Nested drop zones** - Adding items to slots with existing content can be tricky. CSS padding on `[data-puck-dropzone]` helps provide drop targets.\n5. **Canvas scroll reset** - When using Puck without iframe (`iframe: { enabled: false }`), the key-based remount workaround causes the `#puck-canvas-root` scroll container to reset to the top whenever a selected component re-renders (on edit or drop). Workaround: a `MutationObserver` in `createEclPuckComponent.tsx` tracks scroll position and restores it after DOM mutations, skipping during drag operations to avoid interfering with auto-scroll.\n","readmeFilename":"README.md"}