diff --git a/CHANGELOG.md b/CHANGELOG.md index 3128283e..c3a19324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## unreleased +- Modals can set a header `color`, and `visible` opens that dialog once when the page loads. The page address stays unchanged, and closing it does not open it again when later content loads. + - Now a tab with a `color` is underlined only when it is active. ([#791](https://github.com/sqlpage/SQLPage/issues/791)) - Striped table rows are visible in dark mode again. Tabler was updated to [v1.7.3](https://github.com/tabler/tabler/releases/tag/%40tabler%2Fcore%401.7.3) to fix this. ([#957](https://github.com/sqlpage/SQLPage/issues/957)) diff --git a/examples/official-site/sqlpage/migrations/63_modal.sql b/examples/official-site/sqlpage/migrations/63_modal.sql index 3154007c..0250faec 100644 --- a/examples/official-site/sqlpage/migrations/63_modal.sql +++ b/examples/official-site/sqlpage/migrations/63_modal.sql @@ -21,7 +21,9 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('height','Height of the embedded content.','INTEGER',TRUE,TRUE), ('allow','For embedded content, this attribute specifies the features or permissions that can be used.','TEXT',TRUE,TRUE), ('sandbox','For embedded content, this attribute specifies the security restrictions on the loaded content.','TEXT',TRUE,TRUE), - ('style','Applies CSS styles to the embedded content.','TEXT',TRUE,TRUE) + ('style','Applies CSS styles to the embedded content.','TEXT',TRUE,TRUE), + ('color','Optional Tabler color for the modal header. The header uses that background and the same contrasting foreground as a colored toast, with white mapped to the light foreground. The body and footer stay as they are.','COLOR',TRUE,TRUE), + ('visible','Opens this modal once on load when true, without an inline script and without changing the URL hash. If several modals set this, only the last one opens. A URL hash that names a different modal takes precedence.','BOOLEAN',TRUE,TRUE) ) x; INSERT INTO example(component, description, properties) VALUES @@ -78,6 +80,15 @@ INSERT INTO example(component, description, properties) VALUES }, {"component": "text", "contents_md": "Open a [modal with a video](#my_embed_video_modal)"} ]') + ), + ('modal', + 'A modal with a colored header, opened by a button. The header uses the color and a contrasting foreground; the body stays in the normal text color.', + json('[ + {"component": "modal","id": "colored_modal","title": "Saved","color": "green","close": "Close"}, + {"contents_md":"The header uses the chosen color."}, + {"component": "button"}, + {"title":"Open a colored modal","link":"#colored_modal"} + ]') ); INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'button', * FROM (VALUES diff --git a/frontend/src/sqlpage.ts b/frontend/src/sqlpage.ts index 4bacfc75..051d47f7 100644 --- a/frontend/src/sqlpage.ts +++ b/frontend/src/sqlpage.ts @@ -520,12 +520,38 @@ function sqlpage_modal(root: InitRoot) { // .page instead of the viewport. The modal then scrolls with the page // content and ends up behind its own backdrop, so its buttons cannot be // clicked. Moving modals to
keeps them viewport-fixed. - for (const modal of select_all(root, "body .page .modal", HTMLElement)) { - document.body.appendChild(modal); - // The modal leaves the original root; announce its subtree separately so - // every initializer, including later-loaded bundles, can still see it. + const modals = select_all(root, "body .page .modal", HTMLElement); + for (const modal of modals) document.body.appendChild(modal); + // The modals left the original root; announce each subtree separately so + // every initializer, including later-loaded bundles, can still see it. The + // event runs this function again, so move them all first: a dialog opened + // below must not be moved afterwards. + for (const modal of modals) { modal.dispatchEvent(new CustomEvent("fragment-loaded", { bubbles: true })); } + + // `visible` opens the last such dialog once. A hash that already names a + // different modal keeps that dialog. Clearing every marker stops a later + // pass from opening an earlier dialog or reopening one the user closed. + const visible_modals = document.querySelectorAll