@panphora/hyper-cms 0.6.0 → 0.6.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/README.md +19 -5
- package/dist/hypercms.min.js +467 -74
- package/package.json +7 -3
- package/src/enhance.js +97 -0
- package/src/events.js +4 -1
- package/src/form-rules.js +2 -0
- package/src/hypercms.js +22 -1
- package/src/refresh.js +8 -1
- package/src/templates.js +17 -2
- package/src/theme/pixel-quiet.overrides.css +26 -1
- package/src/theme.generated.css +434 -61
- package/src/toggle.js +88 -0
- package/src/vendor/mirk.vendor.js +15 -0
package/src/theme.generated.css
CHANGED
|
@@ -35,10 +35,84 @@
|
|
|
35
35
|
/* Preflight sets these to ui-monospace; keep them in Departure Mono. */
|
|
36
36
|
.hcms-shell pre, .hcms-shell code, .hcms-shell kbd, .hcms-shell samp { font-family: inherit; }
|
|
37
37
|
|
|
38
|
-
/* 28 tokens, one value each. light-dark() picks the side from color-scheme.
|
|
38
|
+
/* 28 tokens, one value each. light-dark() picks the side from color-scheme.
|
|
39
|
+
:root paints the DEFAULT theme — "Pixel Quiet" (see 0030): mirk's warm soul
|
|
40
|
+
with the volume down. The louder original palette is the opt-in
|
|
41
|
+
[data-theme="full-volume"] block below. */
|
|
39
42
|
.hcms-shell {
|
|
40
43
|
color-scheme: light dark; /* default: follow the OS */
|
|
41
44
|
|
|
45
|
+
--mirk-canvas: light-dark(#FDF8F0, #0B0C13);
|
|
46
|
+
--mirk-bg: light-dark(#FDF8F0, #11131E);
|
|
47
|
+
--mirk-fg: light-dark(#2B241B, #ECEAF2);
|
|
48
|
+
--mirk-accent: light-dark(#efefe5, #1D1F2F);
|
|
49
|
+
--mirk-destructive: light-dark(#C24A3A, #ff5566);
|
|
50
|
+
--mirk-focus-color: light-dark(#C7AE93, #4A506B);
|
|
51
|
+
--mirk-bevel-bg: light-dark(#FCF8F1, #1A1D2C);
|
|
52
|
+
--mirk-bevel-fg: light-dark(#2B241B, #ECEAF2);
|
|
53
|
+
--mirk-bevel-tl: light-dark(#F0E7D8, #2A2E42);
|
|
54
|
+
--mirk-bevel-br: light-dark(#E2D4BF, #14182A);
|
|
55
|
+
--mirk-bevel-hover-bg: light-dark(#F4ECDF, #202436);
|
|
56
|
+
--mirk-pill-inner-top: light-dark(#FBF6EE, #202436);
|
|
57
|
+
--mirk-input-border: light-dark(#D8C8AF, #353B52);
|
|
58
|
+
--mirk-placeholder-color: light-dark(#A8987F, #6A7090);
|
|
59
|
+
--mirk-ctrl-bg: light-dark(#8C7660, #5F6582);
|
|
60
|
+
--mirk-toggle-bg: light-dark(#EFDBBD, #3E4660);
|
|
61
|
+
--mirk-toggle-hi: light-dark(#F4EADA, #4E567A);
|
|
62
|
+
--mirk-toggle-lo: light-dark(#C2A87E, #262B42);
|
|
63
|
+
--mirk-mark-fg: light-dark(#6B5942, #C9CDE0);
|
|
64
|
+
--mirk-sortable-dot: light-dark(#DDCBB0, #353B52);
|
|
65
|
+
--mirk-sortable-shadow: light-dark(#C9B493, #0E1120);
|
|
66
|
+
--mirk-sortable-label: light-dark(#8C7B62, #8A90AB);
|
|
67
|
+
--mirk-sortable-placeholder: light-dark(#A8987F, #6A7090);
|
|
68
|
+
--mirk-slider-fill: light-dark(#F2E0BD, #2A2E42);
|
|
69
|
+
--mirk-slider-nub-bg: light-dark(#EFDBBD, #3E4660);
|
|
70
|
+
--mirk-slider-nub-hi: light-dark(#F4EADA, #4E567A);
|
|
71
|
+
--mirk-slider-nub-lo: light-dark(#C2A87E, #262B42);
|
|
72
|
+
|
|
73
|
+
/* Chip — the recovery/notification component reads from the kit's own tokens
|
|
74
|
+
via 5 slim hooks, so it matches the kit by default and reskins by overriding
|
|
75
|
+
a hook (not a rule). Each follows the theme (incl. the Full Volume variant)
|
|
76
|
+
and OS light/dark with no per-theme repaint; the one exception is the primary
|
|
77
|
+
fill, which the default (Pixel Quiet) leaves as the theme's fg ink while the
|
|
78
|
+
Full Volume variant tints it a warm brown in light (see below). */
|
|
79
|
+
--mirk-chip-surface: var(--mirk-bg); /* raised panel face */
|
|
80
|
+
--mirk-chip-edge: var(--mirk-input-border); /* panel + recess outline */
|
|
81
|
+
--mirk-chip-primary-bg: var(--mirk-fg); /* primary action fill — the theme's fg ink (Full Volume tints it warm brown in light) */
|
|
82
|
+
--mirk-chip-primary-fg: var(--mirk-bg);
|
|
83
|
+
--mirk-chip-alert: var(--mirk-destructive); /* icon + struck "now" value */
|
|
84
|
+
|
|
85
|
+
--mirk-radius: 5px; /* the "rounded" corner */
|
|
86
|
+
--mirk-focus-offset: 2px; /* non-color → can't ride light-dark() */
|
|
87
|
+
|
|
88
|
+
background: var(--mirk-canvas);
|
|
89
|
+
color: var(--mirk-fg);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* The one non-color token with a real light/dark split (was 2px / 3px). */
|
|
93
|
+
@media (prefers-color-scheme: dark) { .hcms-shell { --mirk-focus-offset: 3px; } }
|
|
94
|
+
|
|
95
|
+
/* Force a mode on any subtree with one attribute (class aliases for hosts that
|
|
96
|
+
prefer class-based theming and Tailwind's dark-variant convention). Each also
|
|
97
|
+
paints its own canvas so a wrapper visibly flips. */
|
|
98
|
+
.hcms-shell[data-theme="light"], .hcms-shell.light {
|
|
99
|
+
color-scheme: light; --mirk-focus-offset: 2px;
|
|
100
|
+
background: var(--mirk-canvas); color: var(--mirk-fg);
|
|
101
|
+
}
|
|
102
|
+
.hcms-shell[data-theme="dark"], .hcms-shell.dark {
|
|
103
|
+
color-scheme: dark; --mirk-focus-offset: 3px;
|
|
104
|
+
background: var(--mirk-canvas); color: var(--mirk-fg);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Built-in brand variant — "Full Volume": mirk's original full-strength palette,
|
|
108
|
+
the loud pole of the volume axis (Pixel Quiet, now the default, is the quiet
|
|
109
|
+
end). Full-contrast bevel, warm cream / deep navy, crimson destructive.
|
|
110
|
+
Authored with light-dark() like :root, so it follows the OS and still flips
|
|
111
|
+
with .dark / .light. Opt in: data-theme="full-volume". Sits after :root
|
|
112
|
+
(equal specificity, source order wins). --mirk-radius / --mirk-focus-offset /
|
|
113
|
+
--mirk-ctrl-bg and the four shared --mirk-chip-* hooks inherit from :root
|
|
114
|
+
unchanged; only the tokens that differ from the default are re-declared here. */
|
|
115
|
+
.hcms-shell[data-theme="full-volume"] {
|
|
42
116
|
--mirk-canvas: light-dark(#F7F2EA, #0B0C13);
|
|
43
117
|
--mirk-bg: light-dark(#F7F2EA, #1D1F2F);
|
|
44
118
|
--mirk-fg: light-dark(#15120e, #F6F7F9);
|
|
@@ -53,7 +127,6 @@
|
|
|
53
127
|
--mirk-pill-inner-top: light-dark(#efdac7, #232639);
|
|
54
128
|
--mirk-input-border: light-dark(#957E65, #6E738E);
|
|
55
129
|
--mirk-placeholder-color: light-dark(#7F7366, #545973);
|
|
56
|
-
--mirk-ctrl-bg: light-dark(#8C7660, #5F6582);
|
|
57
130
|
--mirk-toggle-bg: light-dark(#DFC9AF, #656D95);
|
|
58
131
|
--mirk-toggle-hi: light-dark(#E9D6C3, #7F87AD);
|
|
59
132
|
--mirk-toggle-lo: light-dark(#C7A88A, #505677);
|
|
@@ -66,64 +139,9 @@
|
|
|
66
139
|
--mirk-slider-nub-bg: light-dark(#DFC9AF, #656D95);
|
|
67
140
|
--mirk-slider-nub-hi: light-dark(#E9D6C3, #7F87AD);
|
|
68
141
|
--mirk-slider-nub-lo: light-dark(#C7A88A, #505677);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
--mirk-
|
|
72
|
-
|
|
73
|
-
background: var(--mirk-canvas);
|
|
74
|
-
color: var(--mirk-fg);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/* The one non-color token with a real light/dark split (was 2px / 3px). */
|
|
78
|
-
@media (prefers-color-scheme: dark) { .hcms-shell { --mirk-focus-offset: 3px; } }
|
|
79
|
-
|
|
80
|
-
/* Force a mode on any subtree with one attribute (class aliases for hosts that
|
|
81
|
-
prefer class-based theming and Tailwind's dark-variant convention). Each also
|
|
82
|
-
paints its own canvas so a wrapper visibly flips. */
|
|
83
|
-
.hcms-shell[data-theme="light"], .hcms-shell.light {
|
|
84
|
-
color-scheme: light; --mirk-focus-offset: 2px;
|
|
85
|
-
background: var(--mirk-canvas); color: var(--mirk-fg);
|
|
86
|
-
}
|
|
87
|
-
.hcms-shell[data-theme="dark"], .hcms-shell.dark {
|
|
88
|
-
color-scheme: dark; --mirk-focus-offset: 3px;
|
|
89
|
-
background: var(--mirk-canvas); color: var(--mirk-fg);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Built-in brand variant — "Pixel Quiet": mirk's warm soul with the volume
|
|
93
|
-
down. A softened, near-equal bevel, warmer ink, terracotta destructive, a
|
|
94
|
-
deeper navy-black dark. Authored with light-dark() like :root, so it follows
|
|
95
|
-
the OS and still flips with .dark / .light. Opt in: data-theme="pixel-quiet".
|
|
96
|
-
Sits after :root (equal specificity, source order wins). --mirk-radius /
|
|
97
|
-
--mirk-focus-offset are inherited unchanged; --mirk-ctrl-bg is unused. */
|
|
98
|
-
.hcms-shell[data-theme="pixel-quiet"] {
|
|
99
|
-
--mirk-fg: light-dark(#2B241B, #ECEAF2);
|
|
100
|
-
--mirk-bg: light-dark(#F7F2EA, #11131E);
|
|
101
|
-
--mirk-canvas: light-dark(#F7F2EA, #0B0C13);
|
|
102
|
-
--mirk-accent: light-dark(#efefe5, #1D1F2F);
|
|
103
|
-
--mirk-destructive: light-dark(#C24A3A, #ff5566);
|
|
104
|
-
--mirk-focus-color: light-dark(#C7AE93, #4A506B);
|
|
105
|
-
--mirk-bevel-bg: light-dark(#FCF8F1, #1A1D2C);
|
|
106
|
-
--mirk-bevel-fg: light-dark(#2B241B, #ECEAF2);
|
|
107
|
-
--mirk-bevel-tl: light-dark(#F0E7D8, #2A2E42);
|
|
108
|
-
--mirk-bevel-br: light-dark(#E2D4BF, #14182A);
|
|
109
|
-
--mirk-bevel-hover-bg: light-dark(#F4ECDF, #202436);
|
|
110
|
-
--mirk-pill-inner-top: light-dark(#FBF6EE, #202436);
|
|
111
|
-
--mirk-input-border: light-dark(#D8C8AF, #353B52);
|
|
112
|
-
--mirk-placeholder-color: light-dark(#A8987F, #6A7090);
|
|
113
|
-
--mirk-mark-fg: light-dark(#6B5942, #C9CDE0);
|
|
114
|
-
--mirk-toggle-bg: light-dark(#EFDBBD, #3E4660);
|
|
115
|
-
--mirk-toggle-hi: light-dark(#F4EADA, #4E567A);
|
|
116
|
-
--mirk-toggle-lo: light-dark(#C2A87E, #262B42);
|
|
117
|
-
--mirk-sortable-dot: light-dark(#DDCBB0, #353B52);
|
|
118
|
-
--mirk-sortable-shadow: light-dark(#C9B493, #0E1120);
|
|
119
|
-
--mirk-sortable-label: light-dark(#8C7B62, #8A90AB);
|
|
120
|
-
--mirk-sortable-placeholder: light-dark(#A8987F, #6A7090);
|
|
121
|
-
/* slider nub reuses the toggle family (identical pairing in :root); the fill
|
|
122
|
-
is the one value with no CMS precedent — a soft warm / muted navy track. */
|
|
123
|
-
--mirk-slider-fill: light-dark(#F2E0BD, #2A2E42);
|
|
124
|
-
--mirk-slider-nub-bg: light-dark(#EFDBBD, #3E4660);
|
|
125
|
-
--mirk-slider-nub-hi: light-dark(#F4EADA, #4E567A);
|
|
126
|
-
--mirk-slider-nub-lo: light-dark(#C2A87E, #262B42);
|
|
142
|
+
/* :root (Pixel Quiet) leaves the chip primary as its own fg ink; the original
|
|
143
|
+
default tinted it a warm brown in light — restore that here. */
|
|
144
|
+
--mirk-chip-primary-bg: light-dark(#1C170E, var(--mirk-fg));
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
/* Roll your own the same way — an explicit [data-theme] block is the escape hatch:
|
|
@@ -194,6 +212,15 @@
|
|
|
194
212
|
.hcms-shell .mirk-button--round.mirk-button--large { border-radius: 16px; }
|
|
195
213
|
.hcms-shell .mirk-button--round.mirk-button--large .mirk-button__label { padding: 7px 24px 9px; border-radius: 14px; }
|
|
196
214
|
|
|
215
|
+
/* Quiet: a borderless text button (transparent border keeps the hit area + the
|
|
216
|
+
baseline aligned with neighbouring bevel buttons). For tertiary actions. */
|
|
217
|
+
.hcms-shell .mirk-button--quiet {
|
|
218
|
+
background: none; background-image: none;
|
|
219
|
+
border-color: transparent; color: var(--mirk-placeholder-color);
|
|
220
|
+
}
|
|
221
|
+
.hcms-shell .mirk-button--quiet:hover { background: none; color: var(--mirk-fg); }
|
|
222
|
+
.hcms-shell .mirk-button--quiet:active { border-color: transparent; }
|
|
223
|
+
|
|
197
224
|
/* ============================ TEXT INPUT ============================ */
|
|
198
225
|
.hcms-shell .mirk-input {
|
|
199
226
|
width: 100%;
|
|
@@ -371,6 +398,7 @@
|
|
|
371
398
|
.hcms-shell .mirk-toggle { display: inline-flex; align-items: center; gap: 0.75rem; cursor: pointer; width: fit-content; }
|
|
372
399
|
.hcms-shell .mirk-toggle__track {
|
|
373
400
|
position: relative; flex-shrink: 0; width: 49px; height: 27px; border-radius: 0;
|
|
401
|
+
background: var(--mirk-canvas); /* own recessed channel, like the slider track — never the host page (0033) */
|
|
374
402
|
border: 1px solid var(--mirk-input-border);
|
|
375
403
|
}
|
|
376
404
|
.hcms-shell .mirk-toggle__thumb {
|
|
@@ -649,6 +677,326 @@
|
|
|
649
677
|
.hcms-shell .mirk-sortable--small .mirk-sortable__row { padding: 5px 13px 6px; }
|
|
650
678
|
.hcms-shell .mirk-sortable--small .mirk-sortable__label { font-size: 10px; margin-bottom: 1px; }
|
|
651
679
|
.hcms-shell .mirk-sortable--small .mirk-sortable__field { font-size: 14px; }
|
|
680
|
+
|
|
681
|
+
/* ============================ CHIP ============================ */
|
|
682
|
+
/* A collapsible recovery/notification: a round pill that expands into a RAISED
|
|
683
|
+
panel — the kit's one elevated surface (a distinct --mirk-bg face, a hairline
|
|
684
|
+
outline, and a soft drop shadow, the only shadow in the kit, reserved for this
|
|
685
|
+
raised tier). State lives in classes (--open, is-changes) so it round-trips via
|
|
686
|
+
outerHTML; mirk.js only flips them on click. Color reads from generic kit
|
|
687
|
+
tokens through 5 slim --mirk-chip-* hooks, so it matches the kit and reskins by
|
|
688
|
+
overriding a hook, not a rule. In a Hyperclay app, add `save-remove` to the
|
|
689
|
+
block so a transient prompt never persists into the saved file. */
|
|
690
|
+
.hcms-shell .mirk-chip { display: inline-flex; flex-direction: column; align-items: flex-start; }
|
|
691
|
+
.hcms-shell .mirk-chip__panel { display: none; }
|
|
692
|
+
.hcms-shell .mirk-chip--open .mirk-chip__trigger { display: none; }
|
|
693
|
+
.hcms-shell .mirk-chip--open .mirk-chip__panel { display: flex; }
|
|
694
|
+
|
|
695
|
+
/* Collapsed chip — a round mirk-button pill (mirk-button--round in the markup),
|
|
696
|
+
the alert glyph seated in the label, so it reads as a distinct affordance, not
|
|
697
|
+
a flat button. A soft, tight lift sets it above the page; deeper on a dark
|
|
698
|
+
canvas (same media + forced-mode pattern as the panel shadow below). */
|
|
699
|
+
.hcms-shell .mirk-chip__trigger .mirk-button__label { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; }
|
|
700
|
+
.hcms-shell .mirk-chip__trigger { box-shadow: 0 6px 14px -8px rgba(43, 36, 27, 0.5), 0 2px 5px -3px rgba(43, 36, 27, 0.32); }
|
|
701
|
+
@media (prefers-color-scheme: dark) { .hcms-shell .mirk-chip__trigger { box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.6), 0 2px 6px -3px rgba(0, 0, 0, 0.5); } }
|
|
702
|
+
.hcms-shell.light .mirk-chip__trigger, .hcms-shell[data-theme="light"] .mirk-chip__trigger { box-shadow: 0 6px 14px -8px rgba(43, 36, 27, 0.5), 0 2px 5px -3px rgba(43, 36, 27, 0.32); }
|
|
703
|
+
.hcms-shell.dark .mirk-chip__trigger, .hcms-shell[data-theme="dark"] .mirk-chip__trigger { box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.6), 0 2px 6px -3px rgba(0, 0, 0, 0.5); }
|
|
704
|
+
/* Drive the warning fill from CSS, not an SVG fill="var(...)" presentation
|
|
705
|
+
attribute (var() is not reliably honored there). */
|
|
706
|
+
.hcms-shell .mirk-chip__warn { fill: var(--mirk-chip-alert); }
|
|
707
|
+
.hcms-shell .mirk-chip__trigger .mirk-chip__warn { vertical-align: -2px; }
|
|
708
|
+
|
|
709
|
+
/* The panel — the raised surface: a --mirk-bg face over the page, a hairline
|
|
710
|
+
outline, a soft drop shadow. */
|
|
711
|
+
.hcms-shell .mirk-chip__panel {
|
|
712
|
+
width: 300px; max-width: calc(100vw - 44px);
|
|
713
|
+
background: var(--mirk-chip-surface); color: var(--mirk-fg);
|
|
714
|
+
border: 1px solid var(--mirk-chip-edge); border-radius: var(--mirk-radius);
|
|
715
|
+
box-shadow: 0 20px 50px -30px rgba(43, 36, 27, 0.6);
|
|
716
|
+
padding: 14px 15px 13px; flex-direction: column; gap: 12px;
|
|
717
|
+
}
|
|
718
|
+
/* The panel's larger drop (the collapsed pill above carries a tighter one); deepen
|
|
719
|
+
both on a dark canvas. Mirrors the --focus-offset pattern: a media default for
|
|
720
|
+
the OS, plus explicit forced-mode overrides. */
|
|
721
|
+
@media (prefers-color-scheme: dark) { .hcms-shell .mirk-chip__panel { box-shadow: 0 20px 52px -26px rgba(0, 0, 0, 0.78); } }
|
|
722
|
+
.hcms-shell.light .mirk-chip__panel, .hcms-shell[data-theme="light"] .mirk-chip__panel { box-shadow: 0 20px 50px -30px rgba(43, 36, 27, 0.6); }
|
|
723
|
+
.hcms-shell.dark .mirk-chip__panel, .hcms-shell[data-theme="dark"] .mirk-chip__panel { box-shadow: 0 20px 52px -26px rgba(0, 0, 0, 0.78); }
|
|
724
|
+
|
|
725
|
+
/* Head — icon, text, collapse glyph. */
|
|
726
|
+
.hcms-shell .mirk-chip__head { display: flex; gap: 10px; align-items: flex-start; }
|
|
727
|
+
.hcms-shell .mirk-chip__icon { flex-shrink: 0; line-height: 0; margin-top: 1px; }
|
|
728
|
+
.hcms-shell .mirk-chip__headtext { min-width: 0; }
|
|
729
|
+
.hcms-shell .mirk-chip__eyebrow {
|
|
730
|
+
font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.2em;
|
|
731
|
+
color: var(--mirk-placeholder-color); margin-bottom: 3px;
|
|
732
|
+
}
|
|
733
|
+
.hcms-shell .mirk-chip__title { margin: 0; font-size: 13px; font-weight: 400; line-height: 1.3; color: var(--mirk-fg); }
|
|
734
|
+
.hcms-shell .mirk-chip__collapse {
|
|
735
|
+
margin-left: auto; flex-shrink: 0; width: 24px; height: 24px;
|
|
736
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
737
|
+
background: none; border: 0; cursor: pointer; padding: 0;
|
|
738
|
+
color: var(--mirk-placeholder-color);
|
|
739
|
+
}
|
|
740
|
+
.hcms-shell .mirk-chip__collapse:hover { color: var(--mirk-fg); }
|
|
741
|
+
.hcms-shell .mirk-chip__collapse svg { display: block; }
|
|
742
|
+
|
|
743
|
+
/* Meta line. */
|
|
744
|
+
.hcms-shell .mirk-chip__meta { font-size: 11px; letter-spacing: 0.04em; color: var(--mirk-placeholder-color); }
|
|
745
|
+
.hcms-shell .mirk-chip__changes-toggle {
|
|
746
|
+
background: none; border: 0; cursor: pointer; font: inherit; font-size: 11px; padding: 0 0 0 4px;
|
|
747
|
+
color: var(--mirk-placeholder-color); text-decoration: underline; text-underline-offset: 2px;
|
|
748
|
+
}
|
|
749
|
+
.hcms-shell .mirk-chip__changes-toggle:hover { color: var(--mirk-fg); }
|
|
750
|
+
|
|
751
|
+
/* Before/after field table — a recessed stack, revealed by the changes toggle. */
|
|
752
|
+
.hcms-shell .mirk-chip__preview {
|
|
753
|
+
display: none; flex-direction: column; gap: 11px;
|
|
754
|
+
background: var(--mirk-bevel-bg); border: 1px solid var(--mirk-chip-edge);
|
|
755
|
+
padding: 10px 11px;
|
|
756
|
+
}
|
|
757
|
+
.hcms-shell .mirk-chip__panel.is-changes .mirk-chip__preview { display: flex; }
|
|
758
|
+
.hcms-shell .mirk-chip__row { display: flex; flex-direction: column; gap: 2px; }
|
|
759
|
+
.hcms-shell .mirk-chip__key {
|
|
760
|
+
font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.1em;
|
|
761
|
+
color: var(--mirk-placeholder-color);
|
|
762
|
+
}
|
|
763
|
+
.hcms-shell .mirk-chip__old, .hcms-shell .mirk-chip__new { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
764
|
+
.hcms-shell .mirk-chip__old { font-size: 11px; color: var(--mirk-chip-alert); text-decoration: line-through; opacity: 0.85; }
|
|
765
|
+
.hcms-shell .mirk-chip__new { font-size: 12px; color: var(--mirk-fg); }
|
|
766
|
+
|
|
767
|
+
/* Action stack — full-width buttons by weight: an embossed primary, a plain bevel
|
|
768
|
+
revert, a quiet (--quiet) dismiss. */
|
|
769
|
+
.hcms-shell .mirk-chip__actions { display: flex; flex-direction: column; gap: 8px; margin-top: 1px; }
|
|
770
|
+
.hcms-shell .mirk-chip__actions .mirk-button { width: 100%; }
|
|
771
|
+
/* Primary action — a genuine kit bevel button whose bevel palette is derived from
|
|
772
|
+
the chip's primary fill (lighter top-left, darker bottom-right), so it embosses
|
|
773
|
+
on any color. The base .mirk-button rules then drive its border, hover, and the
|
|
774
|
+
:active flip, identical to the kit's other buttons. */
|
|
775
|
+
.hcms-shell .mirk-chip__actions .mirk-chip__action--primary {
|
|
776
|
+
--mirk-bevel-bg: var(--mirk-chip-primary-bg);
|
|
777
|
+
--mirk-bevel-fg: var(--mirk-chip-primary-fg);
|
|
778
|
+
--mirk-bevel-tl: color-mix(in srgb, var(--mirk-chip-primary-bg), white 24%);
|
|
779
|
+
--mirk-bevel-br: color-mix(in srgb, var(--mirk-chip-primary-bg), black 32%);
|
|
780
|
+
--mirk-bevel-hover-bg: color-mix(in srgb, var(--mirk-chip-primary-bg), white 10%);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/* ============================ FIELD ============================ */
|
|
784
|
+
/* Label + control + hint as one unit, so a form composes without utilities
|
|
785
|
+
(and without needing the mirk-page rhythm). Any control drops in; the gap
|
|
786
|
+
matches the page scaffold's label/hint hug. Consecutive fields space
|
|
787
|
+
themselves. */
|
|
788
|
+
.hcms-shell .mirk-field { display: flex; flex-direction: column; gap: 6px; }
|
|
789
|
+
.hcms-shell .mirk-field__label { font-size: 16px; line-height: 1.5; }
|
|
790
|
+
.hcms-shell .mirk-field + .mirk-field { margin-block-start: 18px; }
|
|
791
|
+
.hcms-shell .mirk-field--small .mirk-field__label { font-size: 14px; }
|
|
792
|
+
|
|
793
|
+
/* An alert hint inside a field stays hidden until a control in the field
|
|
794
|
+
goes :user-invalid — a zero-JS native-validation message. Show one
|
|
795
|
+
unconditionally (a server-side error) by placing it outside the field or
|
|
796
|
+
overriding display. */
|
|
797
|
+
.hcms-shell .mirk-field .mirk-hint--alert { display: none; }
|
|
798
|
+
.hcms-shell .mirk-field:has(:user-invalid) .mirk-hint--alert { display: block; }
|
|
799
|
+
|
|
800
|
+
/* ============================ INVALID ============================ */
|
|
801
|
+
/* Native constraint validation, styled. :user-invalid fires only after the
|
|
802
|
+
user interacts (unlike :invalid, which would paint required fields red on
|
|
803
|
+
load). A flat destructive border is the 0012 stateful read; the focus ring
|
|
804
|
+
stays the focus signal. Pair the message with mirk-hint--alert. */
|
|
805
|
+
.hcms-shell .mirk-input:user-invalid, .hcms-shell .mirk-textarea:user-invalid, .hcms-shell .mirk-date__field:user-invalid, .hcms-shell .mirk-select__field:user-invalid, .hcms-shell .mirk-number:has(.mirk-number__input:user-invalid) {
|
|
806
|
+
border-color: var(--mirk-destructive);
|
|
807
|
+
}
|
|
808
|
+
.hcms-shell .mirk-checkbox:has(:user-invalid) .mirk-checkbox__box, .hcms-shell .mirk-toggle:has(:user-invalid) .mirk-toggle__track {
|
|
809
|
+
border-color: var(--mirk-destructive);
|
|
810
|
+
}
|
|
811
|
+
/* The unchecked ring is a borderless gradient pill; invalid swaps it for a
|
|
812
|
+
flat destructive ring (the fill pill still seats inside). */
|
|
813
|
+
.hcms-shell .mirk-radio:has(:user-invalid) .mirk-radio__ring {
|
|
814
|
+
background-image: none;
|
|
815
|
+
border: 2px solid var(--mirk-destructive);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/* ============================ PROGRESS ============================ */
|
|
819
|
+
/* A native <progress> in the slider's clothes: the canvas channel, the
|
|
820
|
+
slider-fill value bar. --blocks segments the fill into pixel blocks.
|
|
821
|
+
Keep the -webkit and -moz rules separate — an unrecognized pseudo-element
|
|
822
|
+
invalidates the whole selector list. */
|
|
823
|
+
.hcms-shell .mirk-progress {
|
|
824
|
+
appearance: none; -webkit-appearance: none;
|
|
825
|
+
display: block; width: 100%; height: 12px;
|
|
826
|
+
border: 1px solid var(--mirk-input-border);
|
|
827
|
+
background: var(--mirk-canvas);
|
|
828
|
+
}
|
|
829
|
+
.hcms-shell .mirk-progress::-webkit-progress-bar { background: transparent; }
|
|
830
|
+
.hcms-shell .mirk-progress::-webkit-progress-value { background: var(--mirk-slider-fill); }
|
|
831
|
+
.hcms-shell .mirk-progress::-moz-progress-bar { background: var(--mirk-slider-fill); }
|
|
832
|
+
.hcms-shell .mirk-progress--small { height: 8px; }
|
|
833
|
+
.hcms-shell .mirk-progress--round { border-radius: 9999px; overflow: hidden; }
|
|
834
|
+
.hcms-shell .mirk-progress--blocks::-webkit-progress-value {
|
|
835
|
+
background: repeating-linear-gradient(to right,
|
|
836
|
+
var(--mirk-slider-fill) 0 8px, transparent 8px 11px);
|
|
837
|
+
}
|
|
838
|
+
.hcms-shell .mirk-progress--blocks::-moz-progress-bar {
|
|
839
|
+
background: repeating-linear-gradient(to right,
|
|
840
|
+
var(--mirk-slider-fill) 0 8px, transparent 8px 11px);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/* ============================ NOTE ============================ */
|
|
844
|
+
/* An informational callout. Flat on purpose: bevel means pressable in this
|
|
845
|
+
kit (0012) and a note is content, so it gets the 1px content border over
|
|
846
|
+
the recessed face, never the raised edge. The 4px left edge carries the
|
|
847
|
+
status: neutral ink by default, destructive on --alert. */
|
|
848
|
+
.hcms-shell .mirk-note {
|
|
849
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
850
|
+
background: var(--mirk-bevel-bg);
|
|
851
|
+
border: 1px solid var(--mirk-input-border);
|
|
852
|
+
border-left: 4px solid var(--mirk-mark-fg);
|
|
853
|
+
padding: 10px 14px 11px;
|
|
854
|
+
font-size: 14px; line-height: 1.5;
|
|
855
|
+
}
|
|
856
|
+
.hcms-shell .mirk-note__title {
|
|
857
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.2em;
|
|
858
|
+
color: var(--mirk-mark-fg);
|
|
859
|
+
}
|
|
860
|
+
.hcms-shell .mirk-note__body { color: var(--mirk-fg); }
|
|
861
|
+
.hcms-shell .mirk-note--alert { border-left-color: var(--mirk-destructive); }
|
|
862
|
+
.hcms-shell .mirk-note--alert .mirk-note__title { color: var(--mirk-destructive); }
|
|
863
|
+
.hcms-shell .mirk-note--rounded { border-radius: var(--mirk-radius); }
|
|
864
|
+
|
|
865
|
+
/* ============================ HINT ============================ */
|
|
866
|
+
/* Small print under a field: neutral help text, or --alert validation text. */
|
|
867
|
+
.hcms-shell .mirk-hint { margin: 0; font-size: 13px; line-height: 1.5; color: var(--mirk-placeholder-color); }
|
|
868
|
+
.hcms-shell .mirk-hint--alert { color: var(--mirk-destructive); }
|
|
869
|
+
|
|
870
|
+
/* ============================ LIST ============================ */
|
|
871
|
+
/* Content bullets. <ul> gets a square pixel dot (the sortable dot's idiom,
|
|
872
|
+
one step larger); a nested <ul> hollows it. <ol> gets zero-padded counters
|
|
873
|
+
in the muted label ink. Styles the bare <li> by descent, like the platform. */
|
|
874
|
+
.hcms-shell .mirk-list {
|
|
875
|
+
list-style: none; margin: 0; padding: 0;
|
|
876
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
877
|
+
font-size: 16px; line-height: 1.5;
|
|
878
|
+
}
|
|
879
|
+
.hcms-shell .mirk-list li { position: relative; padding-left: 22px; }
|
|
880
|
+
.hcms-shell ul.mirk-list > li::before {
|
|
881
|
+
content: ""; position: absolute; left: 2px; top: 0.55em;
|
|
882
|
+
width: 6px; height: 6px; background: var(--mirk-mark-fg);
|
|
883
|
+
box-shadow: 1px 1px 0 0 var(--mirk-sortable-shadow);
|
|
884
|
+
}
|
|
885
|
+
.hcms-shell ol.mirk-list { counter-reset: mirk-li; }
|
|
886
|
+
.hcms-shell ol.mirk-list > li { counter-increment: mirk-li; padding-left: 38px; }
|
|
887
|
+
.hcms-shell ol.mirk-list > li::before {
|
|
888
|
+
content: counter(mirk-li, decimal-leading-zero) ".";
|
|
889
|
+
position: absolute; left: 0; top: 0;
|
|
890
|
+
color: var(--mirk-sortable-label);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/* One nested level: hollow square, same stack. */
|
|
894
|
+
.hcms-shell .mirk-list ul {
|
|
895
|
+
list-style: none; margin: 6px 0 0; padding: 0;
|
|
896
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
897
|
+
}
|
|
898
|
+
.hcms-shell .mirk-list ul > li::before {
|
|
899
|
+
content: ""; position: absolute; left: 2px; top: 0.55em;
|
|
900
|
+
width: 6px; height: 6px; background: transparent;
|
|
901
|
+
border: 1.5px solid var(--mirk-mark-fg); box-shadow: none;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.hcms-shell .mirk-list--small { font-size: 14px; gap: 4px; }
|
|
905
|
+
.hcms-shell .mirk-list--small li { padding-left: 18px; }
|
|
906
|
+
.hcms-shell ul.mirk-list--small > li::before, .hcms-shell .mirk-list--small ul > li::before { width: 5px; height: 5px; }
|
|
907
|
+
.hcms-shell ol.mirk-list--small > li { padding-left: 32px; }
|
|
908
|
+
|
|
909
|
+
/* ============================ BADGE ============================ */
|
|
910
|
+
/* A static tag label — the display counterpart to the mirk-tags input. Flat
|
|
911
|
+
on purpose: bevel means pressable (0012), a badge is content. */
|
|
912
|
+
.hcms-shell .mirk-badge {
|
|
913
|
+
display: inline-flex; align-items: center; gap: 0.375rem;
|
|
914
|
+
padding: 1px 8px 2px; font-size: 12px; line-height: 1.5;
|
|
915
|
+
color: var(--mirk-fg); background: var(--mirk-bevel-bg);
|
|
916
|
+
border: 1px solid var(--mirk-input-border);
|
|
917
|
+
white-space: nowrap; vertical-align: middle;
|
|
918
|
+
}
|
|
919
|
+
.hcms-shell .mirk-badge--accent { background: var(--mirk-accent); }
|
|
920
|
+
.hcms-shell .mirk-badge--round { border-radius: 9999px; padding: 1px 10px 2px; }
|
|
921
|
+
.hcms-shell .mirk-badge--alert { color: var(--mirk-destructive); border-color: var(--mirk-destructive); }
|
|
922
|
+
|
|
923
|
+
/* ============================ TABLE ============================ */
|
|
924
|
+
/* A flat data surface: the content face in a 1px frame, header cells in the
|
|
925
|
+
eyebrow register, hairline row dividers. Semantic <table> styled by
|
|
926
|
+
descent — no per-cell classes. The header/stripe tints ride color-mix
|
|
927
|
+
toward the ink so they stay visible in every palette (Pixel Quiet's
|
|
928
|
+
canvas and face are nearly the same value). Wrap in an overflow-x:auto
|
|
929
|
+
div when the table can outgrow its column. */
|
|
930
|
+
.hcms-shell .mirk-table {
|
|
931
|
+
width: 100%; border-collapse: collapse;
|
|
932
|
+
background: var(--mirk-bevel-bg);
|
|
933
|
+
border: 1px solid var(--mirk-input-border);
|
|
934
|
+
font-size: 14px; line-height: 1.5;
|
|
935
|
+
}
|
|
936
|
+
.hcms-shell .mirk-table th {
|
|
937
|
+
text-align: left; font-weight: 400;
|
|
938
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.15em;
|
|
939
|
+
color: var(--mirk-placeholder-color);
|
|
940
|
+
background: color-mix(in srgb, var(--mirk-bevel-bg), var(--mirk-fg) 4%);
|
|
941
|
+
padding: 7px 14px;
|
|
942
|
+
border-bottom: 1px solid var(--mirk-input-border);
|
|
943
|
+
}
|
|
944
|
+
.hcms-shell .mirk-table td {
|
|
945
|
+
padding: 8px 14px;
|
|
946
|
+
border-bottom: 1px solid var(--mirk-input-border);
|
|
947
|
+
vertical-align: top;
|
|
948
|
+
}
|
|
949
|
+
.hcms-shell .mirk-table tbody tr:last-child td { border-bottom: 0; }
|
|
950
|
+
.hcms-shell .mirk-table--striped tbody tr:nth-child(even) td {
|
|
951
|
+
background: color-mix(in srgb, var(--mirk-bevel-bg), var(--mirk-fg) 3%);
|
|
952
|
+
}
|
|
953
|
+
.hcms-shell .mirk-table--small { font-size: 13px; }
|
|
954
|
+
.hcms-shell .mirk-table--small th { padding: 5px 12px; font-size: 10px; }
|
|
955
|
+
.hcms-shell .mirk-table--small td { padding: 5px 12px; }
|
|
956
|
+
|
|
957
|
+
/* ============================ PAGE ============================ */
|
|
958
|
+
/* The quickstart shell: mirk-page on <body> (or any wrapper) gives a centered
|
|
959
|
+
column + typographic defaults, so two CDN tags and one class boot a full
|
|
960
|
+
page. Every rule rides :where() (zero specificity): any utility, component
|
|
961
|
+
class, or consumer rule beats it. Flow rhythm targets direct children only,
|
|
962
|
+
so margins never leak inside component internals. */
|
|
963
|
+
.hcms-shell .mirk-page { max-width: 640px; margin-inline: auto; padding: 48px 24px 96px; }
|
|
964
|
+
.hcms-shell .mirk-page--wide { max-width: 960px; }
|
|
965
|
+
|
|
966
|
+
/* Departure Mono ships one weight — hierarchy comes from size, never bold. */
|
|
967
|
+
.hcms-shell .mirk-page :where(h1, h2, h3, h4) { margin: 0; font-weight: 400; line-height: 1.15; }
|
|
968
|
+
.hcms-shell .mirk-page :where(h1) { font-size: 40px; }
|
|
969
|
+
.hcms-shell .mirk-page :where(h2) { font-size: 26px; }
|
|
970
|
+
.hcms-shell .mirk-page :where(h3) { font-size: 20px; }
|
|
971
|
+
.hcms-shell .mirk-page :where(h4) { font-size: 16px; }
|
|
972
|
+
.hcms-shell .mirk-page :where(p) { margin: 0; font-size: 16px; line-height: 1.6; }
|
|
973
|
+
|
|
974
|
+
.hcms-shell .mirk-page :where(a) { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
|
|
975
|
+
.hcms-shell .mirk-page :where(code, kbd) {
|
|
976
|
+
font-size: 0.875em; padding: 1px 4px;
|
|
977
|
+
background: var(--mirk-bevel-bg); border: 1px solid var(--mirk-input-border);
|
|
978
|
+
}
|
|
979
|
+
.hcms-shell .mirk-page :where(pre) {
|
|
980
|
+
margin: 0; padding: 12px 14px; font-size: 13px; line-height: 1.5; overflow-x: auto;
|
|
981
|
+
background: var(--mirk-bevel-bg); border: 1px solid var(--mirk-input-border);
|
|
982
|
+
}
|
|
983
|
+
.hcms-shell .mirk-page :where(pre code) { padding: 0; border: 0; background: none; font-size: inherit; }
|
|
984
|
+
.hcms-shell .mirk-page :where(hr) { border: 0; border-top: 1px solid var(--mirk-input-border); }
|
|
985
|
+
|
|
986
|
+
/* Flow rhythm — direct children only; headings open sections, eyebrows and
|
|
987
|
+
hints hug their neighbors. Equal specificity, so source order settles ties. */
|
|
988
|
+
.hcms-shell .mirk-page > :where(* + *) { margin-block-start: 14px; }
|
|
989
|
+
.hcms-shell .mirk-page > :where(h1, h2, h3, h4):where(* + *) { margin-block-start: 40px; }
|
|
990
|
+
.hcms-shell .mirk-page > :where(.mirk-eyebrow + *), .hcms-shell .mirk-page > :where(* + .mirk-hint) { margin-block-start: 6px; }
|
|
991
|
+
|
|
992
|
+
/* ============================ EYEBROW ============================ */
|
|
993
|
+
/* The kit's signature section label, as a shippable class. Block so it works
|
|
994
|
+
the same on <p>, <label>, or <div>, and flow margins always apply. */
|
|
995
|
+
.hcms-shell .mirk-eyebrow {
|
|
996
|
+
display: block; margin: 0;
|
|
997
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.2em;
|
|
998
|
+
color: var(--mirk-placeholder-color);
|
|
999
|
+
}
|
|
652
1000
|
}
|
|
653
1001
|
|
|
654
1002
|
/* Respect the machine — outside @layer components so it always wins. */
|
|
@@ -890,7 +1238,7 @@ body.hcms-open.hcms-overlay { overflow: hidden; }
|
|
|
890
1238
|
display: flex;
|
|
891
1239
|
flex-direction: column;
|
|
892
1240
|
gap: 26px;
|
|
893
|
-
padding:
|
|
1241
|
+
padding: 12px 24px 28px;
|
|
894
1242
|
}
|
|
895
1243
|
|
|
896
1244
|
/* one labeled scalar field */
|
|
@@ -916,6 +1264,31 @@ body.hcms-open.hcms-overlay { overflow: hidden; }
|
|
|
916
1264
|
padding: 9px 14px 10px;
|
|
917
1265
|
}
|
|
918
1266
|
.hcms-shell.pixel-quiet textarea.mirk-input { min-height: 76px; resize: vertical; }
|
|
1267
|
+
|
|
1268
|
+
/* default scalar fields: one-row textareas that grow with their content.
|
|
1269
|
+
Browsers without field-sizing get a scrollHeight fallback (enhance.js). */
|
|
1270
|
+
.hcms-shell.pixel-quiet .hcms-form textarea.mirk-textarea {
|
|
1271
|
+
field-sizing: content;
|
|
1272
|
+
resize: none;
|
|
1273
|
+
overflow: hidden;
|
|
1274
|
+
min-height: 0;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/* rich-text surface (@richtext): a contenteditable styled like a textarea */
|
|
1278
|
+
.hcms-shell.pixel-quiet .hcms-richtext {
|
|
1279
|
+
min-height: 2.5em;
|
|
1280
|
+
cursor: text;
|
|
1281
|
+
overflow-wrap: break-word;
|
|
1282
|
+
}
|
|
1283
|
+
.hcms-shell.pixel-quiet .hcms-richtext a {
|
|
1284
|
+
color: inherit;
|
|
1285
|
+
text-decoration: underline;
|
|
1286
|
+
text-underline-offset: 2px;
|
|
1287
|
+
}
|
|
1288
|
+
.hcms-shell.pixel-quiet .hcms-richtext:empty::before {
|
|
1289
|
+
content: attr(data-hcms-placeholder);
|
|
1290
|
+
color: var(--mirk-placeholder-color);
|
|
1291
|
+
}
|
|
919
1292
|
.hcms-shell.pixel-quiet .mirk-select__field {
|
|
920
1293
|
font-size: 15px;
|
|
921
1294
|
padding: 8px 40px 9px 14px;
|
package/src/toggle.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Floating bottom-right button that toggles the CMS open/closed, so a page
|
|
2
|
+
// owner can reopen the CMS after closing it (the shell's X is the only other
|
|
3
|
+
// affordance, and nothing reopens it). Injected at page load only when the
|
|
4
|
+
// visitor is in edit mode AND the page actually has cms rules, so regular
|
|
5
|
+
// visitors and rules-less pages (hypercms rides in broad presets) never see it.
|
|
6
|
+
// Runtime-only chrome: no-save + snapshot-remove keep it out of every save and
|
|
7
|
+
// snapshot (a static no-save element written into the file would be stripped
|
|
8
|
+
// from disk on the first save), save-ignore keeps it out of autosave/undo,
|
|
9
|
+
// mirroring the shell's own attributes.
|
|
10
|
+
|
|
11
|
+
const TOGGLE_ID = 'hcms-toggle'
|
|
12
|
+
const STYLE_ID = 'hcms-toggle-style'
|
|
13
|
+
|
|
14
|
+
const STYLE = `
|
|
15
|
+
#hcms-toggle {
|
|
16
|
+
position: fixed; right: 16px; bottom: 16px; z-index: 2147482900;
|
|
17
|
+
display: inline-flex; align-items: center; gap: 7px; padding: 9px 14px;
|
|
18
|
+
border: 1px solid #3a3f58; background: #14161f; color: #f2f3f7;
|
|
19
|
+
font: 500 13px/1 system-ui, sans-serif; border-radius: 999px; cursor: pointer;
|
|
20
|
+
box-shadow: 0 10px 28px -12px rgba(0, 0, 0, .55);
|
|
21
|
+
}
|
|
22
|
+
#hcms-toggle:hover { background: #1d2030; }
|
|
23
|
+
#hcms-toggle .hcms-toggle__close { display: none; }
|
|
24
|
+
body.hcms-open #hcms-toggle .hcms-toggle__open { display: none; }
|
|
25
|
+
body.hcms-open #hcms-toggle .hcms-toggle__close { display: inline; }
|
|
26
|
+
body.hcms-open:not(.hcms-overlay):not(.hcms-side-left) #hcms-toggle { right: calc(380px + 16px); }
|
|
27
|
+
body.hcms-open.hcms-overlay #hcms-toggle { display: none; }
|
|
28
|
+
`
|
|
29
|
+
|
|
30
|
+
// Mirrors hyperclayjs core/isAdminOfCurrentResource.js: an explicit
|
|
31
|
+
// ?editmode=true|false param wins, then the window.__hyperclayEditMode global
|
|
32
|
+
// (standalone opt-in), then the platform's isAdminOfCurrentResource cookie.
|
|
33
|
+
// Replicated here rather than read off window.hyperclay because the edit-mode
|
|
34
|
+
// module isn't part of the cms preset, so the global isn't reliably present.
|
|
35
|
+
export function detectEditMode({ search = '', cookie = '', forced = null } = {}) {
|
|
36
|
+
const str = typeof search === 'string' ? search : ''
|
|
37
|
+
const qIndex = str.indexOf('?')
|
|
38
|
+
const query = qIndex === -1 ? str : str.slice(qIndex + 1)
|
|
39
|
+
const param = new URLSearchParams(query).get('editmode')
|
|
40
|
+
if (param) return param === 'true'
|
|
41
|
+
if (forced != null) return Boolean(forced)
|
|
42
|
+
return /(?:^|;\s*)isAdminOfCurrentResource=[^;]/.test(cookie)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function injectToggle({ open, close, isOpen }, doc = document) {
|
|
46
|
+
const existing = doc.getElementById(TOGGLE_ID)
|
|
47
|
+
if (existing) return existing
|
|
48
|
+
if (!doc.getElementById(STYLE_ID)) {
|
|
49
|
+
const style = doc.createElement('style')
|
|
50
|
+
style.id = STYLE_ID
|
|
51
|
+
style.setAttribute('snapshot-remove', '')
|
|
52
|
+
style.textContent = STYLE
|
|
53
|
+
doc.head.appendChild(style)
|
|
54
|
+
}
|
|
55
|
+
const btn = doc.createElement('button')
|
|
56
|
+
btn.type = 'button'
|
|
57
|
+
btn.id = TOGGLE_ID
|
|
58
|
+
btn.setAttribute('no-save', '')
|
|
59
|
+
btn.setAttribute('snapshot-remove', '')
|
|
60
|
+
btn.setAttribute('save-ignore', '')
|
|
61
|
+
btn.setAttribute('aria-label', 'Toggle content editor')
|
|
62
|
+
btn.innerHTML = '<span class="hcms-toggle__open">Edit content</span><span class="hcms-toggle__close">Close editor</span>'
|
|
63
|
+
btn.addEventListener('click', async () => {
|
|
64
|
+
try {
|
|
65
|
+
if (isOpen()) close()
|
|
66
|
+
else await open()
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.warn('hypercms: toggle failed to open the CMS', err)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
doc.body.appendChild(btn)
|
|
72
|
+
return btn
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Inject at page load when in edit mode and the page has cms rules. DOM-ready
|
|
76
|
+
// deferral matters when the module evaluates while the document is still
|
|
77
|
+
// parsing (the rules tag or <body> may not exist yet).
|
|
78
|
+
export function maybeInjectToggle(api) {
|
|
79
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return
|
|
80
|
+
const forced = window.__hyperclayEditMode != null ? window.__hyperclayEditMode : null
|
|
81
|
+
if (!detectEditMode({ search: window.location.search, cookie: document.cookie, forced })) return
|
|
82
|
+
const run = () => { if (document.body && api.hasRules(document)) injectToggle(api) }
|
|
83
|
+
if (document.readyState === 'loading') {
|
|
84
|
+
document.addEventListener('DOMContentLoaded', run, { once: true })
|
|
85
|
+
} else {
|
|
86
|
+
run()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -162,6 +162,21 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
|
162
162
|
if (tags && e.target === tags) tags.querySelector(".mirk-tags__input")?.focus();
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
+
// Collapsible chip — open the panel, collapse back to the chip, toggle the
|
|
166
|
+
// before/after field table. State lives in classes on the markup, so a
|
|
167
|
+
// saved-and-reopened chip keeps whatever it was; this only flips them live.
|
|
168
|
+
document.addEventListener("click", (e) => {
|
|
169
|
+
const t = e.target.closest("[data-mirk-chip]");
|
|
170
|
+
if (!t) return;
|
|
171
|
+
const action = t.getAttribute("data-mirk-chip");
|
|
172
|
+
if (action === "open") t.closest(".mirk-chip")?.classList.add("mirk-chip--open");
|
|
173
|
+
else if (action === "collapse") t.closest(".mirk-chip")?.classList.remove("mirk-chip--open");
|
|
174
|
+
else if (action === "changes") {
|
|
175
|
+
const on = t.closest(".mirk-chip__panel")?.classList.toggle("is-changes");
|
|
176
|
+
t.textContent = on ? "(hide changes)" : "(view changes)";
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
165
180
|
// Copy button — delegated so dynamically-added buttons work too.
|
|
166
181
|
// [data-copy] copies the component's innerHTML (clean markup).
|
|
167
182
|
// [data-copy="text"] copies textContent (for <pre>/<code> source blocks
|