diff --git a/CHANGELOG.md b/CHANGELOG.md index 4785b27..76b9261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## 2.3.0 + BREAKING: a modal dialog now freezes the page behind it by default; pass backgroundScroll: true to restore the old behaviour (non-modal dialogs are unchanged) + fix: locking the page no longer shifts fixed elements sideways, because the scrollbar gutter is reserved instead of padded away + fix: an anchored popover now follows its anchor out of the viewport instead of sticking to the viewport edge feat: a dark palette behind data-theme="dark", with data-theme="auto" following prefers-color-scheme; opt-in, so an existing site is unchanged on upgrade feat: every colour in the stylesheet is now a --dialogify-* custom property, so a page can retheme the dialog without overriding rules feat: --dialogify-icon-filter re-tints the built-in single-colour icons, whose fill is baked into the inline SVGs diff --git a/README.md b/README.md index 65455a5..5ae4870 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,21 @@ # Dialogify -A javascript plugin for creating dialog, implements with HTMLDialogElement. +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/OneupNetwork/dialogify/ci.yml?branch=master)](https://github.com/OneupNetwork/dialogify/actions/workflows/ci.yml) +[![NPM Version](https://img.shields.io/npm/v/%40oneup_network%2Fdialogify)](https://www.npmjs.com/package/@oneup_network/dialogify) +[![GitHub License](https://img.shields.io/github/license/OneupNetwork/dialogify)](LICENSE) + +A dialog plugin built on the native `` element. + +[繁體中文](README.zh-TW.md) + +The browser does the heavy lifting — the top layer, the backdrop, focus trapping +and Esc to close all come from `` itself. Dialogify adds the +chrome around it: a title bar, a button row, ajax content, forms, drawers, +toasts and anchored popovers. + +**[Documentation and live examples →](https://oneupnetwork.github.io/dialogify/)** + +![basic dialogify](docs/img/screenshot1.png) ## Installation @@ -8,373 +23,56 @@ A javascript plugin for creating dialog, implements with HTMLDialogElement. npm install @oneup_network/dialogify jquery ``` -`jquery` is a **peer dependency** — install it alongside dialogify (or load it -globally before the browser bundle). +jQuery is a **peer dependency**, so install it alongside dialogify. -### Browser (global build) +### Browser ```html ``` -The browser build injects its own CSS, so no extra stylesheet is required. +The browser build reads jQuery from the global scope. ### Module (ESM / CommonJS) ```javascript import Dialogify from '@oneup_network/dialogify'; -// optionally link the standalone stylesheet: @oneup_network/dialogify/css ``` +### Styles + +Every build carries its own CSS, so no extra stylesheet is required. To style +the dialog yourself, link the standalone `@oneup_network/dialogify/css` with +`id="dialogifyCss"` before dialogify loads and the injection is skipped. + ## Basic usage +Build a dialog by chaining, then open it. `show()` and `showModal()` return a +promise that resolves with the dialog's `returnValue` once it closes. + ```javascript new Dialogify('dialog content') .title('dialog title') .buttons([{ type: Dialogify.BUTTON_PRIMARY }]) .showModal(); - -// alternate alert, confirm and prompt -(async function () { - await Dialogify.alert('Alert!!'); - - if (await Dialogify.confirm('Yes or no?')) { - // blah.. - } - - const answer = await Dialogify.prompt('Question?'); - // if (answer == blah...) -})(); -``` - -![basic dialogify](docs/img/screenshot1.png) - -## Working with an open dialog - -### Awaiting the result - -`show()` and `showModal()` resolve with the dialog's `returnValue` once it closes: - -```javascript -const dialog = new Dialogify('Are you sure?').buttons([ - { text: 'Yes', type: Dialogify.BUTTON_PRIMARY, click: () => dialog.close('yes') }, - { text: 'No', click: () => dialog.close('no') } -]); - -const answer = await dialog.showModal(); // 'yes' or 'no' -``` - -### Preventing a close - -`beforeclose` fires before every close — buttons, the close button, `ESC`, the -backdrop, a `
` submit and `close()` itself. Cancel it to -keep the dialog open: - -```javascript -dialog.on('beforeclose', async (event) => { - if (isDirty) { - event.preventDefault(); - if (await Dialogify.confirm('Discard your changes?')) { - isDirty = false; - dialog.close(); - } - } -}); -``` - -### Updating content - -```javascript -dialog.setContent('

Step 2

'); // replaces the body, keeps title and buttons -dialog.getContent(); - -await dialog.load('/ajax/step2', { id: 7 }); // fetch into the body, with a loading state -``` - -### Loading state - -```javascript -dialog.setLoading(true); // overlay across the dialog content -dialog.setLoading(false); -dialog.isLoading(); - -dialog.updateButton('save', { loading: true }); // busy spinner, disabled, swaps in loadingText -``` - -### Forms - -The content is wrapped in `` by default, so native -validation and `FormData` work out of the box: - -```javascript -const dialog = new Dialogify('').buttons([ - { - id: 'save', - text: 'Save', - type: Dialogify.BUTTON_PRIMARY, - loadingText: 'Saving...', - click: async () => { - if (!dialog.validate()) { - return; // reports the first invalid field - } - - dialog.updateButton('save', { loading: true }); - await save(dialog.formValues()); // { title: '...' } - dialog.close('saved'); - } - } -]); -``` - -`formValues()` returns a plain object (repeated fields become arrays); -`formData()` returns a `FormData`, or `null` when `useDialogForm` is `false`. - -### Managing buttons - -```javascript -dialog.addButton({ id: 'more', text: 'More' }); -dialog.addButton({ id: 'first', text: 'First' }, { prepend: true }); -dialog.updateButton('more', { text: 'Fewer', type: Dialogify.BUTTON_DANGER, disabled: true }); -dialog.removeButton('more'); -dialog.getButton('save'); // the button's jQuery object -``` - -## Drawers, toasts and popovers - -### Drawer - -`position` slides the dialog in from an edge instead of centring it: - -```javascript -new Dialogify('

Filters

', { position: Dialogify.POSITION_RIGHT }).showModal(); -``` - -```html -Filters ``` -Accepts `left`, `right`, `top` and `bottom`. - -### Toast - -A lightweight, auto-dismissing notification. Toasts stack in a fixed corner -container and are not modal, so they never block the page: +`alert`, `confirm` and `prompt` have one-line replacements: ```javascript -Dialogify.toast('Saved'); -Dialogify.toast('Could not save', { - type: Dialogify.TOAST_ERROR, // TOAST_INFO (default), TOAST_SUCCESS, TOAST_WARNING - position: 'bottom-right', // or Dialogify.TOAST_BOTTOM_RIGHT; default top-right - duration: 5000, // 0 keeps it open until closed - title: 'Error', - closable: true -}); -``` - -Hovering a toast pauses its auto-dismiss timer. `Dialogify.toast()` returns the -dialog instance, so `close()` and the usual events are available. - -### Anchored popover - -`showAt()` positions a non-modal dialog next to another element, for dropdowns -and popover cards. It flips to the opposite side when the preferred placement -would overflow the viewport, and follows the anchor on scroll and resize: - -```javascript -new Dialogify('
    ...
', { closable: false, useDialogForm: false }).showAt('#menu-button', { - placement: 'bottom', // top | bottom | left | right - align: 'start', // start | center | end - offset: 8, - toggle: true // clicking the anchor again closes it, default true -}); -``` - -```html -… -``` - -```javascript -document.querySelector('dialog[is="bahamut-dialogify"]').showAt(); -``` - -A popover is dismissed by a click anywhere outside it. Clicking the anchor that -opened it toggles it shut: that click is swallowed, so the handler that called -`showAt()` does not run and immediately reopen it. Clicking a _different_ -trigger is left alone, so it closes this popover and opens its own. Pass -`toggle: false` to let the anchor click through, or `closable: false` to keep -the popover open until you close it yourself. - -### Animation - -Drawers slide in and out from their edge, toasts fade and scale, and popovers -fade in place. The exit animation runs after the dialog has closed, so a dialog -with `autoRemove` stays in the DOM until it finishes. A closing toast collapses -its own height at the same time, so the toasts stacked below it slide up instead -of jumping when it is removed. All of them are disabled under -`prefers-reduced-motion: reduce`. - -### Stacking (`z-index`) - -`showModal()` promotes a dialog to the browser's top layer, which always paints -above the page, so `z-index` never applies to modal dialogs. Everything else — -`show()`, `showAt()` popovers and toasts — stays in the normal stacking context -and has to out-stack the rest of the page. - -The defaults are deliberately high so a toast is not hidden behind a sticky -header: - -| Custom property | Default | Applies to | -| --------------------------- | ------- | ------------------------------ | -| `--dialogify-z-index` | `1000` | Non-modal dialogs and popovers | -| `--dialogify-toast-z-index` | `1010` | The toast container | - -Override them anywhere in your own CSS to fit a different layering scheme: +await Dialogify.alert('Alert!!'); -```css -:root { - --dialogify-z-index: 500; - --dialogify-toast-z-index: 510; +if (await Dialogify.confirm('Yes or no?')) { + // ... } -``` - -The bundled stylesheet only reads these properties (they are `var()` fallbacks, -never declared by the library), so your declaration wins regardless of source -order — even though the browser build injects its stylesheet last. - -## Theming -Every colour in the bundled stylesheet is read through a custom property, so you -can retheme the dialog without fighting selectors: - -```css -:root { - --dialogify-primary: #6741d9; - --dialogify-primary-hover: #7950f2; - --dialogify-surface: #fdfdfd; -} +const answer = await Dialogify.prompt('Question?'); ``` -The library never declares these properties itself — the light values are only -`var()` fallbacks — so your declaration wins regardless of source order, even -though the browser build injects its stylesheet last. - -
-All theme properties - -| Custom property | Default | Applies to | -| -------------------------------- | -------------------------- | ------------------------------------ | -| `--dialogify-surface` | `#fff` | Dialog background | -| `--dialogify-text` | `#464646` | Body text | -| `--dialogify-heading` | `#00555f` | `title()` and the close button | -| `--dialogify-muted` | `#a6a6a6` | Secondary text | -| `--dialogify-divider` | `#e0e6e8` | `
` | -| `--dialogify-link` | `#117e96` | Links in the body | -| `--dialogify-link-hover` | `#126e7d` | Links on hover | -| `--dialogify-focus-ring` | `#117e96` | `:focus-visible` outlines | -| `--dialogify-icon-filter` | `none` | Filter applied to the built-in icons | -| `--dialogify-shadow` | three-layer drop shadow | Dialog shadow | -| `--dialogify-loading-overlay` | `rgba(255, 255, 255, .75)` | `setLoading()` overlay | -| `--dialogify-button-bg` | `#e5e5e5` | Default button | -| `--dialogify-button-hover-bg` | `#dcdcdc` | Default button on hover | -| `--dialogify-button-text` | `#a6a6a6` | Default button label | -| `--dialogify-primary` | `#117e96` | `BUTTON_PRIMARY` | -| `--dialogify-primary-hover` | `#126e7d` | `BUTTON_PRIMARY` on hover | -| `--dialogify-on-primary` | `#fff` | `BUTTON_PRIMARY` label | -| `--dialogify-danger` | `#f44336` | `BUTTON_DANGER` | -| `--dialogify-danger-hover` | `#de2427` | `BUTTON_DANGER` on hover | -| `--dialogify-on-danger` | `#fff` | `BUTTON_DANGER` label | -| `--dialogify-danger-shadow` | red-tinted drop shadow | `.btn-danger-shadow` | -| `--dialogify-field-bg` | `#fff` | `.text-field` background | -| `--dialogify-field-border` | `#d9d9d9` | `.text-field` border | -| `--dialogify-field-text` | `#00555f` | `.text-field` text | -| `--dialogify-field-focus-border` | `#117e96` | `.text-field` border on focus | -| `--dialogify-field-placeholder` | `#b3b3b3` | `.text-field` placeholder | -| `--dialogify-error` | `#de2427` | `.is-error` fields | -| `--dialogify-toast-info` | `#117e96` | Toast status bar, `info` | -| `--dialogify-toast-success` | `#2f9e44` | Toast status bar, `success` | -| `--dialogify-toast-warning` | `#e8a33d` | Toast status bar, `warning` | -| `--dialogify-toast-error` | `#de2427` | Toast status bar, `error` | -| `--dialogify-toast-close` | `#8d8d8d` | Toast close button | -| `--dialogify-toast-close-hover` | `#464646` | Toast close button on hover | -| `--dialogify-backdrop` | `#000` | Modal backdrop | -| `--dialogify-backdrop-opacity` | `0.7` | Modal backdrop opacity | - -
- -### Dark theme - -A dark palette ships with the stylesheet, behind `data-theme`: - -```html - -``` - -| `data-theme` | Result | -| ------------ | ---------------------------------------------------- | -| absent | Light (the default, unchanged from earlier releases) | -| `light` | Light | -| `dark` | Dark | -| `auto` | Follows `prefers-color-scheme` | - -The attribute can sit on any ancestor of the dialog — `` is the usual -choice — and can be flipped at runtime without reloading: - -```js -document.documentElement.dataset.theme = 'dark'; -``` - -Dark mode is **opt-in on purpose**: without the attribute the dialog stays light -even when the visitor's system is set to dark, so an existing site never changes -appearance on upgrade. Use `data-theme="auto"` to follow the system. - -The dark rules only set custom properties, never a real declaration, so if you -already ship your own overrides for `.dialogify` they keep winning by source -order exactly as before. Be aware that anything your stylesheet leaves alone -will pick up the dark palette, which can mix the two; declare -`--dialogify-*` properties instead of rules if you want full control. - -The built-in icons are single-colour SVGs with the fill baked in, so they are -re-tinted with `--dialogify-icon-filter` rather than swapped. This also applies -to a custom `closeButton.image`; set `--dialogify-icon-filter: none` to opt out. - -## Accessibility - -- `title()` links the heading to the dialog with `aria-labelledby`. -- The close button is exposed as a button, is keyboard focusable and activates - with Enter / Space, and is labelled from the locale. -- Decorative icons are hidden from assistive technology; the loading indicator - and toasts announce themselves with `role="status"`. -- Buttons in their loading state are marked `aria-busy`. -- Animations are disabled under `prefers-reduced-motion`. - -Modal dialogs get focus trapping and focus restoration from the native -`` element itself. - -## Dialog content is HTML - -The `source` passed to `new Dialogify(source)` and the `message` passed to -`Dialogify.alert` / `confirm` / `prompt` are inserted as **HTML** — this is an -intentional feature so you can render rich content: - -```javascript -new Dialogify('

Rich HTML content

').showModal(); -``` - -`source` is resolved as follows: - -- a string starting with `#` is treated as a selector and its inner HTML is cloned; -- a string starting with the ajax prefix (default `/ajax/`) is fetched and injected; -- any other string is inserted as raw HTML. - -> ⚠️ **Security:** because content is rendered as HTML, always escape/sanitize any -> untrusted or user-supplied input before passing it to Dialogify to avoid XSS. - -## Declarative usage (``) - -Besides the programmatic API, a dialog can be written directly in HTML using a -[customized built-in element](https://developer.mozilla.org/docs/Web/API/Web_components/Using_custom_elements#customized_built-in_elements). -The element is registered automatically when dialogify is loaded: +A dialog can also be written straight into the page as a +[customized built-in element](https://developer.mozilla.org/docs/Web/API/Web_components/Using_custom_elements#customized_built-in_elements), +with options as attributes and the whole API on the element itself: ```html @@ -385,222 +83,68 @@ The element is registered automatically when dialogify is loaded: ``` ```javascript -const dialog = document.querySelector('dialog[is="bahamut-dialogify"]'); -dialog.showModal(); -``` - -The element name is prefixed because the custom elements spec requires a hyphen -in the name — `is="dialogify"` is not a valid custom element name. - -Unlike `new Dialogify()`, a declarative dialog is **not** removed from the DOM when -it closes, so it can be shown again. - -### Methods - -`document.querySelector(...)` returns the element itself, and the dialogify API is -available on it: - -| Method | Description | -| ---------------------------- | ------------------------------------------------- | -| `show()` | Show the dialog (non-modal); resolves on close | -| `showModal()` | Show the dialog as a modal; resolves on close | -| `showAt(anchor?, options?)` | Show anchored to another element | -| `reposition()` | Recompute the anchored position | -| `close(returnValue?)` | Close the dialog (fires `beforeclose`) | -| `isOpen()` | Whether the dialog is open | -| `setTitle(text)` | Set the dialog title | -| `setContent(html)` | Replace the body, keeping title and buttons | -| `getContent()` | The current body HTML | -| `load(url, data?)` | Load body content over ajax, with a loading state | -| `setLoading(bool)` | Toggle the loading overlay | -| `isLoading()` | Whether the loading overlay is shown | -| `validate()` | Run native form validation | -| `formData()` | The dialog form as `FormData` | -| `formValues()` | The dialog form as a plain object | -| `buttons(buttons, options?)` | Set the buttons programmatically | -| `addButton(button, opts?)` | Append (or prepend) one button | -| `updateButton(id, changes)` | Update a button in place | -| `removeButton(id)` | Remove a button | -| `getButton(id)` | A button's jQuery object | -| `on/off(event, handler)` | Bind/unbind a dialogify instance event | -| `.dialogify` | The backing `Dialogify` instance | - -> `setTitle()` is used instead of `title()` because `HTMLElement.title` is the -> native tooltip property and must keep its standard behaviour. - -### Options as attributes - -| Attribute | Option | Notes | -| ------------------- | ------------------ | ------------------------------------------------------------- | -| `dialog-title` | — | Dialog title (`title` is the native tooltip) | -| `size="large"` | `size` | Maps to `Dialogify.SIZE_LARGE`; any other value is used as-is | -| `position` | `position` | Drawer edge: `left` / `right` / `top` / `bottom` | -| `closable` | `closable` | Show the close button | -| `fixed` | `fixed` | Use `position: fixed` | -| `background-scroll` | `backgroundScroll` | Allow scrolling behind the dialog | -| `use-dialog-form` | `useDialogForm` | Wrap content in `` | -| `src` | — | Load content from this URL instead of the inline markup | -| `ajax-prefix` | `ajaxPrefix` | Ajax prefix used to resolve `src` | -| `options='{...}'` | — | Any option as a JSON object | -| `buttons-position` | — | `left` / `center` / `right` (default `right`) | -| `anchor` | — | Selector for the anchor element used by `showAt()` | -| `placement` | — | `top` / `bottom` / `left` / `right` for `showAt()` | -| `align` | — | `start` / `center` / `end` for `showAt()` | -| `offset` | — | Gap in pixels for `showAt()` | - -> ⚠️ Because attributes are strings, these booleans are **value-based**, not -> presence-based: use `closable="false"` to disable. A bare attribute, `"true"`, -> or any other value means `true`. This differs from standard HTML boolean -> attributes. - -Options that cannot be expressed as attributes (callbacks, style objects) can be -assigned before the dialog is first shown: - -```javascript -dialog.options = { - ajaxComplete() { - /* ... */ - } -}; -``` - -### Buttons - -Add a ` - - -``` - -> Write ``, not ` - -``` - -```javascript -dialog.updateButton('save', { loading: true }); +document.querySelector('dialog[is="bahamut-dialogify"]').showModal(); ``` -### Events - -`close` and `cancel` are native `` events; `show` and `beforeclose` are -dispatched by dialogify. They can all be bound in the usual ways: - -```javascript -dialog.addEventListener('show', () => {}); -dialog.onshow = () => {}; -dialog.onclose = () => {}; - -dialog.addEventListener('beforeclose', (e) => e.preventDefault()); // keeps it open -dialog.onbeforeclose = () => false; // same thing -``` +## Documentation -They can also be bound in markup. `onclose` and `oncancel` are native inline -handlers and behave exactly as the browser defines. In addition, dialogify -resolves a **function name** on `onshow`, `onclose`, `oncancel` and -`onbeforeclose`: +Everything else lives on the documentation site, with a runnable example next to +each feature: -```html - -``` +| Topic | What you will find | +| ---------------------------------------------------------------------------- | -------------------------------------------------------------- | +| [Getting started](https://oneupnetwork.github.io/dialogify/#getting-started) | Installing and opening a first dialog | +| [Core concepts](https://oneupnetwork.github.io/dialogify/#concepts) | Modal vs non-modal, chaining, promises, lifecycle | +| [Creating a dialog](https://oneupnetwork.github.io/dialogify/#create) | Every constructor option | +| [Content](https://oneupnetwork.github.io/dialogify/#content-api) | Titles, HTML content, ajax loading, loading state | +| [Forms](https://oneupnetwork.github.io/dialogify/#forms) | Validation and reading values back | +| [Buttons](https://oneupnetwork.github.io/dialogify/#buttons) | Defining them and driving them at runtime | +| [Events](https://oneupnetwork.github.io/dialogify/#events) | `show`, `close`, `cancel` and intercepting a close | +| [Drawer, toast, popover](https://oneupnetwork.github.io/dialogify/#variants) | Edge drawers, notifications and anchored popovers | +| [Shortcuts](https://oneupnetwork.github.io/dialogify/#shortcuts) | `alert`, `confirm`, `prompt`, `toast` | +| [Declarative usage](https://oneupnetwork.github.io/dialogify/#declarative) | ``, attributes, buttons, events | +| [Theming](https://oneupnetwork.github.io/dialogify/#theming) | Custom properties, dark theme, `z-index` | +| [Locale](https://oneupnetwork.github.io/dialogify/#locale) | Built-in languages and adding your own | +| [API reference](https://oneupnetwork.github.io/dialogify/#api) | Every method, option and constant | -The name is looked up in `Dialogify.handlers` first, then on `window` (dotted -paths are supported), and it is resolved when the event fires — so handlers may be -defined after the markup. Values that are not plain identifiers are ignored by -dialogify and left to the browser's native inline handler support, so no code is -ever evaluated by dialogify itself. +Content is inserted as **HTML**, which makes rich layouts easy but means any +user-supplied value has to be escaped before it goes in. -```javascript -Dialogify.handlers.myHandler = function () { - // `this` is the dialog element -}; -``` +## Browser support -### Browser support +Any browser with a native `` element: Chrome 94+, Firefox 98+ and +Safari 15.4+. The bundles target `es2022`. -Customized built-in elements are not supported in Safari/WebKit, so -`` is never upgraded there. An optional bundle of -[`@ungap/custom-elements`](https://github.com/ungap/custom-elements) ships with -dialogify; load it **before** dialogify to enable the declarative syntax: - -```html - - -``` - -```javascript -import '@oneup_network/dialogify/custom-elements'; -import Dialogify from '@oneup_network/dialogify'; -``` - -It is a no-op in browsers that already support the feature, and the programmatic -API works everywhere without it. - -## Optional legacy polyfill - -Modern browsers support `` natively, so no polyfill is bundled by default. -To support legacy browsers, load the optional polyfill **before** dialogify; it -exposes `window.dialogPolyfill`, which dialogify detects at runtime: +Two optional bundles cover the gaps, and both must be loaded **before** +dialogify: ```html + + + ``` -## Usage and examples - -[https://oneupnetwork.github.io/dialogify/](https://oneupnetwork.github.io/dialogify/) - ## Dependencies -- [jQuery](https://jquery.com/) (peer dependency, `>=3.0.0`, jQuery 4 included) - -## Browser compatibility - -Every browser with a native `` element (Chrome 94+, Firefox 98+, -Safari 15.4+); the bundles are built for `es2022`. Older browsers can load the -optional `dialog-polyfill` bundle. +- [jQuery](https://jquery.com/) — peer dependency, `>=3.0.0` (jQuery 4 included) ## Designed by [Phoebe](https://github.com/Phoebe1226) -## Contribute +## Contributing -- Fork & clone this repo - ``` - npm install - npm run build - ``` -- Create branch and commit your changes -- Open a pull request +```bash +npm install +npm run build +npm test +``` -Feel free to contribute +Fork the repo, branch off master, and open a pull request. Contributions are +welcome. ## License -MIT +[MIT](LICENSE) diff --git a/README.zh-TW.md b/README.zh-TW.md new file mode 100644 index 0000000..b5f5b10 --- /dev/null +++ b/README.zh-TW.md @@ -0,0 +1,146 @@ +# Dialogify + +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/OneupNetwork/dialogify/ci.yml?branch=master)](https://github.com/OneupNetwork/dialogify/actions/workflows/ci.yml) +[![NPM Version](https://img.shields.io/npm/v/%40oneup_network%2Fdialogify)](https://www.npmjs.com/package/@oneup_network/dialogify) +[![GitHub License](https://img.shields.io/github/license/OneupNetwork/dialogify)](LICENSE) + +以原生 `` 元素打造的燈箱套件。 + +[English](README.md) + +繁重的工作交給瀏覽器 — top layer、backdrop、焦點鎖定與 Esc +關閉全都由 `` 原生提供。Dialogify 補上外圍的部分:標題列、按鈕列、ajax +內容、表單,以及 drawer、toast 與 anchored popover。 + +**[說明文件與線上範例 →](https://oneupnetwork.github.io/dialogify/)** + +![basic dialogify](docs/img/screenshot1.png) + +## 安裝 + +```bash +npm install @oneup_network/dialogify jquery +``` + +jQuery 屬於 **peer dependency**,請與 dialogify 一併安裝。 + +### 瀏覽器 + +```html + + +``` + +瀏覽器版會直接讀取全域的 jQuery。 + +### 模組(ESM / CommonJS) + +```javascript +import Dialogify from '@oneup_network/dialogify'; +``` + +### 樣式 + +每個建置產物都已內嵌 CSS,不需要另外引入樣式表。若想自行調整樣式,可在載入 +dialogify 前先以 `id="dialogifyCss"` 連結獨立的 +`@oneup_network/dialogify/css`,此時就不會再注入內嵌樣式。 + +## 基本用法 + +以鏈式呼叫組出燈箱後開啟。`show()` 與 `showModal()` 會回傳 promise,並在燈箱 +關閉時以 `returnValue` resolve。 + +```javascript +new Dialogify('dialog content') + .title('dialog title') + .buttons([{ type: Dialogify.BUTTON_PRIMARY }]) + .showModal(); +``` + +`alert`、`confirm` 與 `prompt` 都有一行搞定的替代方案: + +```javascript +await Dialogify.alert('Alert!!'); + +if (await Dialogify.confirm('Yes or no?')) { + // ... +} + +const answer = await Dialogify.prompt('Question?'); +``` + +燈箱也可以直接寫在頁面裡,做成 +[customized built-in element](https://developer.mozilla.org/docs/Web/API/Web_components/Using_custom_elements#customized_built-in_elements), +設定以屬性表示,整套 API 都掛在該元素上: + +```html + + dialog content bold text + + + +``` + +```javascript +document.querySelector('dialog[is="bahamut-dialogify"]').showModal(); +``` + +## 說明文件 + +其餘內容都在說明文件站上,每項功能都附有可實際操作的範例: + +| 主題 | 內容 | +| ---------------------------------------------------------------------------- | --------------------------------------------------- | +| [快速開始](https://oneupnetwork.github.io/dialogify/#getting-started) | 安裝並開啟第一個燈箱 | +| [核心概念](https://oneupnetwork.github.io/dialogify/#concepts) | modal 與非 modal、鏈式呼叫、promise、生命週期 | +| [建立燈箱](https://oneupnetwork.github.io/dialogify/#create) | 所有建構選項 | +| [內容操作](https://oneupnetwork.github.io/dialogify/#content-api) | 標題、HTML 內容、ajax 載入、載入狀態 | +| [表單](https://oneupnetwork.github.io/dialogify/#forms) | 驗證與取回欄位值 | +| [按鈕](https://oneupnetwork.github.io/dialogify/#buttons) | 按鈕定義與執行期控制 | +| [事件](https://oneupnetwork.github.io/dialogify/#events) | `show`、`close`、`cancel` 與攔截關閉 | +| [Drawer、toast、popover](https://oneupnetwork.github.io/dialogify/#variants) | 側邊抽屜、通知訊息與錨定浮層 | +| [快捷方法](https://oneupnetwork.github.io/dialogify/#shortcuts) | `alert`、`confirm`、`prompt`、`toast` | +| [宣告式用法](https://oneupnetwork.github.io/dialogify/#declarative) | ``、屬性、按鈕、事件 | +| [樣式主題](https://oneupnetwork.github.io/dialogify/#theming) | 自訂屬性、暗色主題、`z-index` | +| [語系](https://oneupnetwork.github.io/dialogify/#locale) | 內建語言與自行擴充 | +| [API 參考](https://oneupnetwork.github.io/dialogify/#api) | 所有方法、選項與常數 | + +燈箱內容是以 **HTML** 插入的,因此排版可以很自由,但所有來自使用者的資料都必須 +先做跳脫處理。 + +## 瀏覽器支援 + +支援原生 `` 元素的瀏覽器:Chrome 94+、Firefox 98+ 與 Safari 15.4+。建置 +產物的目標為 `es2022`。 + +另有兩個 optional bundle 可補足缺口,兩者都必須在 dialogify **之前**載入: + +```html + + + + + +``` + +## 相依套件 + +- [jQuery](https://jquery.com/) — peer dependency,`>=3.0.0`(含 jQuery 4) + +## 視覺設計 + +[Phoebe](https://github.com/Phoebe1226) + +## 參與開發 + +```bash +npm install +npm run build +npm test +``` + +Fork 專案、從 master 開新 branch,然後發 pull request。歡迎一起參與。 + +## 授權 + +[MIT](LICENSE) diff --git a/dist/dialogify.cjs b/dist/dialogify.cjs index 7bfedb3..26d8954 100644 --- a/dist/dialogify.cjs +++ b/dist/dialogify.cjs @@ -1,3 +1,3 @@ -var W=Object.create;var v=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var U=(a,i)=>{for(var t in i)v(a,t,{get:i[t],enumerable:!0})},J=(a,i,t,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let e of Y(i))!V.call(a,e)&&e!==t&&v(a,e,{get:()=>i[e],enumerable:!(o=G(i,e))||o.enumerable});return a};var P=(a,i,t)=>(t=a!=null?W($(a)):{},J(i||!a||!a.__esModule?v(t,"default",{value:a,enumerable:!0}):t,a)),F=a=>J(v({},"__esModule",{value:!0}),a);var pi={};U(pi,{default:()=>ui});module.exports=F(pi);var n=P(require("jquery"),1);function z(){let a=window.dialogifyConfig;return(a==null||typeof a!="object")&&(a={}),a.locale=a.locale||"zh_TW",a}function M(a,i,t){if(!Object.prototype.hasOwnProperty.call(a,i)){let o=window.HTMLDialogElement&&window.HTMLDialogElement.prototype;if(o&&typeof o[i]=="function")return o[i].apply(a,t||[])}return a[i].apply(a,t||[])}function m(a,i,t){if(typeof window.CustomEvent!="function")return!0;let o=new window.CustomEvent(i,{bubbles:!1,cancelable:!0,detail:t});return a.dispatchEvent(o),!o.defaultPrevented}function I(){let a=z();return r.LOCALE[a.locale]||r.LOCALE.zh_TW}var Q=["left","right","top","bottom"],X=["top-left","top-center","top-right","bottom-left","bottom-center","bottom-right"],L="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMzZweCcgaGVpZ2h0PSczNnB4JyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgY2xhc3M9InVpbC1kZWZhdWx0Ij48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+PHJlY3QgIHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjMDA5NDk5JyB0cmFuc2Zvcm09J3JvdGF0ZSgwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4gIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9J29wYWNpdHknIGZyb209JzEnIHRvPScwJyBkdXI9JzFzJyBiZWdpbj0nMHMnIHJlcGVhdENvdW50PSdpbmRlZmluaXRlJy8+PC9yZWN0PjxyZWN0ICB4PSc0Ni41JyB5PSc0MCcgd2lkdGg9JzcnIGhlaWdodD0nMjAnIHJ4PSc1JyByeT0nNScgZmlsbD0nIzAwOTQ5OScgdHJhbnNmb3JtPSdyb3RhdGUoMzAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0nb3BhY2l0eScgZnJvbT0nMScgdG89JzAnIGR1cj0nMXMnIGJlZ2luPScwLjA4MzMzMzMzMzMzMzMzMzMzcycgcmVwZWF0Q291bnQ9J2luZGVmaW5pdGUnLz48L3JlY3Q+PHJlY3QgIHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjMDA5NDk5JyB0cmFuc2Zvcm09J3JvdGF0ZSg2MCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+ICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSdvcGFjaXR5JyBmcm9tPScxJyB0bz0nMCcgZHVyPScxcycgYmVnaW49JzAuMTY2NjY2NjY2NjY2NjY2NjZzJyByZXBlYXRDb3VudD0naW5kZWZpbml0ZScvPjwvcmVjdD48cmVjdCAgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyMwMDk0OTknIHRyYW5zZm9ybT0ncm90YXRlKDkwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4gIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9J29wYWNpdHknIGZyb209JzEnIHRvPScwJyBkdXI9JzFzJyBiZWdpbj0nMC4yNXMnIHJlcGVhdENvdW50PSdpbmRlZmluaXRlJy8+PC9yZWN0PjxyZWN0ICB4PSc0Ni41JyB5PSc0MCcgd2lkdGg9JzcnIGhlaWdodD0nMjAnIHJ4PSc1JyByeT0nNScgZmlsbD0nIzAwOTQ5OScgdHJhbnNmb3JtPSdyb3RhdGUoMTIwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4gIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9J29wYWNpdHknIGZyb209JzEnIHRvPScwJyBkdXI9JzFzJyBiZWdpbj0nMC4zMzMzMzMzMzMzMzMzMzMzcycgcmVwZWF0Q291bnQ9J2luZGVmaW5pdGUnLz48L3JlY3Q+PHJlY3QgIHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjMDA5NDk5JyB0cmFuc2Zvcm09J3JvdGF0ZSgxNTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0nb3BhY2l0eScgZnJvbT0nMScgdG89JzAnIGR1cj0nMXMnIGJlZ2luPScwLjQxNjY2NjY2NjY2NjY2NjdzJyByZXBlYXRDb3VudD0naW5kZWZpbml0ZScvPjwvcmVjdD48cmVjdCAgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyMwMDk0OTknIHRyYW5zZm9ybT0ncm90YXRlKDE4MCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+ICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSdvcGFjaXR5JyBmcm9tPScxJyB0bz0nMCcgZHVyPScxcycgYmVnaW49JzAuNXMnIHJlcGVhdENvdW50PSdpbmRlZmluaXRlJy8+PC9yZWN0PjxyZWN0ICB4PSc0Ni41JyB5PSc0MCcgd2lkdGg9JzcnIGhlaWdodD0nMjAnIHJ4PSc1JyByeT0nNScgZmlsbD0nIzAwOTQ5OScgdHJhbnNmb3JtPSdyb3RhdGUoMjEwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4gIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9J29wYWNpdHknIGZyb209JzEnIHRvPScwJyBkdXI9JzFzJyBiZWdpbj0nMC41ODMzMzMzMzMzMzMzMzM0cycgcmVwZWF0Q291bnQ9J2luZGVmaW5pdGUnLz48L3JlY3Q+PHJlY3QgIHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjMDA5NDk5JyB0cmFuc2Zvcm09J3JvdGF0ZSgyNDAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPiAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0nb3BhY2l0eScgZnJvbT0nMScgdG89JzAnIGR1cj0nMXMnIGJlZ2luPScwLjY2NjY2NjY2NjY2NjY2NjZzJyByZXBlYXRDb3VudD0naW5kZWZpbml0ZScvPjwvcmVjdD48cmVjdCAgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyMwMDk0OTknIHRyYW5zZm9ybT0ncm90YXRlKDI3MCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+ICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSdvcGFjaXR5JyBmcm9tPScxJyB0bz0nMCcgZHVyPScxcycgYmVnaW49JzAuNzVzJyByZXBlYXRDb3VudD0naW5kZWZpbml0ZScvPjwvcmVjdD48cmVjdCAgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyMwMDk0OTknIHRyYW5zZm9ybT0ncm90YXRlKDMwMCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+ICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSdvcGFjaXR5JyBmcm9tPScxJyB0bz0nMCcgZHVyPScxcycgYmVnaW49JzAuODMzMzMzMzMzMzMzMzMzNHMnIHJlcGVhdENvdW50PSdpbmRlZmluaXRlJy8+PC9yZWN0PjxyZWN0ICB4PSc0Ni41JyB5PSc0MCcgd2lkdGg9JzcnIGhlaWdodD0nMjAnIHJ4PSc1JyByeT0nNScgZmlsbD0nIzAwOTQ5OScgdHJhbnNmb3JtPSdyb3RhdGUoMzMwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4gIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9J29wYWNpdHknIGZyb209JzEnIHRvPScwJyBkdXI9JzFzJyBiZWdpbj0nMC45MTY2NjY2NjY2NjY2NjY2cycgcmVwZWF0Q291bnQ9J2luZGVmaW5pdGUnLz48L3JlY3Q+PC9zdmc+";function K(a){return String(a??"").replace(/[&<>"']/g,function(i){return{"&":"&","<":"<",">":">",'"':""","'":"'"}[i]})}function B(a,i,t){return Math.min(Math.max(a,i),t)}function A(a,i,t,o){return a=="center"?i+t/2-o/2:a=="end"?i+t-o:i}var q=600;function ii(a,i){let t=typeof a.getAnimations=="function"?a.getAnimations():[];if(!t.length){i();return}let o=!1,e=function(){o||(o=!0,i())};Promise.all(t.map(function(d){return d.finished.catch(function(){})})).then(e),window.setTimeout(e,q)}function ti(a,i){let t=a.getBoundingClientRect();return!t.width&&!t.height?!0:i.clientXt.right||i.clientYt.bottom}var r=class a{constructor(i,t){this._setup(window.document.createElement("dialog"),i,t,{append:!0})}_setup(i,t,o,e){let d=z();(e==null||typeof e!="object")&&(e={}),window.dialogPolyfill&&typeof window.dialogPolyfill.registerDialog=="function"&&window.dialogPolyfill.registerDialog(i),(o==null||typeof o!="object")&&(o={}),this.options=o;let s=o.ajaxPrefix===void 0?"/ajax/":o.ajaxPrefix,l=this,f,c=!1,u=e.content||null;u?f=null:t.charAt(0)=="#"?f=(0,n.default)(t).html():t.indexOf(s)==0?(f=`
${K(I().loading)}
`,c=!0):f=t,f=f==null?null:`
${f}
`;let H=o.size||"dialogify__fixedwidth",w="dialogify";o.fixed!==!1&&(w+=" fixed"),Q.indexOf(o.position)>=0&&(w+=` dialogify--drawer dialogify--${o.position}`),o.variant&&(w+=` dialogify--${o.variant}`);let D=`
`;o.useDialogForm!==!1&&(D=`${D}`),a.counter++,this.id=i.id||`dialogify_${a.counter}`;let C=(0,n.default)(i).attr("id",this.id).addClass(w).append(D).on("close",function(){l._teardownAnchor(),(0,n.default)(l).triggerHandler("close"),o.backgroundScroll===!1&&(0,n.default)("body").css({overflow:"","padding-right":""}),(0,n.default)(i).addClass("dialogify--closing"),l._onClosing&&l._onClosing(),ii(i,function(){(0,n.default)(i).removeClass("dialogify--closing"),o.autoRemove!==!1&&(l._resizeObserver&&l._resizeObserver.disconnect(),(0,n.default)(i).remove()),l._onExit&&l._onExit()}),l._resolveClosePromise()}).on("cancel",function(g){(0,n.default)(l).triggerHandler("cancel"),o.closable===!1||!l._fireBeforeClose()?g.preventDefault():l._markClosing()}).on("click",function(g){o.closable!==!1&&g.target==i&&ti(i,g)&&m(i,"cancel")&&l.close()});if(o.useDialogForm!==!1&&C.find('form[method="dialog"]').on("submit",function(g){l._fireBeforeClose(this.returnValue)?l._markClosing():g.preventDefault()}),e.append!==!1&&C.appendTo("body"),o.closable!==!1){let g="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE2IDE2OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6IzA1NTY2Njt9PC9zdHlsZT48cmVjdCB4PSIzLjEiIHk9IjEiIGNsYXNzPSJzdDAiIHdpZHRoPSIxLjciIGhlaWdodD0iMSIvPjxyZWN0IHg9IjIuMSIgeT0iMi4xIiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMSIgeT0iMy4xIiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxLjciLz48cmVjdCB4PSIxIiB5PSIxMS4xIiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxLjciLz48cmVjdCB4PSIyLjEiIHk9IjEyLjkiIGNsYXNzPSJzdDAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz48cmVjdCB4PSIzLjEiIHk9IjEzLjkiIGNsYXNzPSJzdDAiIHdpZHRoPSIxLjciIGhlaWdodD0iMSIvPjxnPjxyZWN0IHg9IjQuOSIgY2xhc3M9InN0MCIgd2lkdGg9IjYuMyIgaGVpZ2h0PSIxIi8+PC9nPjxyZWN0IHg9IjQuOSIgeT0iMTUiIGNsYXNzPSJzdDAiIHdpZHRoPSI2LjMiIGhlaWdodD0iMSIvPjxyZWN0IHk9IjQuOSIgY2xhc3M9InN0MCIgd2lkdGg9IjEiIGhlaWdodD0iNi4zIi8+PHJlY3QgeD0iMTEuMSIgeT0iMSIgY2xhc3M9InN0MCIgd2lkdGg9IjEuNyIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMTIuOSIgeT0iMi4xIiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMTMuOSIgeT0iMy4xIiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxLjciLz48cmVjdCB4PSIxMy45IiB5PSIxMS4xIiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxLjciLz48cmVjdCB4PSIxMi45IiB5PSIxMi45IiBjbGFzcz0ic3QwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMTEuMSIgeT0iMTMuOSIgY2xhc3M9InN0MCIgd2lkdGg9IjEuNyIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMTUiIHk9IjQuOSIgY2xhc3M9InN0MCIgd2lkdGg9IjEiIGhlaWdodD0iNi4zIi8+PHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI5LjcsMy44IDkuNyw0LjkgOC43LDQuOSA4LjcsNS45IDcuMyw1LjkgNy4zLDQuOSA2LjMsNC45IDYuMywzLjggMy44LDMuOCAzLjgsNS4yIDQuOSw1LjIgNC45LDYuMyA1LjksNi4zIDUuOSw3LjMgNyw3LjMgNyw4LjMgNS45LDguMyA1LjksOS40IDQuOSw5LjQgNC45LDEwLjQgMy44LDEwLjQgMy44LDEwLjggMy44LDExLjggNi4zLDExLjggNi4zLDEwLjggNy4zLDEwLjggNy4zLDkuNyA4LjcsOS43IDguNywxMC44IDkuNywxMC44IDkuNywxMS44IDEyLjIsMTEuOCAxMi4yLDEwLjQgMTEuMSwxMC40IDExLjEsOS40IDEwLjEsOS40IDEwLjEsOC4zIDksOC4zIDksNy4zIDEwLjEsNy4zIDEwLjEsNi4zIDExLjEsNi4zIDExLjEsNS4yIDEyLjIsNS4yIDEyLjIsNC45IDEyLjIsMy44ICIvPjwvc3ZnPg==",x="",h={},b=o.closeButton||d.closeButton;b!=null&&typeof b=="object"&&(g=b.image||g,x=b.className||x,h=b.style||h);let R=(0,n.default)("").attr({src:g,alt:""}),k=(0,n.default)("").addClass("dialogify__close").attr({role:"button",tabindex:0,"aria-label":I().close}).css(h).append(R).on("click",function(){m(i,"cancel")&&l.close()}).on("keydown",function(_){(_.key=="Enter"||_.key==" "||_.key=="Spacebar")&&(_.preventDefault(),(0,n.default)(this).trigger("click"))});x&&k.addClass(x),(0,n.default)(i).append(k)}let p=o.dialog||d.dialog;if(p!=null&&typeof p=="object"){(0,n.default)(i).css(p.style||{}),p.className&&(0,n.default)(i).addClass(p.className);let g=(0,n.default)(i).find(".dialogify__content");g.css(p.contentStyle||{}),p.contentClassName&&g.addClass(p.contentClassName)}if(this.$content=(0,n.default)(i).find(".dialogify__content > div"),u?this.$content.append((0,n.default)("
").addClass("dialogify__body").append(u)):this.$content.append(f),this.$body=this.$content.find(".dialogify__body"),this.$form=(0,n.default)(i).find('form[method="dialog"]'),c){let g=t,x=o.ajaxData||{};try{let h=new URL(t,window.location.href);Object.keys(x).forEach(function(b){h.searchParams.append(b,x[b])}),g=h.toString()}catch{}window.fetch(g,{headers:{"X-Requested-With":"XMLHttpRequest"}}).then(function(h){return h.text()}).then(function(h){o.ajaxComplete&&o.ajaxComplete.call(l),l.$content.find(".dialogify-ajax-content").html(h)})}this.dialog=i,window.ResizeObserver&&!(0,n.default)(i).is(".dialogify--drawer, .dialogify--toast")&&(this._resizeObserver=new window.ResizeObserver(function(){(0,n.default)(i).is(".dialogify--popover")||ni(i)}),this._resizeObserver.observe(i))}_fireBeforeClose(i){let t=n.default.Event("beforeclose");return t.returnValue=i,(0,n.default)(this).triggerHandler(t),m(this.dialog,"beforeclose",{returnValue:i})&&!t.isDefaultPrevented()}_resolveClosePromise(){if(this._closeResolver){let i=this._closeResolver;this._closeResolver=null,this._closePromise=null,i(this.dialog.returnValue)}}_openPromise(){let i=this;return(0,n.default)(this.dialog).removeClass("dialogify--closing"),this._closePromise||(this._closePromise=new Promise(function(t){i._closeResolver=t})),this._closePromise}showModal(){this.options.backgroundScroll===!1&&E();let i=this._openPromise();return M(this.dialog,"showModal"),m(this.dialog,"show"),(0,n.default)(this).triggerHandler("show"),i}show(){this.options.backgroundScroll===!1&&E();let i=this._openPromise();return M(this.dialog,"show"),m(this.dialog,"show"),(0,n.default)(this).triggerHandler("show"),i}showAt(i,t){let o=(0,n.default)(i);if(!o.length)return this.show();(t==null||typeof t!="object")&&(t={}),this._anchor=o[0],this._anchorOptions=t,(0,n.default)(this.dialog).addClass("dialogify--popover").removeClass("fixed");let e=this.show();this.reposition();let d=this;return this._anchorListener=function(){d.reposition()},window.addEventListener("scroll",this._anchorListener,!0),window.addEventListener("resize",this._anchorListener),this.options.closable!==!1&&(this._outsideListener=function(s){if(d.dialog.contains(s.target))return;let l=t.toggle!==!1&&d._anchor.contains(s.target);m(d.dialog,"cancel")&&(l&&(s.preventDefault(),s.stopImmediatePropagation()),d.close())},this._outsideTimer=window.setTimeout(function(){d._outsideListener&&window.document.addEventListener("click",d._outsideListener,!0)},0)),e}reposition(){if(!this._anchor||!this.dialog.open)return this;let i=this._anchorOptions||{},t=i.offset===void 0?8:i.offset,o=i.align||"start",e=this._anchor.getBoundingClientRect(),d=this.dialog.getBoundingClientRect(),s=window.innerWidth||0,l=window.innerHeight||0,f=i.placement||"bottom";f=="bottom"&&e.bottom+t+d.height>l?f="top":f=="top"&&e.top-t-d.height<0?f="bottom":f=="right"&&e.right+t+d.width>s?f="left":f=="left"&&e.left-t-d.width<0&&(f="right");let c,u;return f=="top"||f=="bottom"?(c=f=="bottom"?e.bottom+t:e.top-t-d.height,u=A(o,e.left,e.width,d.width)):(u=f=="right"?e.right+t:e.left-t-d.width,c=A(o,e.top,e.height,d.height)),u=B(u,0,Math.max(0,s-d.width)),c=B(c,0,Math.max(0,l-d.height)),(0,n.default)(this.dialog).attr("data-placement",f).css({position:"fixed",top:`${Math.round(c)}px`,left:`${Math.round(u)}px`,right:"auto",bottom:"auto",transform:"none",margin:0}),this}_teardownAnchor(){this._anchorListener&&(window.removeEventListener("scroll",this._anchorListener,!0),window.removeEventListener("resize",this._anchorListener),this._anchorListener=null),this._outsideTimer&&(window.clearTimeout(this._outsideTimer),this._outsideTimer=null),this._outsideListener&&(window.document.removeEventListener("click",this._outsideListener,!0),this._outsideListener=null),this._anchor=null}_markClosing(){this.dialog.open&&(0,n.default)(this.dialog).addClass("dialogify--closing")}close(i){return this._fireBeforeClose(i)?(this._markClosing(),M(this.dialog,"close",i===void 0?[]:[i]),this):this}isOpen(){return this.dialog.open}on(i,t){return(0,n.default)(this).on(i,t),this}off(i,t){return(0,n.default)(this).off(i,t),this}setContent(i){return i instanceof window.Node||i instanceof n.default?this.$body.empty().append(i):this.$body.html(i??""),this}getContent(){return this.$body.html()}load(i,t){let o=this,e=i;try{let d=new URL(i,window.location.href),s=t||{};Object.keys(s).forEach(function(l){d.searchParams.append(l,s[l])}),e=d.toString()}catch{}return this.setLoading(!0),window.fetch(e,{headers:{"X-Requested-With":"XMLHttpRequest"}}).then(function(d){return d.text()}).then(function(d){return o.setContent(d),o.setLoading(!1),o.options.ajaxComplete&&o.options.ajaxComplete.call(o),o}).catch(function(d){throw o.setLoading(!1),d})}setLoading(i){if(i===!1)return this.$content.find(".dialogify__loading").remove(),(0,n.default)(this.dialog).removeClass("is-loading"),this;if(!this.$content.find(".dialogify__loading").length){let t=typeof i=="string"?i:I().loading;this.$content.append((0,n.default)("
").addClass("dialogify__loading").attr({role:"status","aria-live":"polite"}).append((0,n.default)("").attr({src:L,alt:t}))),(0,n.default)(this.dialog).addClass("is-loading")}return this}isLoading(){return this.$content.find(".dialogify__loading").length>0}validate(){let i=this.$form[0];return!i||typeof i.reportValidity!="function"?!0:i.reportValidity()}formData(){let i=this.$form[0];return!i||typeof window.FormData!="function"?null:new window.FormData(i)}formValues(){let i=this.formData();if(!i)return{};let t={};return i.forEach(function(o,e){Object.prototype.hasOwnProperty.call(t,e)?(Array.isArray(t[e])||(t[e]=[t[e]]),t[e].push(o)):t[e]=o}),t}title(i){let t=`${this.id}_title`,o=(0,n.default)("
").addClass("dialogify_title").attr("id",t).append('').append(i);return this.$content.find(".dialogify_title").length?this.$content.find(".dialogify_title").replaceWith(o):this.$content.prepend(o),(0,n.default)(this.dialog).attr("aria-labelledby",t),this}_createButton(i,t){let o=this;if(typeof i=="string"){let d=(0,n.default)(i);return{id:d.attr("id")||t,$button:d}}(i==null||typeof i!="object")&&(i={});let e=(0,n.default)('