domma-cms 0.54.0 → 0.54.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -152,7 +152,7 @@ Menus live in `config/menus/<slug>.json`. The slot → menu map lives in `config
152
152
 
153
153
  **Bindings.** A menu can carry `binding: {slot, projects: [], pages: []}` and take that slot over on matching pages instead of being mapped globally. `resolveLocation(slot, user, {urlPath, project})` checks bindings first (exact page > `/glob/*` > project, longest pattern wins) and falls back to `menu-locations.json`; **callers that know the page must pass the ctx** or bindings silently never fire - `renderer.js` (both render paths) and `parseMarkdown({urlPath})` do. All menu files are cached in-process via `getAllMenus()`; anything writing a menu file outside `menus.js` must call `invalidateMenuIndex()` (the two boot migrations do).
154
154
 
155
- **Item highlighting.** `highlight: {hover, active, accent}` on a menu picks the pointer effect and the current-page marker; the whole implementation is `public/css/menu-highlight.css` keyed off `data-menu-hover` / `data-menu-active` (+ inline `--dm-menu-highlight`), stamped by `applyHighlight` (client) and `buildMenuNav` (server). Unlike the float helpers, `highlightAttrs`/`isCurrentItem` live ONCE in `menu-decor.mjs` and the server imports them - do not duplicate them into `menus.js`. Marking the current item needs the page's URL: `buildMenuNav({currentPath})` (renderer + markdown pass it), `location.pathname` in site.js. The rules carry `!important` because Domma's `.navbar-dark .navbar-link:hover` and site.css's blanket `.page-body a` rule both outrank a class selector - and site.css's rule additionally excludes `:not([data-menu-hover] a):not([data-menu-active] a)`, so a highlighted `[menu]` in the body is not underlined at rest.
155
+ **Item highlighting.** `highlight: {hover, active, accent}` on a menu picks the pointer effect and the current-page marker; the whole implementation is `public/css/menu-highlight.css` keyed off `data-menu-hover` / `data-menu-active` (+ inline `--dm-menu-highlight`), stamped by `applyHighlight` (client) and `buildMenuNav` (server). Unlike the float helpers, `highlightAttrs`/`isCurrentItem` live ONCE in `menu-decor.mjs` and the server imports them - do not duplicate them into `menus.js`. Marking the current item needs the page's URL: `buildMenuNav({currentPath})` (renderer + markdown pass it), `location.pathname` in site.js. **`!important` alone is NOT enough for the navbar** and 0.54.0 shipped believing it was: Domma's `elements.css` has `[data-mode="light"] .navbar:not(.navbar-light):not(.navbar-dark) .navbar-link:hover {color:#fff!important;background-color:#1e293b!important}` at specificity (0,5,0), and two `!important`s are resolved by specificity, so every hover effect rendered dead on a `transparent`/unset-variant navbar in light mode. Each hover effect is therefore declared TWICE - plain, and again scoped `#site-navbar…` at (1,3,0) - and `tests/services/menu-highlight.test.js` asserts the pair exists for every effect in `HIGHLIGHT_HOVER`; add an effect, add both. `transform` failed the same way with no visible competitor, so contested properties (transform, text-transform, letter-spacing, animation, text-shadow) all carry `!important` too. Motion effects are dropped for BOTH reduced-motion signals: the OS media query and `html.dm-reduced-motion`, which is what the footer's own motion switch stamps. site.css's blanket `.page-body a` rule additionally excludes `:not([data-menu-hover] a):not([data-menu-active] a)`, so a highlighted `[menu]` in the body is not underlined at rest.
156
156
 
157
157
  **Orientation.** `orientation: horizontal|vertical` + `side: left|right`. Vertical navbar = CSS rail over Domma's normal navbar markup (`#site-navbar.dm-nav-vertical` + server-stamped `body.dm-vnav-*` gutter, ≥993px only; below that the mobile drawer already applies). The `[menu]` shortcode emits `dm-menu--vertical|horizontal` **only when set** - no class means the historic plain-list rendering.
158
158
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.54.0",
3
+ "version": "0.54.2",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -1 +1 @@
1
- [data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),[data-menu-active] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a){transition:color .18s ease,background-color .18s ease,text-shadow .18s ease,transform .18s ease,letter-spacing .18s ease,text-decoration-color .18s ease}.page-body :is([data-menu-hover],[data-menu-active]).dm-menu li>a{color:var(--dm-primary, inherit);text-decoration:none}[data-menu-hover=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),[data-menu-hover=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{color:inherit!important;background-color:transparent!important;text-decoration:none!important;text-shadow:none!important;transform:none!important}[data-menu-hover=colour] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{color:var(--dm-menu-highlight, var(--dm-primary))!important;background-color:transparent!important;text-decoration:none!important}[data-menu-hover=underline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{background-color:transparent!important;text-decoration:underline!important;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-hover=overline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{background-color:transparent!important;text-decoration:overline!important;text-decoration-thickness:2px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-hover=background] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{background-color:color-mix(in srgb,var(--dm-menu-highlight, var(--dm-primary)) 18%,transparent)!important;border-radius:var(--dm-radius-md, 6px);text-decoration:none!important}[data-menu-hover=uppercase] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{text-transform:uppercase!important;letter-spacing:.04em;background-color:transparent!important;text-decoration:none!important}:is([data-menu-hover=grow],[data-menu-hover=breathe]) :is(.dm-menu,.footer-nav) li>a{display:inline-block}[data-menu-hover=grow] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{transform:scale(1.08);background-color:transparent!important;text-decoration:none!important}@keyframes dm-menu-breathe{0%,to{opacity:1;transform:scale(1)}50%{opacity:.72;transform:scale(1.05)}}[data-menu-hover=breathe] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{animation:dm-menu-breathe 1.4s ease-in-out infinite;background-color:transparent!important;text-decoration:none!important}[data-menu-hover=glow] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{text-shadow:0 0 8px color-mix(in srgb,var(--dm-menu-highlight, var(--dm-primary)) 70%,transparent);background-color:transparent!important;text-decoration:none!important}[data-menu-active] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){box-shadow:none!important;text-decoration:none!important}[data-menu-active=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){font-weight:inherit!important;background-color:transparent!important}[data-menu-active=bold] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){font-weight:700!important;background-color:transparent!important}[data-menu-active=underline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:transparent!important;text-decoration:underline!important;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-active=overline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:transparent!important;text-decoration:overline!important;text-decoration-thickness:2px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-active=background] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:color-mix(in srgb,var(--dm-menu-highlight, var(--dm-primary)) 18%,transparent)!important;border-radius:var(--dm-radius-md, 6px)}[data-menu-active=darken] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:#00000038!important;border-radius:var(--dm-radius-md, 6px)}[data-menu-active=pill] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:var(--dm-menu-highlight, var(--dm-primary))!important;color:#fff!important;border-radius:999px;padding-inline:.9em}@media(prefers-reduced-motion:reduce){[data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),[data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{transition:none;animation:none!important;transform:none!important}}
1
+ [data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),[data-menu-active] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a){transition:color .18s ease,background-color .18s ease,text-shadow .18s ease,transform .18s ease,letter-spacing .18s ease,text-decoration-color .18s ease}.page-body :is([data-menu-hover],[data-menu-active]).dm-menu li>a{color:var(--dm-primary, inherit);text-decoration:none}[data-menu-hover=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),[data-menu-hover=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),#site-navbar[data-menu-hover=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{color:inherit!important;background-color:transparent!important;text-decoration:none!important;text-shadow:none!important;transform:none!important}[data-menu-hover=colour] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=colour] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{color:var(--dm-menu-highlight, var(--dm-primary, #0d6efd))!important;background-color:transparent!important;text-decoration:none!important}[data-menu-hover=underline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=underline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{background-color:transparent!important;text-decoration:underline!important;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-hover=overline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=overline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{background-color:transparent!important;text-decoration:overline!important;text-decoration-thickness:2px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-hover=background] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=background] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{background-color:color-mix(in srgb,var(--dm-menu-highlight, var(--dm-primary, #0d6efd)) 18%,transparent)!important;border-radius:var(--dm-radius-md, 6px);text-decoration:none!important}[data-menu-hover=uppercase] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=uppercase] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{text-transform:uppercase!important;letter-spacing:.04em!important;background-color:transparent!important;text-decoration:none!important}:is([data-menu-hover=grow],[data-menu-hover=breathe]) :is(.dm-menu,.footer-nav) li>a{display:inline-block}[data-menu-hover=grow] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=grow] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{transform:scale(1.08)!important;background-color:transparent!important;text-decoration:none!important}@keyframes dm-menu-breathe{0%,to{opacity:1;transform:scale(1)}50%{opacity:.72;transform:scale(1.05)}}[data-menu-hover=breathe] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=breathe] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{animation:dm-menu-breathe 1.4s ease-in-out infinite!important;background-color:transparent!important;text-decoration:none!important}[data-menu-hover=glow] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,#site-navbar[data-menu-hover=glow] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{text-shadow:0 0 8px color-mix(in srgb,var(--dm-menu-highlight, var(--dm-primary, #0d6efd)) 70%,transparent)!important;background-color:transparent!important;text-decoration:none!important}[data-menu-active] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){box-shadow:none!important;text-decoration:none!important}[data-menu-active=none] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){font-weight:inherit!important;background-color:transparent!important}[data-menu-active=bold] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){font-weight:700!important;background-color:transparent!important}[data-menu-active=underline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:transparent!important;text-decoration:underline!important;text-decoration-thickness:2px;text-underline-offset:4px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-active=overline] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:transparent!important;text-decoration:overline!important;text-decoration-thickness:2px;text-decoration-color:var(--dm-menu-highlight, currentColor)!important}[data-menu-active=background] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:color-mix(in srgb,var(--dm-menu-highlight, var(--dm-primary, #0d6efd)) 18%,transparent)!important;border-radius:var(--dm-radius-md, 6px)}[data-menu-active=darken] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:#00000038!important;border-radius:var(--dm-radius-md, 6px)}[data-menu-active=pill] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):is(.active,.is-active,[aria-current=page]){background-color:var(--dm-menu-highlight, var(--dm-primary, #0d6efd))!important;color:#fff!important;border-radius:999px;padding-inline:.9em}@media(prefers-reduced-motion:reduce){[data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),[data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{transition:none;animation:none!important;transform:none!important}}html.dm-reduced-motion [data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a),html.dm-reduced-motion [data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover,html.dm-reduced-motion #site-navbar[data-menu-hover] :is(.navbar-link,.navbar-dropdown-item,.navbar-dropdown-toggle,.sidebar-link,li>a):hover{transition:none;animation:none!important;transform:none!important}
@@ -1 +1 @@
1
- export const PRESET_COLOUR_VARS={primary:"--dm-primary",success:"--dm-success",danger:"--dm-danger",warning:"--dm-warning",info:"--dm-info",neutral:"--dm-text-muted"};const l=/^#[0-9a-fA-F]{6}$/;export function colourToCss(t){return typeof t!="string"?"":PRESET_COLOUR_VARS[t]?`var(${PRESET_COLOUR_VARS[t]})`:l.test(t)?t:""}export const FLOAT_ANCHORS=["TL","TR","BL","BR","L","R"],FLOAT_DEFAULTS={anchor:"TL",offsetX:16,offsetY:16,zIndex:150},FLOAT_SHADOWS={none:"none",sm:"var(--dm-shadow-sm)",md:"var(--dm-shadow-md)",lg:"var(--dm-shadow-lg)",xl:"var(--dm-shadow-xl)"};function s(t,o,e,r){const n=Number(t);return Number.isFinite(n)?Math.min(r,Math.max(e,Math.round(n))):o}function f(t){const o=[];if(t.radius!=null&&t.radius!==""&&o.push(`border-radius:${s(t.radius,8,0,48)}px`),t.shadow!=null&&t.shadow!==""&&Object.prototype.hasOwnProperty.call(FLOAT_SHADOWS,t.shadow)&&o.push(`box-shadow:${FLOAT_SHADOWS[t.shadow]}`),t.accent&&colourToCss(t.accent)){const e=colourToCss(t.accent);o.push(`--dm-menu-accent:${e}`,`border-color:${e}`)}return t.opacity!=null&&t.opacity!==""&&o.push(`--dm-menu-panel-opacity:${s(t.opacity,100,20,100)/100}`),o}export function floatToCss(t){if(!t||t.position!=="floating")return"";const o=t.float&&typeof t.float=="object"&&!Array.isArray(t.float)?t.float:{},e=FLOAT_ANCHORS.includes(o.anchor)?o.anchor:FLOAT_DEFAULTS.anchor,r=s(o.offsetX,FLOAT_DEFAULTS.offsetX,0,4e3),n=s(o.offsetY,FLOAT_DEFAULTS.offsetY,0,4e3),a=["position:fixed",`z-index:${s(o.zIndex,FLOAT_DEFAULTS.zIndex,0,2147483647)}`];return e==="L"||e==="R"?(a.push("top:50%","transform:translateY(-50%)"),a.push(e==="L"?`left:${r}px`:`right:${r}px`)):(a.push(e[0]==="T"?`top:${n}px`:`bottom:${n}px`),a.push(e[1]==="L"?`left:${r}px`:`right:${r}px`)),o.width!=null&&o.width!==""&&a.push(`width:${s(o.width,260,120,600)}px`),a.push(`max-height:calc(100vh - ${e==="L"||e==="R"?32:n*2}px)`),a.push(...f(o)),a.join(";")}export const FLOAT_FLAGS=["collapsible","collapsed","movable","resizable"];export function floatBehaviour(t){const o={collapsible:!1,collapsed:!1,movable:!1,resizable:!1,chrome:!1,title:""};if(!t||t.position!=="floating")return o;const e=t.float&&typeof t.float=="object"&&!Array.isArray(t.float)?t.float:{},r={collapsible:!!e.collapsible,collapsed:!!e.collapsible&&!!e.collapsed,movable:!!e.movable,resizable:!!e.resizable},n=r.collapsible||r.movable,i=typeof e.title=="string"&&e.title!==""?e.title:t.name||"";return{...r,chrome:n,title:n?i:""}}export function applyFloat(t,o){const e=floatToCss(o);if(!t||!e)return!1;t.style.cssText+=";"+e,t.classList.add("dm-menu-floating"),o.slug&&!t.hasAttribute("data-menu")&&t.setAttribute("data-menu",o.slug),o.float?.anchor&&t.setAttribute("data-float-anchor",o.float.anchor);const r=floatBehaviour(o);for(const n of FLOAT_FLAGS)n!=="collapsed"&&r[n]&&t.setAttribute(`data-float-${n}`,"1");return r.collapsed&&t.setAttribute("data-float-collapsed","1"),r.title&&t.setAttribute("data-float-title",r.title),!0}export function makeBadgeEl(t){if(!t||t.text==null||t.text==="")return null;const o=document.createElement("span");o.className="dm-menu-badge",o.textContent=String(t.text);const e=t.variant?colourToCss(t.variant):"";return e&&(o.style.background=e,o.style.color="var(--dm-text-inverse, #fff)"),o}export function applyColour(t,o){const e=colourToCss(o);e&&(t.style.color=e)}export function applyPill(t,o){if(!o)return;t.classList.add("dm-nav-pill");const e=o.variant?colourToCss(o.variant):"";o.style==="outline"?(t.classList.add("dm-nav-pill--outline"),e&&(t.style.borderColor=e,t.style.color=e)):e&&(t.style.background=e,t.style.color="#fff")}export const HIGHLIGHT_HOVER=["none","colour","underline","overline","background","uppercase","grow","breathe","glow"],HIGHLIGHT_ACTIVE=["none","bold","underline","overline","background","darken","pill"],LEGACY_STYLE_HOVER={none:"none",underline:"underline",overline:"overline",transform:"uppercase","text-transform":"uppercase",breathe:"breathe"},LEGACY_STYLE_ACTIVE={none:"none",darken:"darken",background:"background","background-color":"background",underline:"underline",overline:"overline"};export function legacyStyleHighlight(t){const o=t&&typeof t=="object"&&!Array.isArray(t)?t:{};return{hover:LEGACY_STYLE_HOVER[o.hover]||"",active:LEGACY_STYLE_ACTIVE[o.active]||""}}export function highlightAttrs(t){const o=t&&t.highlight&&typeof t.highlight=="object"&&!Array.isArray(t.highlight)?t.highlight:{},e=legacyStyleHighlight(t&&t.style);return{hover:HIGHLIGHT_HOVER.includes(o.hover)?o.hover:e.hover,active:HIGHLIGHT_ACTIVE.includes(o.active)?o.active:e.active,accent:o.accent?colourToCss(o.accent):""}}export function applyHighlight(t,o){if(!t)return!1;const{hover:e,active:r,accent:n}=highlightAttrs(o);return e&&t.setAttribute("data-menu-hover",e),r&&t.setAttribute("data-menu-active",r),n&&t.style.setProperty("--dm-menu-highlight",n),!!(e||r||n)}function c(t){if(typeof t!="string"||!t)return"";const o=t.split("#")[0].split("?")[0];return o.startsWith("/")?o.length>1&&o.endsWith("/")?o.slice(0,-1):o:""}export function isCurrentItem(t,o){const e=c(t),r=c(o);return!!e&&!!r&&e===r}
1
+ export const PRESET_COLOUR_VARS={primary:"--dm-primary",success:"--dm-success",danger:"--dm-danger",warning:"--dm-warning",info:"--dm-info",neutral:"--dm-text-muted"},PRESET_COLOUR_FALLBACKS={primary:"#0d6efd",success:"#198754",danger:"#dc3545",warning:"#ffc107",info:"#0dcaf0",neutral:"#6c757d"};const l=/^#[0-9a-fA-F]{6}$/;export function colourToCss(t){return typeof t!="string"?"":PRESET_COLOUR_VARS[t]?`var(${PRESET_COLOUR_VARS[t]}, ${PRESET_COLOUR_FALLBACKS[t]})`:l.test(t)?t:""}export const FLOAT_ANCHORS=["TL","TR","BL","BR","L","R"],FLOAT_DEFAULTS={anchor:"TL",offsetX:16,offsetY:16,zIndex:150},FLOAT_SHADOWS={none:"none",sm:"var(--dm-shadow-sm)",md:"var(--dm-shadow-md)",lg:"var(--dm-shadow-lg)",xl:"var(--dm-shadow-xl)"};function s(t,o,e,r){const n=Number(t);return Number.isFinite(n)?Math.min(r,Math.max(e,Math.round(n))):o}function f(t){const o=[];if(t.radius!=null&&t.radius!==""&&o.push(`border-radius:${s(t.radius,8,0,48)}px`),t.shadow!=null&&t.shadow!==""&&Object.prototype.hasOwnProperty.call(FLOAT_SHADOWS,t.shadow)&&o.push(`box-shadow:${FLOAT_SHADOWS[t.shadow]}`),t.accent&&colourToCss(t.accent)){const e=colourToCss(t.accent);o.push(`--dm-menu-accent:${e}`,`border-color:${e}`)}return t.opacity!=null&&t.opacity!==""&&o.push(`--dm-menu-panel-opacity:${s(t.opacity,100,20,100)/100}`),o}export function floatToCss(t){if(!t||t.position!=="floating")return"";const o=t.float&&typeof t.float=="object"&&!Array.isArray(t.float)?t.float:{},e=FLOAT_ANCHORS.includes(o.anchor)?o.anchor:FLOAT_DEFAULTS.anchor,r=s(o.offsetX,FLOAT_DEFAULTS.offsetX,0,4e3),n=s(o.offsetY,FLOAT_DEFAULTS.offsetY,0,4e3),a=["position:fixed",`z-index:${s(o.zIndex,FLOAT_DEFAULTS.zIndex,0,2147483647)}`];return e==="L"||e==="R"?(a.push("top:50%","transform:translateY(-50%)"),a.push(e==="L"?`left:${r}px`:`right:${r}px`)):(a.push(e[0]==="T"?`top:${n}px`:`bottom:${n}px`),a.push(e[1]==="L"?`left:${r}px`:`right:${r}px`)),o.width!=null&&o.width!==""&&a.push(`width:${s(o.width,260,120,600)}px`),a.push(`max-height:calc(100vh - ${e==="L"||e==="R"?32:n*2}px)`),a.push(...f(o)),a.join(";")}export const FLOAT_FLAGS=["collapsible","collapsed","movable","resizable"];export function floatBehaviour(t){const o={collapsible:!1,collapsed:!1,movable:!1,resizable:!1,chrome:!1,title:""};if(!t||t.position!=="floating")return o;const e=t.float&&typeof t.float=="object"&&!Array.isArray(t.float)?t.float:{},r={collapsible:!!e.collapsible,collapsed:!!e.collapsible&&!!e.collapsed,movable:!!e.movable,resizable:!!e.resizable},n=r.collapsible||r.movable,i=typeof e.title=="string"&&e.title!==""?e.title:t.name||"";return{...r,chrome:n,title:n?i:""}}export function applyFloat(t,o){const e=floatToCss(o);if(!t||!e)return!1;t.style.cssText+=";"+e,t.classList.add("dm-menu-floating"),o.slug&&!t.hasAttribute("data-menu")&&t.setAttribute("data-menu",o.slug),o.float?.anchor&&t.setAttribute("data-float-anchor",o.float.anchor);const r=floatBehaviour(o);for(const n of FLOAT_FLAGS)n!=="collapsed"&&r[n]&&t.setAttribute(`data-float-${n}`,"1");return r.collapsed&&t.setAttribute("data-float-collapsed","1"),r.title&&t.setAttribute("data-float-title",r.title),!0}export function makeBadgeEl(t){if(!t||t.text==null||t.text==="")return null;const o=document.createElement("span");o.className="dm-menu-badge",o.textContent=String(t.text);const e=t.variant?colourToCss(t.variant):"";return e&&(o.style.background=e,o.style.color="var(--dm-text-inverse, #fff)"),o}export function applyColour(t,o){const e=colourToCss(o);e&&(t.style.color=e)}export function applyPill(t,o){if(!o)return;t.classList.add("dm-nav-pill");const e=o.variant?colourToCss(o.variant):"";o.style==="outline"?(t.classList.add("dm-nav-pill--outline"),e&&(t.style.borderColor=e,t.style.color=e)):e&&(t.style.background=e,t.style.color="#fff")}export const HIGHLIGHT_HOVER=["none","colour","underline","overline","background","uppercase","grow","breathe","glow"],HIGHLIGHT_ACTIVE=["none","bold","underline","overline","background","darken","pill"],LEGACY_STYLE_HOVER={none:"none",underline:"underline",overline:"overline",transform:"uppercase","text-transform":"uppercase",breathe:"breathe"},LEGACY_STYLE_ACTIVE={none:"none",darken:"darken",background:"background","background-color":"background",underline:"underline",overline:"overline"};export function legacyStyleHighlight(t){const o=t&&typeof t=="object"&&!Array.isArray(t)?t:{};return{hover:LEGACY_STYLE_HOVER[o.hover]||"",active:LEGACY_STYLE_ACTIVE[o.active]||""}}export function highlightAttrs(t){const o=t&&t.highlight&&typeof t.highlight=="object"&&!Array.isArray(t.highlight)?t.highlight:{},e=legacyStyleHighlight(t&&t.style);return{hover:HIGHLIGHT_HOVER.includes(o.hover)?o.hover:e.hover,active:HIGHLIGHT_ACTIVE.includes(o.active)?o.active:e.active,accent:o.accent?colourToCss(o.accent):""}}export function applyHighlight(t,o){if(!t)return!1;const{hover:e,active:r,accent:n}=highlightAttrs(o);return e&&t.setAttribute("data-menu-hover",e),r&&t.setAttribute("data-menu-active",r),n&&t.style.setProperty("--dm-menu-highlight",n),!!(e||r||n)}function c(t){if(typeof t!="string"||!t)return"";const o=t.split("#")[0].split("?")[0];return o.startsWith("/")?o.length>1&&o.endsWith("/")?o.slice(0,-1):o:""}export function isCurrentItem(t,o){const e=c(t),r=c(o);return!!e&&!!r&&e===r}
@@ -18,7 +18,8 @@ import * as cache from './cache/index.js';
18
18
  // imported here so validation and the renderer can never drift apart.
19
19
  import {
20
20
  HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE,
21
- LEGACY_STYLE_HOVER, LEGACY_STYLE_ACTIVE, legacyStyleHighlight
21
+ LEGACY_STYLE_HOVER, LEGACY_STYLE_ACTIVE, legacyStyleHighlight,
22
+ PRESET_COLOUR_VARS, PRESET_COLOUR_FALLBACKS, colourToCss
22
23
  } from '../../public/js/menu-decor.mjs';
23
24
 
24
25
  /**
@@ -60,16 +61,13 @@ export function validateSlug(slug) {
60
61
  // ---------------------------------------------------------------------------
61
62
  // Decoration colour convention - preset key → theme CSS var, or #rrggbb hex.
62
63
  // A "colour value" is one of these preset keys or a 6-digit hex.
63
- // Keep PRESET_COLOUR_VARS in sync with the copy in public/js/menu-decor.mjs.
64
+ //
65
+ // These used to be a hand-kept copy of the pair in public/js/menu-decor.mjs.
66
+ // They are now imported from it and re-exported for the modules that read them
67
+ // from here (menuRender.js), so the two can no longer drift - the same
68
+ // arrangement the highlight helpers above already use.
64
69
  // ---------------------------------------------------------------------------
65
- export const PRESET_COLOUR_VARS = {
66
- primary: '--dm-primary',
67
- success: '--dm-success',
68
- danger: '--dm-danger',
69
- warning: '--dm-warning',
70
- info: '--dm-info',
71
- neutral: '--dm-text-muted'
72
- };
70
+ export {PRESET_COLOUR_VARS, PRESET_COLOUR_FALLBACKS, colourToCss};
73
71
 
74
72
  const HEX_COLOUR_RE = /^#[0-9a-fA-F]{6}$/;
75
73
 
@@ -79,14 +77,6 @@ export function isValidColour(value) {
79
77
  && (Object.prototype.hasOwnProperty.call(PRESET_COLOUR_VARS, value) || HEX_COLOUR_RE.test(value));
80
78
  }
81
79
 
82
- /** Map a colour value to inline-CSS-ready text: var(--dm-…) | #hex | ''. */
83
- export function colourToCss(value) {
84
- if (typeof value !== 'string') return '';
85
- if (PRESET_COLOUR_VARS[value]) return `var(${PRESET_COLOUR_VARS[value]})`;
86
- if (HEX_COLOUR_RE.test(value)) return value;
87
- return '';
88
- }
89
-
90
80
  // ---------------------------------------------------------------------------
91
81
  // Floating menus - a menu with position "floating" detaches from the document
92
82
  // flow and pins itself to a viewport corner or edge, the same way the