@panphora/hyper-cms 0.11.6 → 0.13.0

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/README.md CHANGED
@@ -56,7 +56,7 @@ cms.open({ rules: { title: '.title' } }) // a literal rules object, passed by h
56
56
 
57
57
  ## Auto-open with `?cms=true`
58
58
 
59
- Load any hypercms page with `?cms=true` in the URL and the CMS opens by itself once the DOM is ready — no trigger, no `cms.open()` call. The auto-open uses the host defaults (`document.body` as both `pageRoot` and `mountTo`), and it never double-mounts: if your page also calls `cms.open()`, the second open is a no-op.
59
+ Load any hypercms page with `?cms=true` in the URL and the CMS opens by itself once the DOM is ready — no trigger, no `cms.open()` call. The auto-open uses the host defaults (the whole `document` as `pageRoot`, `document.body` as `mountTo`), and it never double-mounts: if your page also calls `cms.open()`, the second open is a no-op.
60
60
 
61
61
  When the user closes the CMS, hypercms rewrites the param to `cms=false` via `history.replaceState` (the param is kept, no reload), so refreshing or sharing the post-close URL does not auto-reopen:
62
62
 
@@ -96,12 +96,25 @@ The full grammar lives in the [hyper-html-api docs](https://github.com/panphora/
96
96
  | Object array | `"products": [".product", { "name": ".name" }]` | list of cards |
97
97
  | Nested | object arrays inside object arrays | recurses |
98
98
 
99
+ ## Choosing the view
100
+
101
+ A page declares which editor it opens in, on its rules tag:
102
+
103
+ ```html
104
+ <script data-rules-name="cms" data-rules-version="1" data-cms-view="inline" type="application/json">…</script>
105
+ ```
106
+
107
+ `inline` edits text where it sits on the page and opens small popovers for everything else.
108
+ `sidebar` (also what you get with no attribute) opens the form in a side panel. `cms.open()`
109
+ opens whichever view the page declares, and `cms.open({ view: 'inline' })` overrides the
110
+ declaration for one session.
111
+
99
112
  ## Field controls
100
113
 
101
114
  How the sidebar picks a control for each leaf, in order:
102
115
 
103
- 1. If the page element the rule points at carries `data-hcms-component`, that control wins. Explicit beats inferred. Values: `checkbox`, `toggle`, `select`, `radio`, `textarea`, `number`, `image`, `file` on scalar fields, `chips` on list containers.
104
- 2. Otherwise a rule ending in `@src` gets the **image upload** control, and a rule ending in `@checked` gets a **checkbox**.
116
+ 1. If the page element the rule points at carries `data-hcms-component`, that control wins. Explicit beats inferred. Values: `checkbox`, `toggle`, `select`, `radio`, `textarea`, `number`, `image`, `file`, `color`, `font`, `variant` on scalar fields, `chips` on list containers.
117
+ 2. Otherwise a rule ending in `@src` gets the **image upload** control, a rule ending in `@checked` gets a **checkbox**, a rule ending in `@--color` or `@--color-<name>` gets the **color** control, and a rule ending in `@--font` or `@--font-<name>` gets the **font** control.
105
118
  3. Otherwise the rule's shape picks one of the defaults below, unless a [custom template](#custom-form-templates) overrides it.
106
119
 
107
120
  A custom `<template data-hcms-tpl>` matching the field's path always outranks a named component (the author's template is the stronger signal); when that happens the sidebar logs a `console.info` naming the shadowed component.
@@ -194,6 +207,76 @@ A "Choose" button uploads the file, writes the URL into the `a href`, and shows
194
207
 
195
208
  Use it for: a resume PDF, a downloadable spec or whitepaper, a press kit — links whose target the owner should replace by picking a file.
196
209
 
210
+ ### Page style: colors, fonts and layouts (`color`, `font`, `variant`)
211
+
212
+ These three controls change how a page looks rather than what it says. They write CSS custom
213
+ properties or attributes, and the page's own CSS decides what those do.
214
+
215
+ **The `@--name` rule form** reads and writes one CSS custom property in an element's `style`
216
+ attribute, leaving every other declaration alone:
217
+
218
+ ```html
219
+ <html style="--color-accent: #0284c7; --font-body: 'Inter', ui-sans-serif, system-ui, sans-serif">
220
+ <style>
221
+ a, .button { color: var(--color-accent); }
222
+ body { font-family: var(--font-body); }
223
+ </style>
224
+ ```
225
+
226
+ ```json
227
+ { "theme": { "accent": "html@--color-accent", "body": "html@--font-body" } }
228
+ ```
229
+
230
+ Writes through the guarded paths (`writeDocument`, `applyData`, the JSON API) accept a custom
231
+ property only when its value is plain: at most 200 characters, no `;`, braces, angle brackets or
232
+ backslash, and no `url()`, `image()` or similar functions.
233
+
234
+ **Several controls on one element.** Attributes scoped to a property name apply to that rule
235
+ only, so one element can carry a layout choice and a background color:
236
+
237
+ ```html
238
+ <section id="features" data-layout="3" style="--color-section-bg: #f8fafc"
239
+ data-hcms-component:data-layout="variant"
240
+ data-hcms-options:data-layout="1:cols-1 2:cols-2 3:cols-3"
241
+ data-hcms-options:--color-section-bg="slate-50 slate-100 sky-50 custom">
242
+ ```
243
+
244
+ `data-hcms-component:<prop>`, `data-hcms-options:<prop>` and `data-hcms-crop:<prop>` win over the
245
+ plain attribute for the rule ending in `@<prop>`. A plain attribute never applies to an `@--name`
246
+ rule.
247
+
248
+ **Color** (`color`, inferred for `@--color` and `@--color-*`). A swatch grid of the full
249
+ Tailwind palette (26 hues by 11 shades) plus a custom picker. `data-hcms-options` narrows it:
250
+ color ids (`sky-600`), whole hues (`sky`), whole shades (`500`), plain CSS colors, and `custom`
251
+ to keep the custom picker, which a narrowed list hides unless you add it. A clear button empties
252
+ the property so the page falls back to its own CSS.
253
+
254
+ **Font** (`font`, inferred for `@--font` and `@--font-*`). A list of 40 Google Fonts, each shown
255
+ in its own face, plus the system stacks. `data-hcms-options` narrows it: font slugs (`inter`),
256
+ groups (`sans`, `serif`, `display`, `mono`), and `system`. The CMS keeps one
257
+ `<link data-hcms-fonts>` in the head that loads exactly the families the page's font fields hold,
258
+ and removes it when none do.
259
+
260
+ **Variant** (`variant`, never inferred). A row of icon tiles, one per option, backed by native
261
+ radios. Each `data-hcms-options` token is `value:icon` or a bare value; the icons are `cols-1`,
262
+ `cols-2`, `cols-3`, `cols-4`, `media-left`, `media-right`, `media-top`, `list`, `cards`,
263
+ `centered`, `card-flat` and `card-raised`. Choosing one writes only the attribute, so the layout
264
+ lives in your CSS:
265
+
266
+ ```css
267
+ #features { display: grid; gap: 1.5rem; }
268
+ #features[data-layout="2"] { grid-template-columns: repeat(2, 1fr); }
269
+ #features[data-layout="3"] { grid-template-columns: repeat(3, 1fr); }
270
+ ```
271
+
272
+ **Where they show up.** In the sidebar, style fields bound to `html`, `body` or `:root` gather
273
+ in a **Page style** group at the top, and inside every other group and card the style fields
274
+ come first. In the inline view they are never hover targets: a section's style fields open from
275
+ a **Style** chip in its top-left corner, a list row's from **Style** in its gear menu, and the
276
+ page's from the **Page style** button in the bar.
277
+
278
+ Living example: `demo/theme.html`.
279
+
197
280
  ### Everything else: custom templates
198
281
 
199
282
  Anything past the built-ins — multi-select, sliders, color pickers, bespoke layouts, option labels that differ from their values — is [custom template](#custom-form-templates) territory over the same machinery. Living examples: `demo/index.html` (the `quantity` template, a wildcard-path override) and `demo/pixel-quiet.html` (the `bio` template).
@@ -287,7 +370,7 @@ cms.api.removeItem(itemPath) // drop an item by index
287
370
 
288
371
  | Option | Default | Effect |
289
372
  |---|---|---|
290
- | `pageRoot` | `document.body` | Where extract/apply run. |
373
+ | `pageRoot` | `document` | Where extract/apply run. The whole document by default, so a rule can bind `html` or `body` themselves. Events still report `document.body` as `pageRoot`. |
291
374
  | `mountTo` | `document.body` | Where the shell DOM mounts. Can be a descendant of pageRoot. |
292
375
  | `side` | `'right'` | `'right'` or `'left'`. |
293
376
  | `overlay` | `false` | If true, sidebar covers the page; if false, page content gets a padding offset. |
@@ -375,38 +458,13 @@ Common errors thrown by the engine:
375
458
 
376
459
  In hyperclayjs apps, drag works out of the box (hypercms's object-array templates carry the `[sortable]` custom-attribute, which hyperclayjs upgrades). For standalone use, install `sortablejs` and either upgrade the `[sortable]` attribute yourself or replace the template with your own reorder UX. Add/remove still work without sortable.
377
460
 
378
- ## The floating toggle
379
-
380
- In edit mode, on a page that has a cms rules tag, hypercms injects a floating
381
- "Edit content" button in the bottom-right corner. It is runtime-only chrome: it
382
- carries `no-save`, `snapshot-remove` and `save-ignore`, so it never reaches the
383
- saved file and never marks the page dirty.
384
-
385
- It takes the page's font, ink and letterforms by inheritance. The pill's surface
386
- is opaque and neutral, picked between two values by whichever reads better
387
- against the page's own ink, because the button floats over a backdrop it cannot
388
- see and a translucent surface is unreadable on a dark hero or footer.
461
+ ## Opening the CMS
389
462
 
390
- Restyle it with plain CSS. Our cosmetic rules are selected by
391
- `[data-hcms-toggle-host]`, so any `#hcms-toggle { ... }` rule of yours outranks
392
- them:
463
+ Hypercms adds no button of its own. Call `cms.open()` from a control on your page:
393
464
 
394
- #hcms-toggle .hcms-toggle__main { border-radius: 6px; box-shadow: none; }
395
-
396
- Five properties are not reachable that way. `position`, `right`, `bottom`,
397
- `z-index` and `background` are pinned inline with `!important`, so that a host
398
- page's `* { ... !important }` reset cannot leave the button unpositioned or
399
- transparent. `position` is always `fixed`. The other four read a custom
400
- property first, and that is how you change them:
401
-
402
- | property | default | what it does |
403
- |---|---|---|
404
- | `--hcms-toggle-color` | the page's ink | label and focus ring; a plain `#hcms-toggle` rule reaches these too |
405
- | `--hcms-toggle-bg` | the picked surface | the pill's surface; setting it turns off the automatic pick |
406
- | `--hcms-toggle-offset` | `16px` | distance from the corner; it adds to the sidebar shift rather than replacing it |
407
- | `--hcms-toggle-z` | `2147482900` | raise it if a chat widget or cookie banner covers the button |
465
+ <button onclick="hypercms.cms.open()">Edit</button>
408
466
 
409
- :root { --hcms-toggle-offset: 76px; } /* clear a chat widget */
467
+ On a ClayJS page, `?plugins=cms` brings ClayJS's menu, which offers "Open the CMS" to anyone who can edit, when `cms.hasRules()` says the page declares rules. Add `exclude=menu` and open it yourself if you do not want the menu.
410
468
 
411
469
  ## Build, develop, test
412
470