gitoriaLog in with ident

components

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Main branchmain3bc905c5components #15 (mission 003) 2/2: docs (README Look, STATUS, LOG), reportmremain/components/modal/README.md

4.5 KB

  1. # Modal
  2. A window over the page with a title, a × and any content; the page behind is dimmed, blocked and does not scroll.
  3. Esc, the × and a click on the dim background close it (the background click can be switched off); buttons in a footer
  4. close it too. The focus stays inside while it is open and goes back to the element that opened it. The page opens and
  5. closes it and is told **how** it was closed. Demo: `/modal` (https://components.hybriel.worldapi.org/modal).
  6. Pure Hybriel: one file, `modal.hl`, with its own `Style`; no JavaScript file. It is the browser's own `<dialog>` opened
  7. with `showModal()` (page behind inert, focus in, focus back), plus Tab / Shift+Tab going round inside, the reasons, the
  8. background click and the scroll lock.
  9. ## Use it in an app
  10. Copy `components/modal/modal.hl` (optionally `shared/tokens.hl` for the app's own accent), then:
  11. ```
  12. import Modal from './modal.hl'
  13. String result = ''
  14. View {
  15. page { on close(e) { emit pageClosed(e) }
  16. button { type = "button" "Delete…" on click(e) { emit pageAsk(e) } }
  17. Modal { id = "confirm" title = "Delete the draft?"
  18. p { "This cannot be undone." }
  19. footer {
  20. button { type = "button" value = "cancel" "Cancel" }
  21. button { type = "button" value = "ok" "Delete" }
  22. }
  23. }
  24. }
  25. }
  26. on pageAsk(e) { document.getElementById('confirm').showModal() }
  27. on pageClosed(e) { if (e.detail.id == 'confirm' && e.detail.reason == 'ok') { result = 'deleted' } }
  28. ```
  29. ## Attributes
  30. | Attribute | Meaning |
  31. |---|---|
  32. | `id` | the id of the `<dialog>`: the page opens / closes it by this id (required when there are several) — default `modal` |
  33. | `title` | the heading of the window (also its `aria-label`) |
  34. | `backdropClose` | `"false"` = a click on the dim background does NOT close it (default `"true"`) |
  35. | `closeLabel` | the tooltip / label of the × (default `Close`) |
  36. | content | everything written inside `Modal { … }` is the content (the host's content: its handlers and members are the page's) |
  37. Write every attribute as a **literal** (`title = "…"`): then several modals stand on one page, each with its own values.
  38. (A composed component's members are the page's state; bound to a changing member, all modals would share it.)
  39. ## Footer buttons
  40. A `footer { … }` inside the content is the footer: laid out at the bottom right, sticky under a long content, the last
  41. button is the main one (accent colour; black without a theme). A click on a footer button closes the modal with the button's `value` as the
  42. reason (no `value` → its text). A footer button may have its own `on click` (the page's handler): it runs first, and when
  43. it closes the modal itself with `close('saved')`, that reason wins (the demo's "Save"). A button that must keep the modal
  44. open (e.g. a check that fails) goes in the content, not in the `footer`.
  45. ## Open and close from the page
  46. - open: `document.getElementById('<id>').showModal()` in any handler (the focused element — usually the button that was
  47. clicked — gets the focus back when it closes).
  48. - close: `document.getElementById('<id>').close('<reason>')` — the reason is what the page is told.
  49. ## Events
  50. When it closes, a **bubbling `close` event** is sent from `<modal-dialog>`; catch it on any element around the modal:
  51. `on close(e) { … }`.
  52. | `e.detail.reason` | how it was closed |
  53. |---|---|
  54. | `close` | the × |
  55. | `escape` | the Esc key |
  56. | `backdrop` | a click on the dim background |
  57. | the button's `value` (or text) | a footer button |
  58. | whatever the page passed | `close('<reason>')` from the page |
  59. `e.detail.id` is the modal's id. (A custom event name such as `modalclose` would be nicer, but hl:web binds only
  60. standard DOM event names — hybriel ticket #31 — so it is `close`.)
  61. ## Behaviour
  62. - The page behind is inert (no clicks, no focus) and does not scroll (`html { overflow: hidden }` while one is open).
  63. - The window stays inside the screen (at most the screen height minus 2rem); a long content scrolls inside it.
  64. - A press that starts inside the window and ends on the background (selecting text) does not close it.
  65. - Phone: the window is the screen width minus 1rem each side.
  66. - Colours are the semantic tokens with a black/white/grey fallback (the base look is black, white and greys (no frames; root README "Look"); an app that declares the WorldAPI tokens (layouts.worldapi.org's theme) restyles it): a white window on the grey dimmed page; buttons have no border.
  67. ## Not yet
  68. Open / close animation; stacking one modal on top of another is untested; the page cannot bind the modal's `open` state
  69. as a member (it opens it with `showModal()`).

Branches

Latest commits

  • 3bc905c5components #15 (mission 003) 2/2: docs (README Look, STATUS, LOG), reportmre
  • 5a98d77ecomponents #15 (mission 003) 1/2: black/white base look — greys for nuances, no WorldAPI fallbacks, no decorative frames; site + icon black/white; gate 230/0mre
  • 3f93d716components code order (mission 002) 4/4: docs, tests/letcount.py, tests/same-output*, reportmre
  • e747cfa0components code order (mission 002) 3/4: let only where reassigned (257 dropped, 0 never-reassigned left); gate 220/0, layouts gate 73/0, same outputmre
  • 172914a3components code order (mission 002) 2/4: lib/uploads.hl (demo upload storage), thin demo handlers, project.hl map; gate 220/0, same outputmre
  • d29f35b1components code order (mission 002) 1/4: styles.hl -> components/styles.hl; gate 220/0, same outputmre
  • 09945750components: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, http1 773de63e); gate 220/0mre
  • ede4d19acomponents: Hybriel master 190aa11d (fc838894 GC correctness, #126 closure scopes, #127); gate 220/0mre
  • 2bb9a935components: Hybriel master 8efba065 (re-vendor round 069: #126 memory, #48 lambda copy)mre
  • e4f01768antcolony#40: mission references point to the moved missionsmre
  • d32b44e2antcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 0fb80b57components: Hybriel master ff51cf46 (re-vendor round)mre
  • 57408b6ecomponents#14: installable app (manifest, service worker, offline index), own iconmre
  • 317bd09fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • becbd59bcomponents#13: modal dialog (<dialog> based, focus wrap, backdrop/Esc/x close with reason, scroll lock, footer buttons)mre
  • 367dd19adeploy.sh: never send .git or .gitignore to Byrodinmre
  • 4b4dabe5State of 2026-09-27, before the move to gitoriamre