feat(experience-lwc-generate): add native lightning/modal example, mark custom composable modal as fallback

This commit is contained in:
anatolii 2026-08-03 12:42:19 +03:00
parent fe3e2edad0
commit d832b270af
No known key found for this signature in database
5 changed files with 101 additions and 2 deletions

View File

@ -168,7 +168,8 @@ Local Dev commands install just-in-time on first run. They are long-running proc
- [assets/jest-test/componentName.test.js.example](assets/jest-test/componentName.test.js.example) — Jest test template (copy and rename, remove `.example` suffix)
- [assets/message-channel/lmsPublisher.js](assets/message-channel/lmsPublisher.js) — LMS publisher pattern
- [assets/message-channel/lmsSubscriber.js](assets/message-channel/lmsSubscriber.js) — LMS subscriber pattern
- [assets/modal-component/modalComponent.js](assets/modal-component/modalComponent.js) — modal with focus trap and ESC handling
- [assets/native-modal-component/nativeModalComponent.js](assets/native-modal-component/nativeModalComponent.js) — default modal pattern using the native `lightning/modal` service
- [assets/modal-component/modalComponent.js](assets/modal-component/modalComponent.js) — custom composable modal (focus trap, ESC handling); only for cases `lightning/modal` can't cover
- [assets/record-picker/recordPicker.js](assets/record-picker/recordPicker.js) — record picker with search
- [assets/state-store/store.js](assets/state-store/store.js) — reactive state store for cross-component state
- [assets/typescript-component/typescriptComponent.ts](assets/typescript-component/typescriptComponent.ts) — TypeScript-enabled component (Spring '26)

View File

@ -0,0 +1,21 @@
<template>
<lightning-modal-header label={label}></lightning-modal-header>
<lightning-modal-body>
<p>{content}</p>
<slot></slot>
</lightning-modal-body>
<lightning-modal-footer>
<lightning-button
label="Cancel"
onclick={handleCancel}
class="slds-m-right_x-small">
</lightning-button>
<lightning-button
variant="brand"
label="Save"
onclick={handleSave}>
</lightning-button>
</lightning-modal-footer>
</template>

View File

@ -0,0 +1,62 @@
/**
* NATIVE MODAL TEMPLATE (lightning/modal)
*
* Uses the platform's built-in modal service instead of a hand-rolled
* backdrop/focus-trap implementation. LightningModal already provides:
* - focus trap and initial focus
* - ESC-to-close
* - ARIA roles/labels
* - size variants (small / medium / large / full)
* - a promise-based result returned from open()
*
* Supported in Lightning Experience, the Salesforce app, and Experience
* Builder sites (confirm current support for your specific site template
* before relying on it as the only implementation).
*
* Replace: nativeModalComponent yourModalName
* Replace: NativeModalComponent YourModalName
*
* How a parent opens this modal
* import YourModalName from 'c/yourModalName';
*
* async handleOpenModal() {
* const result = await YourModalName.open({
* size: 'small', // small | medium | large | full
* description: 'Accessible description of the modal purpose',
* label: 'Confirm Action', // passed through as @api label
* content: 'Are you sure?' // any custom @api input props
* });
*
* if (result === 'save') {
* // user confirmed
* }
* }
*
*/
import { api } from 'lwc';
import LightningModal from 'lightning/modal';
export default class NativeModalComponent extends LightningModal {
// ═══════════════════════════════════════════════════════════════════════
// PUBLIC API (@api) - Passed in via YourModalName.open({ ... })
// ═══════════════════════════════════════════════════════════════════════
@api label;
@api content;
// ═══════════════════════════════════════════════════════════════════════
// EVENT HANDLERS
// ═══════════════════════════════════════════════════════════════════════
handleCancel() {
// Resolve the promise returned by open() with a falsy/known value
this.close('cancel');
}
handleSave() {
// Resolve the promise returned by open() with the result the
// caller needs. Close is explicit, so validation can prevent it:
// guard this call behind your own validity check before closing.
this.close('save');
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
NATIVE MODAL TEMPLATE - METADATA
Modals that extend lightning/modal are opened imperatively via
YourModalName.open({ ... }) from another component's JS — they are
not placed declaratively on a page, so isExposed stays false and no
targets are declared.
-->
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<isExposed>false</isExposed>
</LightningComponentBundle>

View File

@ -612,7 +612,9 @@ const BATCH_CREATE = gql`
## Modal Component Pattern
Based on [James Simone's composable modal pattern](https://www.jamessimone.net/blog/joys-of-apex/lwc-composable-modal/).
**Default to the native `lightning/modal` service** — see [assets/native-modal-component](../assets/native-modal-component/nativeModalComponent.js). It ships focus trap, ESC-to-close, ARIA, and size variants out of the box, so there's nothing to reinvent. Confirm it's supported on the target surface (some Experience Builder site templates historically lagged Lightning Experience support) before falling back to a custom implementation.
Only reach for a custom composable modal below when `lightning/modal` genuinely can't cover the case (e.g. a confirmed gap on your site template, or composition needs beyond what `LightningModal` slots support). It's based on [James Simone's composable modal pattern](https://www.jamessimone.net/blog/joys-of-apex/lwc-composable-modal/); see [assets/modal-component](../assets/modal-component/modalComponent.js) for the fuller version with cleanup and multi-slot composition. Prefer a `save`/`cancel` `CustomEvent` over a function-reference `@api` prop for the save handler — function references can't be passed from Aura markup, which breaks on Aura-based Experience Builder pages.
```javascript
// composableModal.js