Skip to content

re-frame.fresco.overlay

Use this module for popovers and modal dialogs on the browser's own top layer. popover and modal make the one call an attribute cannot, showPopover or showModal, and leave everything else to the platform.

It is an optional namespace: re-frame.fresco does not require it, so an application that never requires it carries none of its code. It is ClojureScript only; the namespace does not exist on the JVM.

(:require [re-frame.fresco :as h]
          [re-frame.fresco.overlay :as overlay])

The platform handles the rest of an overlay. <dialog> makes the page behind it inert, enforced by the engine rather than by a key handler. popover provides light dismiss (a click outside or Escape closes it) and keeps nested popovers in last-opened, first-closed order. The top layer paints above everything, so no ancestor's overflow, transform or z-index can clip or out-stack a panel. CSS anchor positioning places the panel. What none of them does is enter the top layer: an element does not get there by having an attribute, so the module calls showModal / showPopover at the point React offers before paint, and the inverse before React removes the node.

Overlays and focus teaches the popover and modal patterns and the focus rules.

The heads

Both are legal hiccup heads but not Fresco views: they read no subscriptions. They take these props. A closed overlay does not mount its child views. Reads made in the enclosing view while constructing those children still run in that view.

Prop Head Meaning
:open? both Whether the overlay exists. False renders nothing: no element, no listener, no anchor name. An overlay is open exactly when your app-db says so.
:on-dismiss both Event vector dispatched when the browser dismisses the overlay. Without it the overlay ignores dismissal (see each head).
:label both The accessible name, set as aria-label. When given, it replaces an :aria-label you pass.
:anchor popover The DOM id of the trigger to position against.
:placement popover :top, :bottom, :left or :right, alone or with -start or -end.
:light-dismiss? modal Whether a backdrop click dismisses. Default false.

Every other prop reaches the element unchanged, except the ones the module writes itself: :ref on both, :on-cancel, :on-key-down and :closedby on a modal, and :on-before-toggle and :popover on a popover. A value you pass at one of those is replaced. A :style you pass is merged, with your keys winning over the position-area that :placement sets.

  • Errors:
    • :rf.error/fresco-intent-outside-boundary when an open overlay has :on-dismiss and no frame is in scope, since nothing could dispatch it.
    • :rf.error/fresco-overlay-anchor-missing when a popover opens with an :anchor that names no element in the document. Omitting :anchor is legal.

popover

  • Kind: component (Fresco head)
  • Signature:
    [overlay/popover {:open?      open?
                      :on-dismiss event-v
                      :label      string?
                      :anchor     trigger-dom-id
                      :placement  compass-word}
     child …]
    
  • Description: Renders an anchored, light-dismissable panel on the browser's top layer.
    • While the panel is open, the module gives the :anchor element a generated CSS anchor name, and restores whatever it found when the panel closes. Changing :anchor on an open panel moves it to the new trigger without closing it.
    • :placement becomes a CSS position-area against the anchor; :bottom-start lines the panel's left edge up with the trigger's. Any other value is passed through as a literal position-area string rather than rejected, so a misspelt word is an invalid CSS value and the panel lands at the browser's default position.
    • With :on-dismiss, the panel is popover="auto" and joins the platform's stack of open popovers. Without it, the panel is popover="manual" and nothing dismisses it, because a dismissal nothing handles would leave the browser, instead of your :open? value, deciding whether the panel is open.
  • Example:
    [overlay/popover {:open?      (h/sub [:menu/open? id])
                      :on-dismiss [:menu/dismissed id]
                      :anchor     trigger-id
                      :placement  :bottom-start}
     [:ul {:role "menu"} …]]
    
  • Kind: component (Fresco head)
  • Signature:
    [overlay/modal {:open?          open?
                    :on-dismiss     event-v
                    :label          string?
                    :light-dismiss? boolean?}
     child …]
    
  • Description: Renders a blocking dialog on the browser's top layer, opened with showModal.
    • The engine provides modality: the rest of the document is inert, and ::backdrop is a real CSS selector. Inertness keeps Tab from reaching the page, and the module wraps focus at both ends, so Tab from the last control goes back to the first instead of through <body>.
    • Escape dispatches :on-dismiss. A backdrop click does so only with :light-dismiss? true (default false), so a destructive confirmation does not close on a stray click. Without :on-dismiss, the dialog ignores every close request.
    • Initial focus goes to the first focusable control in tree order, by the platform's own dialog focusing steps, so order the controls instead of using an autofocus attribute.
  • Example:
    [overlay/modal {:open?      (h/sub [:invoice/confirm-delete? id])
                    :on-dismiss [:invoice/delete-cancelled id]
                    :label      "Confirm deletion"}
     [:h2 "Delete this invoice?"]
     [:button {:on-click [:invoice/delete-cancelled id]} "Keep it"]
     [:button {:on-click [:invoice/deleted id]} "Delete"]]
    

See also