mockpit 1.2.0 → 2.0.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 +69 -48
- package/bin/demoData.js +3 -3
- package/bin/mockpit.js +426 -51
- package/dist/mcp/server.js +45 -7
- package/dist/server/apiViews.d.ts +407 -21
- package/dist/server/apiViews.js +110 -11
- package/dist/server/app.d.ts +5 -1
- package/dist/server/app.js +1454 -192
- package/dist/server/codeWorker.js +2 -1
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +100 -0
- package/dist/server/designGuide.d.ts +6 -2
- package/dist/server/designGuide.js +96 -27
- package/dist/server/embeds.d.ts +26 -0
- package/dist/server/embeds.js +144 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +15 -4
- package/dist/server/feedbackBatch.js +31 -7
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/guidance.d.ts +23 -0
- package/dist/server/guidance.js +71 -0
- package/dist/server/index.js +25 -2
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +11 -1
- package/dist/server/mcpHttp.d.ts +6 -0
- package/dist/server/mcpHttp.js +35 -0
- package/dist/server/mcpSpec.d.ts +14 -13
- package/dist/server/mcpSpec.js +133 -18
- package/dist/server/postSurfaces.js +6 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +10 -1
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +18 -13
- package/dist/server/sqlStore.d.ts +97 -5
- package/dist/server/sqlStore.js +367 -27
- package/dist/server/surfacePage.d.ts +6 -2
- package/dist/server/surfacePage.js +142 -10
- package/dist/server/themes.d.ts +2 -0
- package/dist/server/themes.js +23 -3
- package/dist/server/types.d.ts +107 -16
- package/dist/server/types.js +73 -10
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/extensions/mockpit.js +179 -16
- package/guide/packs/design-craft.md +35 -0
- package/guide/topics/asks.md +82 -31
- package/guide/topics/feedback.md +151 -43
- package/guide/topics/html.md +8 -10
- package/guide/topics/http.md +8 -4
- package/guide/topics/knobs.md +4 -3
- package/guide/topics/scripts.md +2 -2
- package/guide/topics/surfaces.md +45 -11
- package/package.json +3 -15
- package/skills/launch-video/SKILL.md +2 -2
- package/skills/mockpit/SKILL.md +23 -7
- package/viewer/dist/index.html +9 -8
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
|
@@ -3,8 +3,8 @@ import type { DesignSettings } from "./types.ts";
|
|
|
3
3
|
import { type Mode, type Theme } from "./themes.ts";
|
|
4
4
|
export declare const BRIDGE_JS = "\nwindow.sendPrompt = function (text) {\n parent.postMessage({ __mockpit: true, type: 'send-prompt', text: String(text) }, '*');\n};\nwindow.openLink = function (url) {\n parent.postMessage({ __mockpit: true, type: 'open-link', url: String(url) }, '*');\n};\nwindow.copyToClipboard = function (text) {\n parent.postMessage({ __mockpit: true, type: 'copy', text: String(text) }, '*');\n};\ndocument.addEventListener('click', function (e) {\n var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;\n if (a && /^https?:/.test(a.href)) { e.preventDefault(); window.openLink(a.href); }\n});\n// Cmd+Option+Up/Down switches sessions in the sidebar, but keydowns fire in\n// whichever document holds focus \u2014 once the user clicks into a surface, this\n// sandboxed iframe swallows them. Forward just that combo to the host.\ndocument.addEventListener('keydown', function (e) {\n if (!e.metaKey || !e.altKey || e.ctrlKey || e.shiftKey) return;\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n parent.postMessage({ __mockpit: true, type: 'switch-session', key: e.key }, '*');\n});\n// Report content height to the parent so it can size this iframe, while\n// breaking a feedback loop that can peg a CPU core.\n//\n// The loop: the parent sets the iframe's height to whatever we report, but some\n// content's height *inverts* with the frame's height \u2014 a scrollbar that appears\n// at height A reflows the content to height B, then disappears at B and reflows\n// back to A (or any 100vh / percentage-derived layout). The ResizeObserver then\n// fires on every flip, so reported heights alternate A, B, A, B... forever. With\n// a cheap surface that's a brief blip; with a heavy one (a big syntax-highlighted\n// diff/markdown surface) each relayout is expensive and the tab sits at 100% CPU\n// until the surface unmounts.\n//\n// A plain h !== __lastH guard can't stop this: in a 2-cycle every value differs\n// from the one immediately before it. So we remember the previous height too and\n// defer a return to it *if it recurs faster than a human could* (< 250ms) \u2014 that's\n// the runaway. The deferred pass keeps one trailing re-measure and reports the\n// taller height in the pair, so an ordinary font/image reflow can't leave the\n// frame permanently clipped.\nvar __lastH = 0;\nvar __prevH = 0;\nvar __lastT = 0;\nvar __seenH = 0;\nvar __trailTimer = 0;\nvar __trailH = 0;\nvar __FLIP_MS = 250;\nvar __TRAIL_MS = 350;\nfunction __now() {\n return typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();\n}\nfunction __measureHeight() {\n return document.body\n ? document.body.scrollHeight\n : document.documentElement.scrollHeight;\n}\nfunction __postHeight(h, t) {\n __prevH = __lastH;\n __lastH = h;\n __lastT = t;\n parent.postMessage({ __mockpit: true, type: 'resize', height: h }, '*');\n}\nfunction __clearTrailing() {\n if (__trailTimer && typeof clearTimeout !== 'undefined') clearTimeout(__trailTimer);\n __trailTimer = 0;\n __trailH = 0;\n}\nfunction __flushTrailing() {\n __trailTimer = 0;\n var measured = __measureHeight();\n if (measured > 0) __seenH = measured;\n var target = Math.max(__trailH || 0, measured || 0);\n __trailH = 0;\n if (target <= 0 || target === __lastH) return;\n __postHeight(target, __now());\n}\nfunction __scheduleTrailing(h, reset) {\n __trailH = Math.max(__trailH || 0, h || 0, __lastH || 0);\n if (__trailTimer) {\n if (!reset) return;\n if (typeof clearTimeout !== 'undefined') clearTimeout(__trailTimer);\n }\n __trailTimer = setTimeout(__flushTrailing, __TRAIL_MS);\n}\nfunction __report() {\n var h = __measureHeight();\n if (h <= 0) return; // no content yet\n var changed = h !== __seenH;\n __seenH = h;\n if (h === __lastH) return; // unchanged\n var t = __now();\n if (h === __prevH && (__trailTimer || t - __lastT < __FLIP_MS)) {\n __scheduleTrailing(h, true); // rapid A<->B flip: defer one settled report\n return;\n }\n __clearTrailing();\n __postHeight(h, t);\n}\nif (document.readyState === 'complete') __report();\nelse window.addEventListener('load', function () { requestAnimationFrame(__report); });\nsetTimeout(__report, 60);\nsetTimeout(__report, 350);\nsetTimeout(__report, 1500);\nsetTimeout(__report, 3000);\nsetTimeout(__report, 6000);\nsetTimeout(__report, 10000);\nif (document.fonts && document.fonts.ready && document.fonts.ready.then) {\n document.fonts.ready.then(function () { __report(); });\n}\nif (window.ResizeObserver) {\n window.__ssRO = new ResizeObserver(__report);\n window.__ssRO.observe(document.documentElement);\n if (document.body) window.__ssRO.observe(document.body);\n}\n";
|
|
5
5
|
export declare const HIT_TEST_JS = "\n(function () {\n var MAX_DEPTH = 6;\n var MAX_TEXT = 60;\n function docBox() {\n var w = document.documentElement.clientWidth || 1;\n var h = (document.body && document.body.scrollHeight) || document.documentElement.scrollHeight || 1;\n return { w: w, h: h };\n }\n function ident(v) { return typeof v === 'string' && /^[A-Za-z][\\w-]*$/.test(v); }\n function cssPath(el) {\n var parts = [];\n while (el && el.nodeType === 1 && el !== document.body && el !== document.documentElement) {\n var tag = el.tagName.toLowerCase();\n if (ident(el.id)) { parts.unshift(tag + '#' + el.id); break; }\n var sel = tag;\n var cls = (el.getAttribute('class') || '').split(/\\s+/).filter(ident).slice(0, 2);\n if (cls.length) sel += '.' + cls.join('.');\n var p = el.parentElement;\n if (p) {\n var sibs = [];\n for (var i = 0; i < p.children.length; i++) {\n if (p.children[i].tagName === el.tagName) sibs.push(p.children[i]);\n }\n if (sibs.length > 1) sel += ':nth-of-type(' + (sibs.indexOf(el) + 1) + ')';\n }\n parts.unshift(sel);\n if (parts.length >= MAX_DEPTH) break;\n el = p;\n }\n return parts.join(' > ');\n }\n function firstLine(el) {\n var t = (el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim();\n var line = t.split('\\n')[0].trim();\n return line.length > MAX_TEXT ? line.slice(0, MAX_TEXT - 1) + '\\u2026' : line;\n }\n window.addEventListener('message', function (e) {\n if (e.source !== parent || parent === window) return;\n var d = e.data;\n if (!d || d.__mockpit !== true || d.type !== 'hit-test') return;\n var box = docBox();\n var px = Math.max(0, Math.min(1, Number(d.x) || 0)) * box.w;\n var py = Math.max(0, Math.min(1, Number(d.y) || 0)) * box.h;\n var el = document.elementFromPoint(px - (window.scrollX || 0), py - (window.scrollY || 0));\n var reply = { __mockpit: true, type: 'hit-test-result', ref: d.ref, path: '', text: '', rect: [0, 0, 0, 0] };\n if (el && el.nodeType === 1) {\n var r = el.getBoundingClientRect();\n reply.path = cssPath(el);\n reply.text = firstLine(el);\n reply.rect = [\n (r.left + (window.scrollX || 0)) / box.w,\n (r.top + (window.scrollY || 0)) / box.h,\n r.width / box.w,\n r.height / box.h,\n ];\n }\n parent.postMessage(reply, '*');\n });\n})();\n";
|
|
6
|
-
export declare const PARTS_JS = "\n(function () {\n if (window.parent === window) return;\n var HL = 'mockpit-part-hl';\n var MAX_PARTS = 200;\n var MAX_TEXT = 200;\n var doc = window.__mockpitDoc || {};\n var version = typeof doc.version === 'number' ? doc.version : null;\n function text(v) { return v == null ? null : String(v).slice(0, MAX_TEXT); }\n function isFixed(el) {\n for (var n = el; n && n.nodeType === 1; n = n.parentElement) {\n if (getComputedStyle(n).position === 'fixed') return true;\n }\n return false;\n }\n function collect() {\n var sx = window.scrollX || 0, sy = window.scrollY || 0;\n var els = document.querySelectorAll('[data-part]');\n var parts = [];\n for (var i = 0; i < els.length && parts.length < MAX_PARTS; i++) {\n var el = els[i];\n var name = text(el.getAttribute('data-part'));\n if (!name) continue;\n var r = el.getBoundingClientRect();\n var depth = 0;\n for (var p = el.parentElement; p; p = p.parentElement) if (p.hasAttribute('data-part')) depth++;\n var outer = el.parentElement && el.parentElement.closest('[data-part]');\n parts.push({\n name: name,\n label: text(el.getAttribute('data-part-label')) || name,\n key: text(el.getAttribute('data-part-key')),\n box: { x: r.left + sx, y: r.top + sy, w: r.width, h: r.height },\n visible: (r.width > 0 || r.height > 0) && getComputedStyle(el).visibility !== 'hidden',\n fixed: isFixed(el),\n depth: depth,\n parent: outer ? text(outer.getAttribute('data-part')) : null,\n order: i,\n });\n }\n return {\n version: version,\n parts: parts,\n height: document.body ? document.body.scrollHeight : document.documentElement.scrollHeight,\n scroll: { x: sx, y: sy },\n viewport: { w: innerWidth, h: innerHeight },\n };\n }\n var last = '';\n var timer = 0;\n // setTimeout, not rAF: a hidden cross-origin frame may have rendering (and\n // with it rAF and ResizeObserver) throttled, but timers still run.\n function schedule() {\n if (timer) return;\n timer = setTimeout(function () {\n timer = 0;\n var m = collect();\n var key = JSON.stringify(m);\n if (key === last) return;\n last = key;\n m.__mockpit = true;\n m.type = 'parts';\n parent.postMessage(m, '*');\n }, 0);\n }\n\n var ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(schedule) : null;\n var observed = typeof WeakSet !== 'undefined' ? new WeakSet() : null;\n function observeAll() {\n if (!ro || !observed) return;\n // html/body too: a non-part sibling growing moves parts without resizing them.\n var els = [document.documentElement, document.body].concat([].slice.call(document.querySelectorAll('[data-part]')));\n for (var i = 0; i < els.length; i++) {\n var el = els[i];\n if (el && !observed.has(el)) { observed.add(el); ro.observe(el); }\n }\n }\n var moTimer = 0;\n var mo = new MutationObserver(function () {\n clearTimeout(moTimer);\n moTimer = setTimeout(function () { observeAll(); schedule(); }, 40);\n });\n mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });\n\n // Transforms and transitions move boxes without resizing or mutating anything.\n document.addEventListener('animationend', schedule, true);\n document.addEventListener('transitionend', schedule, true);\n window.addEventListener('scroll', schedule, { passive: true });\n window.addEventListener('resize', schedule);\n if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);\n observeAll();\n schedule();\n // WebKit: RO's initial callback may never fire in a sandboxed frame.\n function settle() {\n schedule();\n [50, 150, 400, 1000].forEach(function (ms) { setTimeout(schedule, ms); });\n }\n if (document.readyState === 'complete') settle();\n else window.addEventListener('load', settle);\n\n function setHighlight(names) {\n var on = document.querySelectorAll('.' + HL);\n for (var i = 0; i < on.length; i++) on[i].classList.remove(HL);\n if (names && names.length) {\n var want = {};\n for (var k = 0; k < names.length && k < MAX_PARTS; k++) want[String(names[k])] = true;\n var els = document.querySelectorAll('[data-part]');\n for (var j = 0; j < els.length; j++) {\n if (want[els[j].getAttribute('data-part')] === true) els[j].classList.add(HL);\n }\n }\n // Our own class flip must not look like the agent's page changing.\n mo.takeRecords();\n }\n\n window.addEventListener('message', function (e) {\n if (e.source !== parent) return;\n var d = e.data;\n if (!d || d.__mockpit !== true) return;\n if (d.type === 'highlight') setHighlight(Array.isArray(d.parts) ? d.parts : []);\n else if (d.type === 'clear') setHighlight(null);\n else if (d.type === 'scroll') window.scrollBy(Number(d.dx) || 0, Number(d.dy) || 0);\n else if (d.type === 'hit') {\n var el = document.elementFromPoint(\n (Number(d.x) || 0) - (window.scrollX || 0),\n (Number(d.y) || 0) - (window.scrollY || 0)\n );\n var part = el && el.closest ? el.closest('[data-part]') : null;\n parent.postMessage({\n __mockpit: true,\n type: 'hit',\n ref: d.ref,\n part: part ? text(part.getAttribute('data-part')) : null,\n key: part ? text(part.getAttribute('data-part-key')) : null,\n }, '*');\n }\n });\n})();\n";
|
|
7
|
-
export declare const KNOBS_JS = "\n(function () {\n\nvar __kPathRe = /^[A-Za-z_][\\w-]{0,63}(\\.[A-Za-z_][\\w-]{0,63}){0,3}$/;\nvar __kTokenRe = /^[#\\w(),.%/+-][#\\w\\s(),.%/+-]*$/;\nfunction __kName(path) { return path.replace(/\\./g, '-'); }\nfunction __kCssString(s) {\n return '\"' + s.replace(/[\\\\\"]/g, function (c) { return '\\\\' + c; })\n .replace(/[\\u0000-\\u001f\\u007f]/g, function (c) { return '\\\\' + c.charCodeAt(0).toString(16) + ' '; }) + '\"';\n}\nfunction __kCss(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? '1' : '0';\n if (typeof v === 'string') return __kTokenRe.test(v) ? v : __kCssString(v);\n return null;\n}\nfunction __kVars(path, v) {\n var name = __kName(path);\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n var out = [];\n for (var f in v) {\n if (!Object.prototype.hasOwnProperty.call(v, f) || !/^[A-Za-z_][\\w-]{0,63}$/.test(f)) continue;\n var c = __kCss(v[f]);\n if (c !== null) out.push([name + '-' + f, c]);\n }\n return out;\n }\n var css = __kCss(v);\n return css === null ? [] : [[name, css]];\n}\nfunction __kAttr(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'string') return v;\n return null;\n}\n\n var doc = window.__mockpitDoc || {};\n var current = {};\n function own(o, k) { return Object.prototype.hasOwnProperty.call(o, k); }\n function bind(path, v) {\n var els = document.querySelectorAll('[data-k-bind]');\n for (var i = 0; i < els.length; i++) {\n if (els[i].getAttribute('data-k-bind') !== path) continue;\n els[i].textContent = v && typeof v === 'object' ? JSON.stringify(v) : String(v);\n }\n }\n function apply(values, live) {\n var root = document.documentElement;\n for (var path in values) {\n if (!own(values, path) || !__kPathRe.test(path)) continue;\n var v = values[path];\n if (v === null || v === undefined) continue;\n current[path] = v;\n if (live) {\n var vars = __kVars(path, v);\n if (!vars.length) root.style.removeProperty('--k-' + __kName(path));\n for (var i = 0; i < vars.length; i++) root.style.setProperty('--k-' + vars[i][0], vars[i][1]);\n var attr = __kAttr(v);\n if (attr === null) root.removeAttribute('data-k-' + __kName(path));\n else root.setAttribute('data-k-' + __kName(path), attr);\n }\n bind(path, v);\n }\n window.dispatchEvent(new CustomEvent('mockpit:knobs', {\n detail: { values: JSON.parse(JSON.stringify(current)) },\n }));\n }\n function initial() {
|
|
6
|
+
export declare const PARTS_JS = "\n(function () {\n if (window.parent === window) return;\n var HL = 'mockpit-part-hl';\n var MAX_PARTS = 200;\n var MAX_TEXT = 200;\n var doc = window.__mockpitDoc || {};\n var version = typeof doc.version === 'number' ? doc.version : null;\n function text(v) { return v == null ? null : String(v).slice(0, MAX_TEXT); }\n function isFixed(el) {\n for (var n = el; n && n.nodeType === 1; n = n.parentElement) {\n if (getComputedStyle(n).position === 'fixed') return true;\n }\n return false;\n }\n function collect() {\n var sx = window.scrollX || 0, sy = window.scrollY || 0;\n var els = document.querySelectorAll('[data-part]');\n var parts = [];\n for (var i = 0; i < els.length && parts.length < MAX_PARTS; i++) {\n var el = els[i];\n var name = text(el.getAttribute('data-part'));\n if (!name) continue;\n var r = el.getBoundingClientRect();\n var depth = 0;\n for (var p = el.parentElement; p; p = p.parentElement) if (p.hasAttribute('data-part')) depth++;\n var outer = el.parentElement && el.parentElement.closest('[data-part]');\n parts.push({\n name: name,\n label: text(el.getAttribute('data-part-label')) || name,\n key: text(el.getAttribute('data-part-key')),\n box: { x: r.left + sx, y: r.top + sy, w: r.width, h: r.height },\n visible: (r.width > 0 || r.height > 0) && getComputedStyle(el).visibility !== 'hidden',\n fixed: isFixed(el),\n depth: depth,\n parent: outer ? text(outer.getAttribute('data-part')) : null,\n order: i,\n });\n }\n return {\n version: version,\n parts: parts,\n height: document.body ? document.body.scrollHeight : document.documentElement.scrollHeight,\n scroll: { x: sx, y: sy },\n // scrollWidth past innerWidth: the design is wider than the frame.\n viewport: { w: innerWidth, h: innerHeight, scrollW: document.documentElement.scrollWidth },\n };\n }\n var last = '';\n var timer = 0;\n // setTimeout, not rAF: a hidden cross-origin frame may have rendering (and\n // with it rAF and ResizeObserver) throttled, but timers still run.\n function schedule() {\n if (timer) return;\n timer = setTimeout(function () {\n timer = 0;\n var m = collect();\n var key = JSON.stringify(m);\n if (key === last) return;\n last = key;\n m.__mockpit = true;\n m.type = 'parts';\n parent.postMessage(m, '*');\n }, 0);\n }\n\n var ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(schedule) : null;\n var observed = typeof WeakSet !== 'undefined' ? new WeakSet() : null;\n function observeAll() {\n if (!ro || !observed) return;\n // html/body too: a non-part sibling growing moves parts without resizing them.\n var els = [document.documentElement, document.body].concat([].slice.call(document.querySelectorAll('[data-part]')));\n for (var i = 0; i < els.length; i++) {\n var el = els[i];\n if (el && !observed.has(el)) { observed.add(el); ro.observe(el); }\n }\n }\n var moTimer = 0;\n var mo = new MutationObserver(function () {\n clearTimeout(moTimer);\n moTimer = setTimeout(function () { observeAll(); schedule(); }, 40);\n });\n mo.observe(document.documentElement, { subtree: true, childList: true, attributes: true, characterData: true });\n\n // Transforms and transitions move boxes without resizing or mutating anything.\n document.addEventListener('animationend', schedule, true);\n document.addEventListener('transitionend', schedule, true);\n window.addEventListener('scroll', schedule, { passive: true });\n window.addEventListener('resize', schedule);\n if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);\n observeAll();\n schedule();\n // WebKit: RO's initial callback may never fire in a sandboxed frame.\n function settle() {\n schedule();\n [50, 150, 400, 1000].forEach(function (ms) { setTimeout(schedule, ms); });\n }\n if (document.readyState === 'complete') settle();\n else window.addEventListener('load', settle);\n\n function setHighlight(names) {\n var on = document.querySelectorAll('.' + HL);\n for (var i = 0; i < on.length; i++) on[i].classList.remove(HL);\n if (names && names.length) {\n var want = {};\n for (var k = 0; k < names.length && k < MAX_PARTS; k++) want[String(names[k])] = true;\n var els = document.querySelectorAll('[data-part]');\n for (var j = 0; j < els.length; j++) {\n if (want[els[j].getAttribute('data-part')] === true) els[j].classList.add(HL);\n }\n }\n // Our own class flip must not look like the agent's page changing.\n mo.takeRecords();\n }\n\n // Try: the host lets the user click through the design; an element marked\n // data-goto moves them to another screen. Only the host knows what that\n // names, so the frame just reports it, and the click goes no further.\n var trying = false;\n function setTrying(on) {\n trying = on;\n document.documentElement.classList.toggle('mockpit-try', on);\n mo.takeRecords();\n }\n window.addEventListener('click', function (e) {\n if (!trying) return;\n var t = e.target && e.target.closest ? e.target.closest('[data-goto]') : null;\n if (!t) return;\n e.preventDefault();\n e.stopPropagation();\n parent.postMessage({\n __mockpit: true,\n type: 'goto',\n version: version,\n to: text(t.getAttribute('data-goto')),\n }, '*');\n }, true);\n\n // A finger's tap, in document px, for a host that lets touches through to the\n // page (its canvas, where the page scrolls natively). A drag is a scroll: the\n // browser cancels the pointer or it moves too far. A mouse clicks the host's\n // own overlay instead, so it never reports here.\n var tapAt = null;\n window.addEventListener('pointerdown', function (e) {\n tapAt = e.isPrimary && e.pointerType !== 'mouse' ? { x: e.clientX, y: e.clientY, t: Date.now() } : null;\n }, true);\n window.addEventListener('pointercancel', function () { tapAt = null; }, true);\n window.addEventListener('pointerup', function (e) {\n var a = tapAt;\n tapAt = null;\n if (!a || !e.isPrimary) return;\n if (Math.abs(e.clientX - a.x) > 10 || Math.abs(e.clientY - a.y) > 10 || Date.now() - a.t > 600) return;\n parent.postMessage({\n __mockpit: true,\n type: 'tap',\n x: e.clientX + (window.scrollX || 0),\n y: e.clientY + (window.scrollY || 0),\n }, '*');\n }, true);\n\n // Two fingers are the host's: a page laid on its canvas takes one-finger\n // scrolls itself, but a pinch zooms the canvas, not this page or the browser.\n var pinching = false;\n function pinch(phase, e) {\n var t = e.touches;\n parent.postMessage({\n __mockpit: true,\n type: 'pinch',\n phase: phase,\n a: t[0] ? { x: t[0].clientX, y: t[0].clientY } : null,\n b: t[1] ? { x: t[1].clientX, y: t[1].clientY } : null,\n }, '*');\n }\n window.addEventListener('touchstart', function (e) {\n if (e.touches.length !== 2) return;\n e.preventDefault();\n pinching = true;\n pinch('start', e);\n }, { capture: true, passive: false });\n window.addEventListener('touchmove', function (e) {\n if (!pinching || e.touches.length < 2) return;\n e.preventDefault();\n pinch('move', e);\n }, { capture: true, passive: false });\n function release(e) {\n if (!pinching || e.touches.length >= 2) return;\n pinching = false;\n pinch('end', e);\n }\n window.addEventListener('touchend', release, true);\n window.addEventListener('touchcancel', release, true);\n // \u2318/Ctrl+scroll and a trackpad pinch (ctrl+wheel) are the host's too: they\n // zoom the canvas around the pointer, never this page or the browser.\n window.addEventListener('wheel', function (e) {\n if (!e.ctrlKey && !e.metaKey) return;\n e.preventDefault();\n parent.postMessage({ __mockpit: true, type: 'zoom', x: e.clientX, y: e.clientY, dy: e.deltaY }, '*');\n }, { capture: true, passive: false });\n\n window.addEventListener('message', function (e) {\n if (e.source !== parent) return;\n var d = e.data;\n if (!d || d.__mockpit !== true) return;\n if (d.type === 'highlight') setHighlight(Array.isArray(d.parts) ? d.parts : []);\n else if (d.type === 'clear') setHighlight(null);\n else if (d.type === 'try') setTrying(d.on === true);\n else if (d.type === 'scroll') window.scrollBy(Number(d.dx) || 0, Number(d.dy) || 0);\n else if (d.type === 'hit') {\n var el = document.elementFromPoint(\n (Number(d.x) || 0) - (window.scrollX || 0),\n (Number(d.y) || 0) - (window.scrollY || 0)\n );\n var part = el && el.closest ? el.closest('[data-part]') : null;\n parent.postMessage({\n __mockpit: true,\n type: 'hit',\n ref: d.ref,\n part: part ? text(part.getAttribute('data-part')) : null,\n key: part ? text(part.getAttribute('data-part-key')) : null,\n }, '*');\n }\n });\n})();\n";
|
|
7
|
+
export declare const KNOBS_JS = "\n(function () {\n\nvar __kPathRe = /^[A-Za-z_][\\w-]{0,63}(\\.[A-Za-z_][\\w-]{0,63}){0,3}$/;\nvar __kTokenRe = /^[#\\w(),.%/+-][#\\w\\s(),.%/+-]*$/;\nfunction __kName(path) { return path.replace(/\\./g, '-'); }\nfunction __kCssString(s) {\n return '\"' + s.replace(/[\\\\\"]/g, function (c) { return '\\\\' + c; })\n .replace(/[\\u0000-\\u001f\\u007f]/g, function (c) { return '\\\\' + c.charCodeAt(0).toString(16) + ' '; }) + '\"';\n}\nfunction __kCss(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? '1' : '0';\n if (typeof v === 'string') return __kTokenRe.test(v) ? v : __kCssString(v);\n return null;\n}\nfunction __kVars(path, v) {\n var name = __kName(path);\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n var out = [];\n for (var f in v) {\n if (!Object.prototype.hasOwnProperty.call(v, f) || !/^[A-Za-z_][\\w-]{0,63}$/.test(f)) continue;\n var c = __kCss(v[f]);\n if (c !== null) out.push([name + '-' + f, c]);\n }\n return out;\n }\n var css = __kCss(v);\n return css === null ? [] : [[name, css]];\n}\nfunction __kAttr(v) {\n if (typeof v === 'number') return isFinite(v) ? String(v) : null;\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'string') return v;\n return null;\n}\n\n var doc = window.__mockpitDoc || {};\n var current = {};\n function own(o, k) { return Object.prototype.hasOwnProperty.call(o, k); }\n function bind(path, v) {\n var els = document.querySelectorAll('[data-k-bind]');\n for (var i = 0; i < els.length; i++) {\n if (els[i].getAttribute('data-k-bind') !== path) continue;\n els[i].textContent = v && typeof v === 'object' ? JSON.stringify(v) : String(v);\n }\n }\n function apply(values, live) {\n var root = document.documentElement;\n for (var path in values) {\n if (!own(values, path) || !__kPathRe.test(path)) continue;\n var v = values[path];\n if (v === null || v === undefined) continue;\n current[path] = v;\n if (live) {\n var vars = __kVars(path, v);\n if (!vars.length) root.style.removeProperty('--k-' + __kName(path));\n for (var i = 0; i < vars.length; i++) root.style.setProperty('--k-' + vars[i][0], vars[i][1]);\n var attr = __kAttr(v);\n if (attr === null) root.removeAttribute('data-k-' + __kName(path));\n else root.setAttribute('data-k-' + __kName(path), attr);\n }\n bind(path, v);\n }\n window.dispatchEvent(new CustomEvent('mockpit:knobs', {\n detail: { values: JSON.parse(JSON.stringify(current)) },\n }));\n }\n // The URL's values are already on <html>; a live command may also have come\n // in before the body existed, so both are bound once it does.\n function initial() {\n var start = doc.knobs && typeof doc.knobs === 'object' ? doc.knobs : {};\n for (var k in start) if (own(start, k) && !own(current, k)) current[k] = start[k];\n apply(current, false);\n }\n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initial);\n else initial();\n // Runs from <head>, before the page's own markup and anything slow it loads\n // (a web font, a CDN stylesheet), so the host can send values at once rather\n // than wait for the first parts report, which those can hold back.\n if (parent !== window) parent.postMessage({ __mockpit: true, type: 'ready', version: doc.version }, '*');\n window.addEventListener('message', function (e) {\n if (e.source !== parent || parent === window) return;\n var d = e.data;\n if (!d || d.__mockpit !== true || d.type !== 'knobs') return;\n if (d.values && typeof d.values === 'object' && !Array.isArray(d.values)) apply(d.values, true);\n });\n})();\n";
|
|
8
8
|
export declare function knobCss(v: unknown): string | null;
|
|
9
9
|
export declare function knobVars(path: string, v: unknown): [string, string][];
|
|
10
10
|
export declare function knobAttr(v: unknown): string | null;
|
|
@@ -13,6 +13,10 @@ export declare function knobPreamble(version: number | undefined, knobs: Record<
|
|
|
13
13
|
htmlTag: string;
|
|
14
14
|
headScript: string;
|
|
15
15
|
};
|
|
16
|
+
export declare function knobAttributes(knobs: Record<string, unknown> | undefined): {
|
|
17
|
+
attrs: string;
|
|
18
|
+
values: Record<string, unknown>;
|
|
19
|
+
};
|
|
16
20
|
export interface StaticAsset {
|
|
17
21
|
path: string;
|
|
18
22
|
contentType: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scanFills, slotCss } from "./fills.js";
|
|
1
2
|
import { CDN_ALLOWLIST, checkCdnUrl } from "./cdn.js";
|
|
2
3
|
import { CORE_CSS, KITS, kitAssets } from "./kits.js";
|
|
3
4
|
import { expandIcons } from "./icons.js";
|
|
@@ -63,7 +64,11 @@ const TOKENS_CSS = `
|
|
|
63
64
|
--border-radius-lg: 12px;
|
|
64
65
|
--border-radius-xl: 16px;
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
+
/* A page that scrolls inside a host's frame stops at its own ends instead of
|
|
68
|
+
handing the rest of a swipe to the host (Safari reads the root's value), and
|
|
69
|
+
a pinch on it can't zoom the whole viewer around it. */
|
|
70
|
+
html { box-sizing: border-box; scrollbar-width: none; overscroll-behavior: contain; touch-action: pan-x pan-y; }
|
|
71
|
+
body { overscroll-behavior: contain; }
|
|
67
72
|
html::-webkit-scrollbar, body::-webkit-scrollbar { display: none; }
|
|
68
73
|
*, *::before, *::after { box-sizing: inherit; }
|
|
69
74
|
body {
|
|
@@ -356,7 +361,7 @@ export const HIT_TEST_JS = `
|
|
|
356
361
|
// copy {text} window.copyToClipboard (BRIDGE_JS)
|
|
357
362
|
// switch-session {key} Cmd+Opt+ArrowUp/Down (BRIDGE_JS)
|
|
358
363
|
// hit-test-result {ref, path, text, rect} reply to hit-test (HIT_TEST_JS)
|
|
359
|
-
// parts {version, parts, height, scroll:{x,y}, viewport:{w,h}} (PARTS_JS)
|
|
364
|
+
// parts {version, parts, height, scroll:{x,y}, viewport:{w,h,scrollW}} (PARTS_JS)
|
|
360
365
|
// parts[i] = {name, label, key, box:{x,y,w,h}, visible, fixed, depth, parent, order}
|
|
361
366
|
// box is in DOCUMENT px (viewport rect + scroll), so the host draws
|
|
362
367
|
// (box − scroll) × scale, which is also right for fixed and sticky parts.
|
|
@@ -370,6 +375,23 @@ export const HIT_TEST_JS = `
|
|
|
370
375
|
// 50/150/400/1000ms timers, and on any layout-affecting change; never
|
|
371
376
|
// twice in a row with identical content.
|
|
372
377
|
// hit {ref, part, key} reply to hit; part = name|null
|
|
378
|
+
// goto {version, to} a click on [data-goto] while the host
|
|
379
|
+
// has Try on (PARTS_JS); to is the
|
|
380
|
+
// attribute as written ("next", "prev",
|
|
381
|
+
// "<mock>:<state>"), ≤200 chars. The host
|
|
382
|
+
// honours it only while Try is on, from
|
|
383
|
+
// the frame on stage at its version, and
|
|
384
|
+
// only to a screen of this project:
|
|
385
|
+
// never a URL
|
|
386
|
+
// tap {x, y} a finger's tap in document px (PARTS_JS);
|
|
387
|
+
// a drag never reports
|
|
388
|
+
// ready {version} the knob runtime is listening (KNOBS_JS,
|
|
389
|
+
// from <head>); values can be sent now
|
|
390
|
+
// pinch {phase, a:{x,y}, b:{x,y}} two fingers on the page, in viewport px
|
|
391
|
+
// zoom {x, y, dy} ⌘/Ctrl+scroll or a trackpad pinch, in viewport px
|
|
392
|
+
// (PARTS_JS); phase start|move|end. The
|
|
393
|
+
// page's own two-finger touches are
|
|
394
|
+
// cancelled: the host zooms its canvas
|
|
373
395
|
//
|
|
374
396
|
// host → frame
|
|
375
397
|
// hit-test {ref, x, y} x,y normalized 0..1 of the document box (Mark tool)
|
|
@@ -383,6 +405,9 @@ export const HIT_TEST_JS = `
|
|
|
383
405
|
// overlay, forwarded)
|
|
384
406
|
// knobs {values} {path: value}, merged into the current values
|
|
385
407
|
// and applied exactly like the `?k=` preamble
|
|
408
|
+
// try {on} Try is on or off: while on, [data-goto] elements are
|
|
409
|
+
// outlined and a click on one reports goto instead
|
|
410
|
+
// of reaching the page; a new document starts off
|
|
386
411
|
//
|
|
387
412
|
// Knobs (`/s/:id?k=`, validated by the route against the mock's declared knobs,
|
|
388
413
|
// and the `knobs` command): for each path, dots become "-" in names, and
|
|
@@ -482,7 +507,8 @@ export const PARTS_JS = `
|
|
|
482
507
|
parts: parts,
|
|
483
508
|
height: document.body ? document.body.scrollHeight : document.documentElement.scrollHeight,
|
|
484
509
|
scroll: { x: sx, y: sy },
|
|
485
|
-
|
|
510
|
+
// scrollWidth past innerWidth: the design is wider than the frame.
|
|
511
|
+
viewport: { w: innerWidth, h: innerHeight, scrollW: document.documentElement.scrollWidth },
|
|
486
512
|
};
|
|
487
513
|
}
|
|
488
514
|
var last = '';
|
|
@@ -552,12 +578,97 @@ export const PARTS_JS = `
|
|
|
552
578
|
mo.takeRecords();
|
|
553
579
|
}
|
|
554
580
|
|
|
581
|
+
// Try: the host lets the user click through the design; an element marked
|
|
582
|
+
// data-goto moves them to another screen. Only the host knows what that
|
|
583
|
+
// names, so the frame just reports it, and the click goes no further.
|
|
584
|
+
var trying = false;
|
|
585
|
+
function setTrying(on) {
|
|
586
|
+
trying = on;
|
|
587
|
+
document.documentElement.classList.toggle('mockpit-try', on);
|
|
588
|
+
mo.takeRecords();
|
|
589
|
+
}
|
|
590
|
+
window.addEventListener('click', function (e) {
|
|
591
|
+
if (!trying) return;
|
|
592
|
+
var t = e.target && e.target.closest ? e.target.closest('[data-goto]') : null;
|
|
593
|
+
if (!t) return;
|
|
594
|
+
e.preventDefault();
|
|
595
|
+
e.stopPropagation();
|
|
596
|
+
parent.postMessage({
|
|
597
|
+
__mockpit: true,
|
|
598
|
+
type: 'goto',
|
|
599
|
+
version: version,
|
|
600
|
+
to: text(t.getAttribute('data-goto')),
|
|
601
|
+
}, '*');
|
|
602
|
+
}, true);
|
|
603
|
+
|
|
604
|
+
// A finger's tap, in document px, for a host that lets touches through to the
|
|
605
|
+
// page (its canvas, where the page scrolls natively). A drag is a scroll: the
|
|
606
|
+
// browser cancels the pointer or it moves too far. A mouse clicks the host's
|
|
607
|
+
// own overlay instead, so it never reports here.
|
|
608
|
+
var tapAt = null;
|
|
609
|
+
window.addEventListener('pointerdown', function (e) {
|
|
610
|
+
tapAt = e.isPrimary && e.pointerType !== 'mouse' ? { x: e.clientX, y: e.clientY, t: Date.now() } : null;
|
|
611
|
+
}, true);
|
|
612
|
+
window.addEventListener('pointercancel', function () { tapAt = null; }, true);
|
|
613
|
+
window.addEventListener('pointerup', function (e) {
|
|
614
|
+
var a = tapAt;
|
|
615
|
+
tapAt = null;
|
|
616
|
+
if (!a || !e.isPrimary) return;
|
|
617
|
+
if (Math.abs(e.clientX - a.x) > 10 || Math.abs(e.clientY - a.y) > 10 || Date.now() - a.t > 600) return;
|
|
618
|
+
parent.postMessage({
|
|
619
|
+
__mockpit: true,
|
|
620
|
+
type: 'tap',
|
|
621
|
+
x: e.clientX + (window.scrollX || 0),
|
|
622
|
+
y: e.clientY + (window.scrollY || 0),
|
|
623
|
+
}, '*');
|
|
624
|
+
}, true);
|
|
625
|
+
|
|
626
|
+
// Two fingers are the host's: a page laid on its canvas takes one-finger
|
|
627
|
+
// scrolls itself, but a pinch zooms the canvas, not this page or the browser.
|
|
628
|
+
var pinching = false;
|
|
629
|
+
function pinch(phase, e) {
|
|
630
|
+
var t = e.touches;
|
|
631
|
+
parent.postMessage({
|
|
632
|
+
__mockpit: true,
|
|
633
|
+
type: 'pinch',
|
|
634
|
+
phase: phase,
|
|
635
|
+
a: t[0] ? { x: t[0].clientX, y: t[0].clientY } : null,
|
|
636
|
+
b: t[1] ? { x: t[1].clientX, y: t[1].clientY } : null,
|
|
637
|
+
}, '*');
|
|
638
|
+
}
|
|
639
|
+
window.addEventListener('touchstart', function (e) {
|
|
640
|
+
if (e.touches.length !== 2) return;
|
|
641
|
+
e.preventDefault();
|
|
642
|
+
pinching = true;
|
|
643
|
+
pinch('start', e);
|
|
644
|
+
}, { capture: true, passive: false });
|
|
645
|
+
window.addEventListener('touchmove', function (e) {
|
|
646
|
+
if (!pinching || e.touches.length < 2) return;
|
|
647
|
+
e.preventDefault();
|
|
648
|
+
pinch('move', e);
|
|
649
|
+
}, { capture: true, passive: false });
|
|
650
|
+
function release(e) {
|
|
651
|
+
if (!pinching || e.touches.length >= 2) return;
|
|
652
|
+
pinching = false;
|
|
653
|
+
pinch('end', e);
|
|
654
|
+
}
|
|
655
|
+
window.addEventListener('touchend', release, true);
|
|
656
|
+
window.addEventListener('touchcancel', release, true);
|
|
657
|
+
// ⌘/Ctrl+scroll and a trackpad pinch (ctrl+wheel) are the host's too: they
|
|
658
|
+
// zoom the canvas around the pointer, never this page or the browser.
|
|
659
|
+
window.addEventListener('wheel', function (e) {
|
|
660
|
+
if (!e.ctrlKey && !e.metaKey) return;
|
|
661
|
+
e.preventDefault();
|
|
662
|
+
parent.postMessage({ __mockpit: true, type: 'zoom', x: e.clientX, y: e.clientY, dy: e.deltaY }, '*');
|
|
663
|
+
}, { capture: true, passive: false });
|
|
664
|
+
|
|
555
665
|
window.addEventListener('message', function (e) {
|
|
556
666
|
if (e.source !== parent) return;
|
|
557
667
|
var d = e.data;
|
|
558
668
|
if (!d || d.__mockpit !== true) return;
|
|
559
669
|
if (d.type === 'highlight') setHighlight(Array.isArray(d.parts) ? d.parts : []);
|
|
560
670
|
else if (d.type === 'clear') setHighlight(null);
|
|
671
|
+
else if (d.type === 'try') setTrying(d.on === true);
|
|
561
672
|
else if (d.type === 'scroll') window.scrollBy(Number(d.dx) || 0, Number(d.dy) || 0);
|
|
562
673
|
else if (d.type === 'hit') {
|
|
563
674
|
var el = document.elementFromPoint(
|
|
@@ -612,9 +723,19 @@ ${KNOB_VALUE_JS}
|
|
|
612
723
|
detail: { values: JSON.parse(JSON.stringify(current)) },
|
|
613
724
|
}));
|
|
614
725
|
}
|
|
615
|
-
|
|
726
|
+
// The URL's values are already on <html>; a live command may also have come
|
|
727
|
+
// in before the body existed, so both are bound once it does.
|
|
728
|
+
function initial() {
|
|
729
|
+
var start = doc.knobs && typeof doc.knobs === 'object' ? doc.knobs : {};
|
|
730
|
+
for (var k in start) if (own(start, k) && !own(current, k)) current[k] = start[k];
|
|
731
|
+
apply(current, false);
|
|
732
|
+
}
|
|
616
733
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initial);
|
|
617
734
|
else initial();
|
|
735
|
+
// Runs from <head>, before the page's own markup and anything slow it loads
|
|
736
|
+
// (a web font, a CDN stylesheet), so the host can send values at once rather
|
|
737
|
+
// than wait for the first parts report, which those can hold back.
|
|
738
|
+
if (parent !== window) parent.postMessage({ __mockpit: true, type: 'ready', version: doc.version }, '*');
|
|
618
739
|
window.addEventListener('message', function (e) {
|
|
619
740
|
if (e.source !== parent || parent === window) return;
|
|
620
741
|
var d = e.data;
|
|
@@ -680,6 +801,16 @@ const scriptJson = (v) => JSON.stringify(v)
|
|
|
680
801
|
// knobCss (quoted unless plain tokens) and then escapeHtml as the style
|
|
681
802
|
// attribute, and the JSON through scriptJson.
|
|
682
803
|
export function knobPreamble(version, knobs) {
|
|
804
|
+
const { attrs, values } = knobAttributes(knobs);
|
|
805
|
+
return {
|
|
806
|
+
htmlTag: `<html lang="en"${attrs}>`,
|
|
807
|
+
headScript: `<script>window.__mockpitDoc=${scriptJson({ version: version ?? null, knobs: values })};</script>`,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
// Knob values as an element's `data-k-*` attributes and `--k-*` style vars, the
|
|
811
|
+
// same marks the preamble puts on <html>; an embedded component's wrapper
|
|
812
|
+
// carries its own settled values this way.
|
|
813
|
+
export function knobAttributes(knobs) {
|
|
683
814
|
const attrs = [];
|
|
684
815
|
const vars = [];
|
|
685
816
|
const values = {};
|
|
@@ -694,14 +825,11 @@ export function knobPreamble(version, knobs) {
|
|
|
694
825
|
vars.push(`--k-${name}:${css}`);
|
|
695
826
|
}
|
|
696
827
|
const style = vars.length ? ` style="${escapeHtml(vars.join(";"))}"` : "";
|
|
697
|
-
return {
|
|
698
|
-
htmlTag: `<html lang="en"${attrs.join("")}${style}>`,
|
|
699
|
-
headScript: `<script>window.__mockpitDoc=${scriptJson({ version: version ?? null, knobs: values })};</script>`,
|
|
700
|
-
};
|
|
828
|
+
return { attrs: `${attrs.join("")}${style}`, values };
|
|
701
829
|
}
|
|
702
830
|
// Our own highlight, shipped in the base stylesheet so turning it on is a class
|
|
703
831
|
// flip rather than a DOM insertion the parts observer would report.
|
|
704
|
-
const PART_HIGHLIGHT_CSS = `.mockpit-part-hl{outline:2px solid #8b7bff !important;outline-offset:2px}`;
|
|
832
|
+
const PART_HIGHLIGHT_CSS = `.mockpit-part-hl{outline:2px solid #8b7bff !important;outline-offset:2px}html.mockpit-try [data-goto]{outline:2px dashed #3fb27f !important;outline-offset:3px;cursor:pointer}`;
|
|
705
833
|
// Tailwind's browser build, pinned to a major (jsdelivr is already on the CDN
|
|
706
834
|
// allowlist, so this needs no CSP widening). Injected only when a project's
|
|
707
835
|
// design settings say `kit: "tailwind"`, i.e. the repo itself is a Tailwind
|
|
@@ -1074,6 +1202,9 @@ export function renderHtmlPage(doc) {
|
|
|
1074
1202
|
]),
|
|
1075
1203
|
`<style>${design.css}${colorSchemeCss(doc.mode)}</style>`,
|
|
1076
1204
|
].join("\n");
|
|
1205
|
+
// Slots (fills.ts): only the chosen fill of each shows, keyed on the knob
|
|
1206
|
+
// attribute; with no value yet, the default fill.
|
|
1207
|
+
const fillCss = slotCss(scanFills(doc.html).slots);
|
|
1077
1208
|
return `<!doctype html>
|
|
1078
1209
|
${htmlTag}
|
|
1079
1210
|
<head>
|
|
@@ -1082,7 +1213,9 @@ ${htmlTag}
|
|
|
1082
1213
|
<meta http-equiv="Content-Security-Policy" content="${buildCsp(doc.origin)}">
|
|
1083
1214
|
<title>${escapeHtml(doc.title)}</title>
|
|
1084
1215
|
${preamble.headScript}
|
|
1216
|
+
${scriptTag(doc.origin, KNOBS_PATH)}
|
|
1085
1217
|
${styles}
|
|
1218
|
+
${fillCss ? `<style>${fillCss}</style>` : ""}
|
|
1086
1219
|
${design.headScripts}
|
|
1087
1220
|
${kits.flatMap((k) => (k.script ? [`<script src="${escapeHtml(k.script)}" defer></script>`] : [])).join("\n")}
|
|
1088
1221
|
</head>
|
|
@@ -1091,7 +1224,6 @@ ${SVG_DEFS}
|
|
|
1091
1224
|
${expandIcons(doc.html, doc.resolveIcon ?? (() => null)).html}
|
|
1092
1225
|
${scriptTag(doc.origin, BRIDGE_PATH)}
|
|
1093
1226
|
${scriptTag(doc.origin, HIT_TEST_PATH)}
|
|
1094
|
-
${scriptTag(doc.origin, KNOBS_PATH)}
|
|
1095
1227
|
${scriptTag(doc.origin, PARTS_PATH)}
|
|
1096
1228
|
${kit.js ? `<script>${kit.js}</script>` : ""}
|
|
1097
1229
|
</body>
|
package/dist/server/themes.d.ts
CHANGED
package/dist/server/themes.js
CHANGED
|
@@ -21,6 +21,8 @@ function viewerVars(p) {
|
|
|
21
21
|
"border-2": p.border2,
|
|
22
22
|
accent: p.info.text,
|
|
23
23
|
"accent-bg": p.info.bg,
|
|
24
|
+
"accent-solid": p.info.solid ?? p.info.text,
|
|
25
|
+
"on-accent": p.info.onSolid ?? "#fff",
|
|
24
26
|
hover: p.hover,
|
|
25
27
|
danger: p.danger.text,
|
|
26
28
|
};
|
|
@@ -111,7 +113,14 @@ export const DIALKIT = {
|
|
|
111
113
|
border: "#e6e6e6",
|
|
112
114
|
border2: "#d6d6d6",
|
|
113
115
|
hover: "#ebebeb",
|
|
114
|
-
|
|
116
|
+
// White on #2563eb is already 5.2:1, so the fill is the accent itself.
|
|
117
|
+
info: {
|
|
118
|
+
bg: "rgba(37, 99, 235, 0.1)",
|
|
119
|
+
text: "#2563eb",
|
|
120
|
+
border: "#2563eb",
|
|
121
|
+
solid: "#2563eb",
|
|
122
|
+
onSolid: "#fff",
|
|
123
|
+
},
|
|
115
124
|
success: { bg: "rgba(43, 147, 72, 0.12)", text: "#2b9348", border: "#2b9348" },
|
|
116
125
|
warning: { bg: "#fff8e6", text: "#e8590c", border: "#f0e0b8" },
|
|
117
126
|
danger: { bg: "rgba(220, 38, 38, 0.1)", text: "#dc2626", border: "#dc2626" },
|
|
@@ -126,7 +135,14 @@ export const DIALKIT = {
|
|
|
126
135
|
border: "#242424",
|
|
127
136
|
border2: "#3a3a3a",
|
|
128
137
|
hover: "#262626",
|
|
129
|
-
|
|
138
|
+
// #8b7bff under white text is 3.3:1; the same hue, darker, is 5.0:1.
|
|
139
|
+
info: {
|
|
140
|
+
bg: "rgba(139, 123, 255, 0.16)",
|
|
141
|
+
text: "#8b7bff",
|
|
142
|
+
border: "#8b7bff",
|
|
143
|
+
solid: "#6b5ae0",
|
|
144
|
+
onSolid: "#fff",
|
|
145
|
+
},
|
|
130
146
|
success: { bg: "rgba(74, 222, 128, 0.14)", text: "#4ade80", border: "#4ade80" },
|
|
131
147
|
warning: { bg: "#2a2312", text: "#e8590c", border: "#4a3c1c" },
|
|
132
148
|
danger: { bg: "rgba(248, 113, 113, 0.14)", text: "#f87171", border: "#f87171" },
|
|
@@ -238,10 +254,14 @@ function pick(vars, names) {
|
|
|
238
254
|
return null;
|
|
239
255
|
}
|
|
240
256
|
function accentFrom(vars, bg, text, border, fallback) {
|
|
257
|
+
const ink = pick(vars, text);
|
|
241
258
|
return {
|
|
242
259
|
bg: pick(vars, bg) ?? fallback.bg,
|
|
243
|
-
text:
|
|
260
|
+
text: ink ?? fallback.text,
|
|
244
261
|
border: pick(vars, border) ?? fallback.border,
|
|
262
|
+
// The default's solid pair belongs to the default's ink; a repo's own ink
|
|
263
|
+
// fills with itself.
|
|
264
|
+
...(ink ? {} : { solid: fallback.solid, onSolid: fallback.onSolid }),
|
|
245
265
|
};
|
|
246
266
|
}
|
|
247
267
|
function paletteFrom(vars, fallback) {
|