@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 +91 -33
- package/dist/hypercms.min.js +158 -126
- package/package.json +5 -3
- package/src/apply-loop.js +3 -2
- package/src/catalogs/colors.js +293 -0
- package/src/catalogs/fonts.js +126 -0
- package/src/catalogs/index.js +3 -0
- package/src/catalogs/variant-icons.js +24 -0
- package/src/color-control.js +196 -0
- package/src/events.js +25 -2
- package/src/font-control.js +142 -0
- package/src/fonts-link.js +83 -0
- package/src/form-builder.js +85 -3
- package/src/form-rules.js +48 -3
- package/src/hypercms.js +111 -31
- package/src/page-root.js +28 -0
- package/src/refresh.js +4 -1
- package/src/session.js +51 -3
- package/src/targets.js +55 -13
- package/src/templates.js +100 -6
- package/src/theme/pixel-quiet.overrides.css +93 -8
- package/src/theme.generated.css +93 -8
- package/src/variant-control.js +46 -0
- package/src/views/inline-layer.js +73 -18
- package/src/views/inline.js +87 -17
- package/src/toggle.js +0 -681
package/src/templates.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ruleAttrIndex } from 'hyper-html-api/engine'
|
|
2
2
|
import { getRuleAtPath } from './path.js'
|
|
3
3
|
|
|
4
|
+
// --font-* names that hold something other than a family (Tailwind 4 uses
|
|
5
|
+
// --font-weight-*, many pages use --font-size-*), so they never get the font menu.
|
|
6
|
+
const NOT_A_FAMILY = /^--font-(size|weight|style|stretch|variant|feature-settings|variation-settings|optical-sizing|kerning|synthesis|smoothing|palette|size-adjust|display)(-|$)/
|
|
7
|
+
|
|
4
8
|
export function humanize(key) {
|
|
5
9
|
return key
|
|
6
10
|
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
@@ -194,6 +198,59 @@ const DEFAULT_TEMPLATES = {
|
|
|
194
198
|
<div class="hcms-error" hidden></div>
|
|
195
199
|
</label>
|
|
196
200
|
`,
|
|
201
|
+
// @color: the hidden input is the field (the form commits it like any
|
|
202
|
+
// scalar); swatches, the current chip and the custom inputs are built and
|
|
203
|
+
// painted by color-control.js.
|
|
204
|
+
'@color': `
|
|
205
|
+
<div class="hcms-field hcms-color" data-hcms-shape="scalar">
|
|
206
|
+
<span class="hcms-label" data-hcms-label></span>
|
|
207
|
+
<input type="hidden" data-hcms-field />
|
|
208
|
+
<div class="hcms-color-row">
|
|
209
|
+
<button type="button" class="hcms-color-current" data-hcms-action="color-panel" aria-expanded="false">
|
|
210
|
+
<span class="hcms-color-chip"></span>
|
|
211
|
+
<span class="hcms-color-name"></span>
|
|
212
|
+
</button>
|
|
213
|
+
<button type="button" class="hcms-color-clear" data-hcms-action="color-clear" aria-label="Clear color">${CLOSE_ICON}</button>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="hcms-color-panel" hidden>
|
|
216
|
+
<div class="hcms-color-grid"></div>
|
|
217
|
+
<div class="hcms-color-custom">
|
|
218
|
+
<input type="color" class="hcms-color-native" data-hcms-color-input="native" aria-label="Pick a custom color" />
|
|
219
|
+
<input class="mirk-input hcms-color-text" data-hcms-color-input="text" spellcheck="false" autocomplete="off" placeholder="Any CSS color" aria-label="Custom color" />
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="hcms-error" hidden></div>
|
|
223
|
+
</div>
|
|
224
|
+
`,
|
|
225
|
+
'@font': `
|
|
226
|
+
<div class="hcms-field hcms-font" data-hcms-shape="scalar">
|
|
227
|
+
<span class="hcms-label" data-hcms-label></span>
|
|
228
|
+
<input type="hidden" data-hcms-field />
|
|
229
|
+
<button type="button" class="hcms-font-current" data-hcms-action="font-panel" aria-haspopup="listbox" aria-expanded="false">
|
|
230
|
+
<span class="hcms-font-name"></span>
|
|
231
|
+
<span aria-hidden="true" class="hcms-font-chevron">›</span>
|
|
232
|
+
</button>
|
|
233
|
+
<div class="hcms-font-panel" hidden>
|
|
234
|
+
<div class="hcms-font-list" role="listbox"></div>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="hcms-error" hidden></div>
|
|
237
|
+
</div>
|
|
238
|
+
`,
|
|
239
|
+
// @variant: a radio group of layout choices, each an icon plus a label; the
|
|
240
|
+
// page's own CSS keys on the attribute the choice writes.
|
|
241
|
+
'@variant': `
|
|
242
|
+
<div class="hcms-field hcms-variant" data-hcms-shape="scalar">
|
|
243
|
+
<span class="hcms-label" data-hcms-label></span>
|
|
244
|
+
<div class="hcms-variant-row" role="radiogroup">
|
|
245
|
+
<label class="hcms-variant-option">
|
|
246
|
+
<input type="radio" class="mirk-sr-only" data-hcms-field />
|
|
247
|
+
<span class="hcms-variant-icon"></span>
|
|
248
|
+
<span class="hcms-variant-label"></span>
|
|
249
|
+
</label>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="hcms-error" hidden></div>
|
|
252
|
+
</div>
|
|
253
|
+
`,
|
|
197
254
|
'@chips': `
|
|
198
255
|
<div class="hcms-field hcms-chips" data-hcms-shape="scalar-array">
|
|
199
256
|
<span class="hcms-label" data-hcms-label></span>
|
|
@@ -247,7 +304,9 @@ export function injectComponentTemplate(doc, key) {
|
|
|
247
304
|
// "true" helps nobody). @href is NOT inferred: editing a link/URL is far more
|
|
248
305
|
// common than uploading a file, so an a@href rule stays a plain URL field and
|
|
249
306
|
// the file upload is opt-in. Suffix is split like the engine (ruleAttrIndex,
|
|
250
|
-
// see engine/extract.js).
|
|
307
|
+
// see engine/extract.js). A custom property named --color or --color-* infers
|
|
308
|
+
// the color picker; --font / --font-* infers the font menu. Returns a
|
|
309
|
+
// DEFAULT_TEMPLATES key.
|
|
251
310
|
const PROP_TO_COMPONENT = { src: '@image', checked: '@checkbox', innerHTML: '@richtext' }
|
|
252
311
|
// data-hcms-component values for scalar rules. "chips" is array-only (below).
|
|
253
312
|
const SCALAR_COMPONENT_BY_NAME = {
|
|
@@ -260,6 +319,9 @@ const SCALAR_COMPONENT_BY_NAME = {
|
|
|
260
319
|
textarea: '@textarea',
|
|
261
320
|
number: '@number',
|
|
262
321
|
richtext: '@richtext',
|
|
322
|
+
color: '@color',
|
|
323
|
+
font: '@font',
|
|
324
|
+
variant: '@variant',
|
|
263
325
|
}
|
|
264
326
|
// Every opt-in component injects on demand; only the 6 shape templates are
|
|
265
327
|
// always present.
|
|
@@ -273,7 +335,7 @@ export function componentForScalarRule(rule, doc, pathArr, pageRules) {
|
|
|
273
335
|
if (typeof rule !== 'string') return '@scalar'
|
|
274
336
|
const at = ruleAttrIndex(rule)
|
|
275
337
|
const host = hostSelectorOf(rule, at, pathArr, pageRules)
|
|
276
|
-
const override =
|
|
338
|
+
const override = readRuleAttr(rule, at, pathArr, pageRules, doc, 'data-hcms-component')
|
|
277
339
|
if (override && SCALAR_COMPONENT_BY_NAME[override]) {
|
|
278
340
|
const key = SCALAR_COMPONENT_BY_NAME[override]
|
|
279
341
|
// Value guards check exactly the elements the engine binds: under an array
|
|
@@ -296,6 +358,8 @@ export function componentForScalarRule(rule, doc, pathArr, pageRules) {
|
|
|
296
358
|
if (at >= 0) {
|
|
297
359
|
const prop = rule.slice(at + 1)
|
|
298
360
|
if (PROP_TO_COMPONENT[prop]) return PROP_TO_COMPONENT[prop]
|
|
361
|
+
if (/^--color(-|$)/.test(prop) && prop !== '--color-scheme') return '@color'
|
|
362
|
+
if (/^--font(-|$)/.test(prop) && !NOT_A_FAMILY.test(prop)) return '@font'
|
|
299
363
|
}
|
|
300
364
|
return '@scalar'
|
|
301
365
|
}
|
|
@@ -307,10 +371,24 @@ export function componentForScalarRule(rule, doc, pathArr, pageRules) {
|
|
|
307
371
|
export function declaredComponentKey(rule, doc, pathArr, pageRules) {
|
|
308
372
|
if (typeof rule !== 'string') return null
|
|
309
373
|
const at = ruleAttrIndex(rule)
|
|
310
|
-
const name =
|
|
374
|
+
const name = readRuleAttr(rule, at, pathArr, pageRules, doc, 'data-hcms-component')
|
|
311
375
|
return (name && SCALAR_COMPONENT_BY_NAME[name]) || null
|
|
312
376
|
}
|
|
313
377
|
|
|
378
|
+
// Style fields (color, font, variant) are grouped apart from content. One bound
|
|
379
|
+
// to html, body or :root outside any list styles the whole page ('page'); any
|
|
380
|
+
// other styles its own element ('section'). Content fields return null.
|
|
381
|
+
const STYLE_COMPONENTS = new Set(['@color', '@font', '@variant'])
|
|
382
|
+
const PAGE_HOSTS = new Set(['html', 'body', ':root'])
|
|
383
|
+
|
|
384
|
+
export function styleRole(rule, doc, pathArr, pageRules) {
|
|
385
|
+
if (typeof rule !== 'string' || rule.endsWith('[]')) return null
|
|
386
|
+
if (!STYLE_COMPONENTS.has(componentForScalarRule(rule, doc, pathArr, pageRules))) return null
|
|
387
|
+
const inList = Array.isArray(pathArr) && pathArr.some((s) => s === '*' || typeof s === 'number')
|
|
388
|
+
const selector = scalarSelectorOf(rule, ruleAttrIndex(rule)).trim()
|
|
389
|
+
return !inList && PAGE_HOSTS.has(selector) ? 'page' : 'section'
|
|
390
|
+
}
|
|
391
|
+
|
|
314
392
|
// A number input physically rejects any value that isn't a plain number — it
|
|
315
393
|
// reads back '' — and the whole-form commit would then blank the page value on
|
|
316
394
|
// the next edit of ANY field. When any bound value isn't number-shaped, fall
|
|
@@ -416,7 +494,7 @@ export function winningScalarArrayComponent(rule, pathArr, doc) {
|
|
|
416
494
|
export function readOptionsOverride(rule, doc, pathArr, pageRules) {
|
|
417
495
|
if (typeof rule !== 'string') return null
|
|
418
496
|
const at = ruleAttrIndex(rule)
|
|
419
|
-
const raw =
|
|
497
|
+
const raw = readRuleAttr(rule, at, pathArr, pageRules, doc, 'data-hcms-options')
|
|
420
498
|
if (raw == null) return null
|
|
421
499
|
const tokens = raw.trim().split(/\s+/).filter(Boolean)
|
|
422
500
|
return tokens.length ? tokens : null
|
|
@@ -428,7 +506,7 @@ export function readOptionsOverride(rule, doc, pathArr, pageRules) {
|
|
|
428
506
|
export function readCropOverride(rule, doc, pathArr, pageRules) {
|
|
429
507
|
if (typeof rule !== 'string') return null
|
|
430
508
|
const at = ruleAttrIndex(rule)
|
|
431
|
-
return
|
|
509
|
+
return readRuleAttr(rule, at, pathArr, pageRules, doc, 'data-hcms-crop')
|
|
432
510
|
}
|
|
433
511
|
|
|
434
512
|
function scalarSelectorOf(rule, at) {
|
|
@@ -493,6 +571,22 @@ function readElementAttr(selector, doc, attrName) {
|
|
|
493
571
|
return el && el.getAttribute ? el.getAttribute(attrName) : null
|
|
494
572
|
}
|
|
495
573
|
|
|
574
|
+
// The authoring attribute for ONE rule. An element can carry several rules
|
|
575
|
+
// (<html> holds every theme property), so data-hcms-component:--color-accent
|
|
576
|
+
// names the control for the @--color-accent rule alone and wins over the plain
|
|
577
|
+
// attribute. The plain attribute never applies to a @--* rule: it describes the
|
|
578
|
+
// element's own value, not a CSS variable stored on it.
|
|
579
|
+
function readRuleAttr(rule, at, pathArr, pageRules, doc, attrName) {
|
|
580
|
+
const host = hostSelectorOf(rule, at, pathArr, pageRules)
|
|
581
|
+
const prop = at >= 0 ? rule.slice(at + 1) : null
|
|
582
|
+
if (prop) {
|
|
583
|
+
const scoped = readElementAttr(host, doc, `${attrName}:${prop}`)
|
|
584
|
+
if (scoped != null) return scoped
|
|
585
|
+
if (prop.startsWith('--')) return null
|
|
586
|
+
}
|
|
587
|
+
return readElementAttr(host, doc, attrName)
|
|
588
|
+
}
|
|
589
|
+
|
|
496
590
|
// Pre-inject the component templates this page's rules actually select, so
|
|
497
591
|
// both deriveFormRules (selectors) and buildForm (DOM) resolve them. Walks the
|
|
498
592
|
// same rule shapes as buildTemplateMap; idempotent and author-template-safe.
|
|
@@ -604,7 +698,7 @@ export function buildTemplateMap(pageRules, doc) {
|
|
|
604
698
|
const winningComp = shape === 'scalar-array' ? winningScalarArrayComponent(rule, pathArr, doc) : null
|
|
605
699
|
const defaultKey =
|
|
606
700
|
shape === 'scalar'
|
|
607
|
-
? componentForScalarRule(rule, doc, pathArr)
|
|
701
|
+
? componentForScalarRule(rule, doc, pathArr, pageRules)
|
|
608
702
|
: (arrayComp && arrayComp.array) || SHAPE_TO_DEFAULT_KEY[shape] || '@scalar'
|
|
609
703
|
|
|
610
704
|
let tpl =
|
|
@@ -140,20 +140,14 @@
|
|
|
140
140
|
box-shadow: 16px 0 48px -28px rgba(43, 36, 27, 0.45);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
/* Push the page over so docked content is never hidden underneath
|
|
144
|
-
the geometry the floating toggle has to compose with. --hcms-toggle-shift is
|
|
145
|
-
the distance the toggle moves left, which is the shell width only while the
|
|
146
|
-
shell is docked on the right; docked left, as an overlay, or full-viewport it
|
|
147
|
-
is zero. */
|
|
143
|
+
/* Push the page over so docked content is never hidden underneath. */
|
|
148
144
|
body.hcms-open { --hcms-shell-width: 380px; }
|
|
149
145
|
body.hcms-open:not(.hcms-overlay) { padding-right: var(--hcms-shell-width); }
|
|
150
|
-
body.hcms-open:not(.hcms-overlay):not(.hcms-side-left) { --hcms-toggle-shift: var(--hcms-shell-width); }
|
|
151
146
|
body.hcms-open.hcms-side-left:not(.hcms-overlay) { padding-right: 0; padding-left: var(--hcms-shell-width); }
|
|
152
|
-
body.hcms-open.hcms-overlay { overflow: hidden;
|
|
147
|
+
body.hcms-open.hcms-overlay { overflow: hidden; }
|
|
153
148
|
|
|
154
149
|
@media (max-width: 799px) {
|
|
155
150
|
body.hcms-open { --hcms-shell-width: 100vw; }
|
|
156
|
-
body.hcms-open:not(.hcms-overlay):not(.hcms-side-left) { --hcms-toggle-shift: 0px; }
|
|
157
151
|
body.hcms-open:not(.hcms-overlay),
|
|
158
152
|
body.hcms-open.hcms-side-left:not(.hcms-overlay) { padding-right: 0; padding-left: 0; }
|
|
159
153
|
body.hcms-open { overflow: hidden; }
|
|
@@ -458,6 +452,94 @@ body.hcms-open.hcms-overlay { overflow: hidden; --hcms-toggle-display: none; }
|
|
|
458
452
|
color: var(--mirk-placeholder-color);
|
|
459
453
|
}
|
|
460
454
|
|
|
455
|
+
/* ---------- COLOR (@color) ----------
|
|
456
|
+
Swatch colors arrive as --hcms-swatch on each element's style, so a value
|
|
457
|
+
never becomes a selector or a class. The full palette is an 11-column grid
|
|
458
|
+
(one row per hue); a narrowed set wraps. */
|
|
459
|
+
.hcms-color-row { display: flex; align-items: center; gap: 8px; }
|
|
460
|
+
.hcms-color-current {
|
|
461
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
462
|
+
padding: 4px 12px 4px 4px; border: 1px solid var(--mirk-input-border); border-radius: 999px;
|
|
463
|
+
background: var(--mirk-input-bg); color: var(--mirk-fg); font: inherit; cursor: pointer;
|
|
464
|
+
}
|
|
465
|
+
.hcms-color--inline .hcms-color-current { cursor: default; }
|
|
466
|
+
.hcms-color-chip, .hcms-color-swatch { background: var(--hcms-swatch, transparent); }
|
|
467
|
+
.hcms-color-chip {
|
|
468
|
+
width: 20px; height: 20px; border-radius: 50%;
|
|
469
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--mirk-fg) 20%, transparent);
|
|
470
|
+
}
|
|
471
|
+
.hcms-color[data-hcms-color-empty] .hcms-color-chip {
|
|
472
|
+
background: repeating-conic-gradient(var(--mirk-input-border) 0 25%, transparent 0 50%) 50% / 8px 8px;
|
|
473
|
+
}
|
|
474
|
+
.hcms-color-clear {
|
|
475
|
+
display: inline-flex; padding: 2px; border: 0; background: transparent;
|
|
476
|
+
color: var(--mirk-placeholder-color); cursor: pointer;
|
|
477
|
+
}
|
|
478
|
+
.hcms-color-clear:hover { color: var(--mirk-fg); }
|
|
479
|
+
.hcms-color[data-hcms-color-empty] .hcms-color-clear { display: none; }
|
|
480
|
+
.hcms-color-panel { margin-top: 8px; }
|
|
481
|
+
.hcms-color-panel[hidden] { display: none; }
|
|
482
|
+
.hcms-color-grid { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
483
|
+
.hcms-color-grid--full { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 3px; }
|
|
484
|
+
.hcms-color-swatch {
|
|
485
|
+
width: 22px; height: 22px; padding: 0; border: 0; border-radius: 4px; cursor: pointer;
|
|
486
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--mirk-fg) 15%, transparent);
|
|
487
|
+
}
|
|
488
|
+
.hcms-color-grid--full .hcms-color-swatch { width: auto; height: auto; aspect-ratio: 1; }
|
|
489
|
+
.hcms-color-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--mirk-bg), 0 0 0 4px var(--mirk-fg); }
|
|
490
|
+
.hcms-color-swatch:focus-visible { outline: 2px solid var(--mirk-focus-color); outline-offset: 2px; }
|
|
491
|
+
.hcms-color-custom { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
|
492
|
+
.hcms-color-native {
|
|
493
|
+
width: 32px; height: 28px; padding: 0; border: 1px solid var(--mirk-input-border);
|
|
494
|
+
border-radius: 4px; background: none; cursor: pointer;
|
|
495
|
+
}
|
|
496
|
+
.hcms-color-text { flex: 1; min-width: 0; }
|
|
497
|
+
|
|
498
|
+
/* ---------- FONT (@font) ----------
|
|
499
|
+
Each option sets its own font-family inline, so the list previews every
|
|
500
|
+
family in its face once the editor-only preview stylesheet arrives. */
|
|
501
|
+
.hcms-font-current {
|
|
502
|
+
display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
|
|
503
|
+
padding: 6px 10px; border: 1px solid var(--mirk-input-border); border-radius: 4px;
|
|
504
|
+
background: var(--mirk-input-bg); color: var(--mirk-fg); font: inherit; cursor: pointer; text-align: left;
|
|
505
|
+
}
|
|
506
|
+
.hcms-font-name { font-size: 15px; }
|
|
507
|
+
.hcms-font-chevron { transform: rotate(90deg); color: var(--mirk-placeholder-color); }
|
|
508
|
+
.hcms-font-panel { margin-top: 6px; }
|
|
509
|
+
.hcms-font-panel[hidden] { display: none; }
|
|
510
|
+
.hcms-font-list {
|
|
511
|
+
display: flex; flex-direction: column; max-height: 320px; overflow-y: auto;
|
|
512
|
+
border: 1px solid var(--mirk-input-border); border-radius: 4px; background: var(--mirk-input-bg);
|
|
513
|
+
}
|
|
514
|
+
.hcms-font-group {
|
|
515
|
+
padding: 8px 10px 4px; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
|
|
516
|
+
color: var(--mirk-placeholder-color);
|
|
517
|
+
}
|
|
518
|
+
.hcms-font-option {
|
|
519
|
+
padding: 6px 10px; border: 0; background: transparent; color: var(--mirk-fg);
|
|
520
|
+
font-size: 16px; text-align: left; cursor: pointer;
|
|
521
|
+
}
|
|
522
|
+
.hcms-font-option:hover { background: color-mix(in oklab, var(--mirk-fg) 8%, transparent); }
|
|
523
|
+
.hcms-font-option[aria-selected="true"] { background: color-mix(in oklab, var(--mirk-fg) 14%, transparent); }
|
|
524
|
+
.hcms-font-option:focus-visible { outline: 2px solid var(--mirk-focus-color); outline-offset: -2px; }
|
|
525
|
+
|
|
526
|
+
/* ---------- VARIANT (@variant) ----------
|
|
527
|
+
Native radios behind icon tiles: arrow keys move the choice, the tile with
|
|
528
|
+
the checked radio is the selected one. */
|
|
529
|
+
.hcms-variant-row { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
530
|
+
.hcms-variant-option {
|
|
531
|
+
position: relative; display: inline-flex; flex-direction: column; align-items: center; gap: 4px;
|
|
532
|
+
min-width: 64px; padding: 8px 10px; border: 1px solid var(--mirk-input-border); border-radius: 6px;
|
|
533
|
+
background: var(--mirk-input-bg); color: var(--mirk-placeholder-color); font-size: 12px; cursor: pointer;
|
|
534
|
+
}
|
|
535
|
+
.hcms-variant-option:hover { color: var(--mirk-fg); }
|
|
536
|
+
.hcms-variant-option:has(input:checked) {
|
|
537
|
+
color: var(--mirk-fg); border-color: var(--mirk-fg);
|
|
538
|
+
box-shadow: inset 0 0 0 1px var(--mirk-fg);
|
|
539
|
+
}
|
|
540
|
+
.hcms-variant-option:has(input:focus-visible) { outline: 2px solid var(--mirk-focus-color); outline-offset: 2px; }
|
|
541
|
+
.hcms-variant-icon { display: inline-flex; }
|
|
542
|
+
|
|
461
543
|
/* ---------- UPLOADING (spec §9) ----------
|
|
462
544
|
One attribute, [data-hcms-uploading], set on the field for as long as the host
|
|
463
545
|
has the bytes, plus --hcms-upload-progress carrying the percent. No markup of
|
|
@@ -826,6 +908,9 @@ tr.hcms-shell.hcms-inline-ghost { display: table-row; }
|
|
|
826
908
|
.hcms-inline .hcms-form [data-hcms-path] { display: none; }
|
|
827
909
|
.hcms-inline .hcms-form [data-hcms-path].is-hcms-inline-onpath,
|
|
828
910
|
.hcms-inline .hcms-form [data-hcms-path].is-hcms-inline-active { display: block; }
|
|
911
|
+
/* The Page style group has no data path of its own: hide it like a path until
|
|
912
|
+
a reveal marks it on-path. */
|
|
913
|
+
.hcms-inline .hcms-form [data-hcms-page-style]:not(.is-hcms-inline-onpath) { display: none; }
|
|
829
914
|
|
|
830
915
|
.hcms-inline .hcms-form .is-hcms-inline-onpath,
|
|
831
916
|
.hcms-inline .hcms-form {
|
package/src/theme.generated.css
CHANGED
|
@@ -1151,20 +1151,14 @@
|
|
|
1151
1151
|
box-shadow: 16px 0 48px -28px rgba(43, 36, 27, 0.45);
|
|
1152
1152
|
}
|
|
1153
1153
|
|
|
1154
|
-
/* Push the page over so docked content is never hidden underneath
|
|
1155
|
-
the geometry the floating toggle has to compose with. --hcms-toggle-shift is
|
|
1156
|
-
the distance the toggle moves left, which is the shell width only while the
|
|
1157
|
-
shell is docked on the right; docked left, as an overlay, or full-viewport it
|
|
1158
|
-
is zero. */
|
|
1154
|
+
/* Push the page over so docked content is never hidden underneath. */
|
|
1159
1155
|
body.hcms-open { --hcms-shell-width: 380px; }
|
|
1160
1156
|
body.hcms-open:not(.hcms-overlay) { padding-right: var(--hcms-shell-width); }
|
|
1161
|
-
body.hcms-open:not(.hcms-overlay):not(.hcms-side-left) { --hcms-toggle-shift: var(--hcms-shell-width); }
|
|
1162
1157
|
body.hcms-open.hcms-side-left:not(.hcms-overlay) { padding-right: 0; padding-left: var(--hcms-shell-width); }
|
|
1163
|
-
body.hcms-open.hcms-overlay { overflow: hidden;
|
|
1158
|
+
body.hcms-open.hcms-overlay { overflow: hidden; }
|
|
1164
1159
|
|
|
1165
1160
|
@media (max-width: 799px) {
|
|
1166
1161
|
body.hcms-open { --hcms-shell-width: 100vw; }
|
|
1167
|
-
body.hcms-open:not(.hcms-overlay):not(.hcms-side-left) { --hcms-toggle-shift: 0px; }
|
|
1168
1162
|
body.hcms-open:not(.hcms-overlay),
|
|
1169
1163
|
body.hcms-open.hcms-side-left:not(.hcms-overlay) { padding-right: 0; padding-left: 0; }
|
|
1170
1164
|
body.hcms-open { overflow: hidden; }
|
|
@@ -1469,6 +1463,94 @@ body.hcms-open.hcms-overlay { overflow: hidden; --hcms-toggle-display: none; }
|
|
|
1469
1463
|
color: var(--mirk-placeholder-color);
|
|
1470
1464
|
}
|
|
1471
1465
|
|
|
1466
|
+
/* ---------- COLOR (@color) ----------
|
|
1467
|
+
Swatch colors arrive as --hcms-swatch on each element's style, so a value
|
|
1468
|
+
never becomes a selector or a class. The full palette is an 11-column grid
|
|
1469
|
+
(one row per hue); a narrowed set wraps. */
|
|
1470
|
+
.hcms-color-row { display: flex; align-items: center; gap: 8px; }
|
|
1471
|
+
.hcms-color-current {
|
|
1472
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
1473
|
+
padding: 4px 12px 4px 4px; border: 1px solid var(--mirk-input-border); border-radius: 999px;
|
|
1474
|
+
background: var(--mirk-input-bg); color: var(--mirk-fg); font: inherit; cursor: pointer;
|
|
1475
|
+
}
|
|
1476
|
+
.hcms-color--inline .hcms-color-current { cursor: default; }
|
|
1477
|
+
.hcms-color-chip, .hcms-color-swatch { background: var(--hcms-swatch, transparent); }
|
|
1478
|
+
.hcms-color-chip {
|
|
1479
|
+
width: 20px; height: 20px; border-radius: 50%;
|
|
1480
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--mirk-fg) 20%, transparent);
|
|
1481
|
+
}
|
|
1482
|
+
.hcms-color[data-hcms-color-empty] .hcms-color-chip {
|
|
1483
|
+
background: repeating-conic-gradient(var(--mirk-input-border) 0 25%, transparent 0 50%) 50% / 8px 8px;
|
|
1484
|
+
}
|
|
1485
|
+
.hcms-color-clear {
|
|
1486
|
+
display: inline-flex; padding: 2px; border: 0; background: transparent;
|
|
1487
|
+
color: var(--mirk-placeholder-color); cursor: pointer;
|
|
1488
|
+
}
|
|
1489
|
+
.hcms-color-clear:hover { color: var(--mirk-fg); }
|
|
1490
|
+
.hcms-color[data-hcms-color-empty] .hcms-color-clear { display: none; }
|
|
1491
|
+
.hcms-color-panel { margin-top: 8px; }
|
|
1492
|
+
.hcms-color-panel[hidden] { display: none; }
|
|
1493
|
+
.hcms-color-grid { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
1494
|
+
.hcms-color-grid--full { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 3px; }
|
|
1495
|
+
.hcms-color-swatch {
|
|
1496
|
+
width: 22px; height: 22px; padding: 0; border: 0; border-radius: 4px; cursor: pointer;
|
|
1497
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--mirk-fg) 15%, transparent);
|
|
1498
|
+
}
|
|
1499
|
+
.hcms-color-grid--full .hcms-color-swatch { width: auto; height: auto; aspect-ratio: 1; }
|
|
1500
|
+
.hcms-color-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--mirk-bg), 0 0 0 4px var(--mirk-fg); }
|
|
1501
|
+
.hcms-color-swatch:focus-visible { outline: 2px solid var(--mirk-focus-color); outline-offset: 2px; }
|
|
1502
|
+
.hcms-color-custom { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
|
1503
|
+
.hcms-color-native {
|
|
1504
|
+
width: 32px; height: 28px; padding: 0; border: 1px solid var(--mirk-input-border);
|
|
1505
|
+
border-radius: 4px; background: none; cursor: pointer;
|
|
1506
|
+
}
|
|
1507
|
+
.hcms-color-text { flex: 1; min-width: 0; }
|
|
1508
|
+
|
|
1509
|
+
/* ---------- FONT (@font) ----------
|
|
1510
|
+
Each option sets its own font-family inline, so the list previews every
|
|
1511
|
+
family in its face once the editor-only preview stylesheet arrives. */
|
|
1512
|
+
.hcms-font-current {
|
|
1513
|
+
display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
|
|
1514
|
+
padding: 6px 10px; border: 1px solid var(--mirk-input-border); border-radius: 4px;
|
|
1515
|
+
background: var(--mirk-input-bg); color: var(--mirk-fg); font: inherit; cursor: pointer; text-align: left;
|
|
1516
|
+
}
|
|
1517
|
+
.hcms-font-name { font-size: 15px; }
|
|
1518
|
+
.hcms-font-chevron { transform: rotate(90deg); color: var(--mirk-placeholder-color); }
|
|
1519
|
+
.hcms-font-panel { margin-top: 6px; }
|
|
1520
|
+
.hcms-font-panel[hidden] { display: none; }
|
|
1521
|
+
.hcms-font-list {
|
|
1522
|
+
display: flex; flex-direction: column; max-height: 320px; overflow-y: auto;
|
|
1523
|
+
border: 1px solid var(--mirk-input-border); border-radius: 4px; background: var(--mirk-input-bg);
|
|
1524
|
+
}
|
|
1525
|
+
.hcms-font-group {
|
|
1526
|
+
padding: 8px 10px 4px; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
|
|
1527
|
+
color: var(--mirk-placeholder-color);
|
|
1528
|
+
}
|
|
1529
|
+
.hcms-font-option {
|
|
1530
|
+
padding: 6px 10px; border: 0; background: transparent; color: var(--mirk-fg);
|
|
1531
|
+
font-size: 16px; text-align: left; cursor: pointer;
|
|
1532
|
+
}
|
|
1533
|
+
.hcms-font-option:hover { background: color-mix(in oklab, var(--mirk-fg) 8%, transparent); }
|
|
1534
|
+
.hcms-font-option[aria-selected="true"] { background: color-mix(in oklab, var(--mirk-fg) 14%, transparent); }
|
|
1535
|
+
.hcms-font-option:focus-visible { outline: 2px solid var(--mirk-focus-color); outline-offset: -2px; }
|
|
1536
|
+
|
|
1537
|
+
/* ---------- VARIANT (@variant) ----------
|
|
1538
|
+
Native radios behind icon tiles: arrow keys move the choice, the tile with
|
|
1539
|
+
the checked radio is the selected one. */
|
|
1540
|
+
.hcms-variant-row { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
1541
|
+
.hcms-variant-option {
|
|
1542
|
+
position: relative; display: inline-flex; flex-direction: column; align-items: center; gap: 4px;
|
|
1543
|
+
min-width: 64px; padding: 8px 10px; border: 1px solid var(--mirk-input-border); border-radius: 6px;
|
|
1544
|
+
background: var(--mirk-input-bg); color: var(--mirk-placeholder-color); font-size: 12px; cursor: pointer;
|
|
1545
|
+
}
|
|
1546
|
+
.hcms-variant-option:hover { color: var(--mirk-fg); }
|
|
1547
|
+
.hcms-variant-option:has(input:checked) {
|
|
1548
|
+
color: var(--mirk-fg); border-color: var(--mirk-fg);
|
|
1549
|
+
box-shadow: inset 0 0 0 1px var(--mirk-fg);
|
|
1550
|
+
}
|
|
1551
|
+
.hcms-variant-option:has(input:focus-visible) { outline: 2px solid var(--mirk-focus-color); outline-offset: 2px; }
|
|
1552
|
+
.hcms-variant-icon { display: inline-flex; }
|
|
1553
|
+
|
|
1472
1554
|
/* ---------- UPLOADING (spec §9) ----------
|
|
1473
1555
|
One attribute, [data-hcms-uploading], set on the field for as long as the host
|
|
1474
1556
|
has the bytes, plus --hcms-upload-progress carrying the percent. No markup of
|
|
@@ -1837,6 +1919,9 @@ tr.hcms-shell.hcms-inline-ghost { display: table-row; }
|
|
|
1837
1919
|
.hcms-inline .hcms-form [data-hcms-path] { display: none; }
|
|
1838
1920
|
.hcms-inline .hcms-form [data-hcms-path].is-hcms-inline-onpath,
|
|
1839
1921
|
.hcms-inline .hcms-form [data-hcms-path].is-hcms-inline-active { display: block; }
|
|
1922
|
+
/* The Page style group has no data path of its own: hide it like a path until
|
|
1923
|
+
a reveal marks it on-path. */
|
|
1924
|
+
.hcms-inline .hcms-form [data-hcms-page-style]:not(.is-hcms-inline-onpath) { display: none; }
|
|
1840
1925
|
|
|
1841
1926
|
.hcms-inline .hcms-form .is-hcms-inline-onpath,
|
|
1842
1927
|
.hcms-inline .hcms-form {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { VARIANT_ICONS } from './catalogs/variant-icons.js'
|
|
2
|
+
import { humanize } from './templates.js'
|
|
3
|
+
|
|
4
|
+
// "2:cols-2" → { value: '2', icon: 'cols-2' }; a bare "cards" → its own icon
|
|
5
|
+
// when one exists. Split on the first colon only, so a value never loses text.
|
|
6
|
+
export function parseVariantOptions(tokens) {
|
|
7
|
+
return (tokens || []).map((token) => {
|
|
8
|
+
const colon = token.indexOf(':')
|
|
9
|
+
const value = colon === -1 ? token : token.slice(0, colon)
|
|
10
|
+
const iconName = colon === -1 ? token : token.slice(colon + 1)
|
|
11
|
+
const icon = VARIANT_ICONS[iconName] || null
|
|
12
|
+
return { value, icon, label: icon ? icon.label : humanize(value) }
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Build one native radio per option from the template's prototype row. The
|
|
17
|
+
// current value is prepended when it is not offered, so opening the editor
|
|
18
|
+
// never silently changes the page.
|
|
19
|
+
export function populateVariant(node, { tokens, value, name, doc }) {
|
|
20
|
+
const options = parseVariantOptions(tokens)
|
|
21
|
+
const current = value == null ? '' : String(value)
|
|
22
|
+
if (current !== '' && !options.some((o) => o.value === current)) {
|
|
23
|
+
options.unshift({ value: current, icon: null, label: humanize(current) })
|
|
24
|
+
}
|
|
25
|
+
const proto = node.querySelector('.hcms-variant-option')
|
|
26
|
+
if (!proto || !proto.parentNode) return options.length
|
|
27
|
+
for (const o of options) {
|
|
28
|
+
const row = proto.cloneNode(true)
|
|
29
|
+
const input = row.querySelector('input[type="radio"]')
|
|
30
|
+
if (input) {
|
|
31
|
+
input.value = o.value
|
|
32
|
+
input.name = name
|
|
33
|
+
}
|
|
34
|
+
const iconSlot = row.querySelector('.hcms-variant-icon')
|
|
35
|
+
if (iconSlot) {
|
|
36
|
+
if (o.icon) iconSlot.innerHTML = o.icon.svg
|
|
37
|
+
else iconSlot.remove()
|
|
38
|
+
}
|
|
39
|
+
const label = row.querySelector('.hcms-variant-label')
|
|
40
|
+
if (label) label.textContent = o.label
|
|
41
|
+
row.setAttribute('title', o.label)
|
|
42
|
+
proto.parentNode.insertBefore(row, proto)
|
|
43
|
+
}
|
|
44
|
+
proto.remove()
|
|
45
|
+
return options.length
|
|
46
|
+
}
|