180 lines
5.4 KiB
JavaScript
180 lines
5.4 KiB
JavaScript
/**
|
|
* Framework-neutral Impeccable live chrome contract.
|
|
*
|
|
* The production browser bundle is intentionally plain DOM so Svelte, React,
|
|
* Vue, and static adapters can all mount the same chrome. This module is the
|
|
* testable contract/inventory for that bundle; live-browser.js mirrors these
|
|
* values at runtime because it is served as a standalone script.
|
|
*/
|
|
|
|
export const LIVE_CHROME_MOUNT_CONTRACT = Object.freeze([
|
|
'root',
|
|
'transport',
|
|
'state',
|
|
'actions',
|
|
]);
|
|
|
|
export const LIVE_UI_SURFACES = Object.freeze([
|
|
{
|
|
key: 'global-bottom-bar',
|
|
ids: [
|
|
'impeccable-live-global-bar',
|
|
'impeccable-live-global-bar-brand',
|
|
'impeccable-live-pick-toggle',
|
|
'impeccable-live-insert-toggle',
|
|
'impeccable-live-detect-toggle',
|
|
'impeccable-live-detect-badge',
|
|
'impeccable-live-design-toggle',
|
|
'impeccable-live-page-chat',
|
|
'impeccable-live-page-chat-input',
|
|
'impeccable-live-page-chat-voice',
|
|
],
|
|
states: ['rest', 'hover', 'focus-visible', 'pressed', 'active', 'tooltip'],
|
|
},
|
|
{
|
|
key: 'pending-copy-edit-dock',
|
|
ids: ['impeccable-live-pending-dock'],
|
|
states: ['closed', 'open', 'hover', 'pressed', 'loading', 'rollback', 'keep-fixing'],
|
|
},
|
|
{
|
|
key: 'element-selection-chrome',
|
|
ids: [
|
|
'impeccable-live-highlight',
|
|
'impeccable-live-tooltip',
|
|
'impeccable-live-bar',
|
|
'impeccable-live-configure-input-wrap',
|
|
'impeccable-live-input',
|
|
'impeccable-live-configure-voice',
|
|
],
|
|
states: ['rest', 'hover', 'focus-visible', 'pressed', 'disabled'],
|
|
},
|
|
{
|
|
key: 'action-picker',
|
|
ids: ['impeccable-live-picker'],
|
|
states: ['closed', 'open', 'option-hover', 'option-focus'],
|
|
},
|
|
{
|
|
key: 'edit-chrome',
|
|
ids: ['impeccable-live-edit-badge'],
|
|
states: ['enabled', 'disabled', 'editing', 'cancel', 'save', 'edited-content'],
|
|
},
|
|
{
|
|
key: 'generating-row',
|
|
ids: ['impeccable-live-bar', 'impeccable-live-shader'],
|
|
states: ['action-label', 'animated-dots', 'generating', 'done'],
|
|
},
|
|
{
|
|
key: 'variant-cycling-row',
|
|
ids: ['impeccable-live-bar', 'impeccable-live-params-panel'],
|
|
states: ['variant-1', 'variant-2', 'variant-3', 'left-disabled', 'right-disabled', 'dot-click', 'accept', 'discard'],
|
|
},
|
|
{
|
|
key: 'variant-params-panel',
|
|
ids: ['impeccable-live-params-panel'],
|
|
states: ['closed', 'open-above', 'open-below', 'range', 'steps', 'toggle'],
|
|
},
|
|
{
|
|
key: 'saving-confirmed-rows',
|
|
ids: ['impeccable-live-bar'],
|
|
states: ['saving', 'applying-variant', 'confirmed'],
|
|
},
|
|
{
|
|
key: 'insert-mode-chrome',
|
|
ids: [
|
|
'impeccable-live-insert-line',
|
|
'impeccable-live-insert-placeholder',
|
|
'impeccable-live-placeholder-resize',
|
|
'impeccable-live-insert-input',
|
|
'impeccable-live-insert-voice',
|
|
'impeccable-live-insert-create',
|
|
'impeccable-live-insert-create-tooltip',
|
|
],
|
|
states: ['toggle-active', 'line', 'placeholder', 'resize', 'enabled', 'disabled', 'tooltip'],
|
|
},
|
|
{
|
|
key: 'annotation-chrome',
|
|
ids: [
|
|
'impeccable-live-annot',
|
|
'impeccable-live-annot-svg',
|
|
'impeccable-live-annot-pins',
|
|
'impeccable-live-annot-clear',
|
|
],
|
|
states: ['overlay', 'drawing', 'pin', 'pin-edit', 'clear'],
|
|
},
|
|
{
|
|
key: 'design-system-panel',
|
|
ids: ['impeccable-live-design-host'],
|
|
states: ['closed', 'open', 'tabs', 'token-tiles', 'copy'],
|
|
},
|
|
{
|
|
key: 'toasts-and-errors',
|
|
ids: ['impeccable-live-toast'],
|
|
states: ['normal', 'error', 'no-variants-mounted'],
|
|
},
|
|
{
|
|
key: 'css-isolation-boundary',
|
|
ids: ['impeccable-live-root'],
|
|
states: ['shadow-root', 'style-tags', 'hostile-css'],
|
|
},
|
|
]);
|
|
|
|
export const LIVE_UI_COMPONENT_IDS = Object.freeze([
|
|
...new Set(LIVE_UI_SURFACES.flatMap((surface) => surface.ids)),
|
|
]);
|
|
|
|
export function resolveLiveUiRoot(env = globalThis) {
|
|
const doc = env?.document;
|
|
const explicit = env?.__IMPECCABLE_LIVE_UI_ROOT__
|
|
|| env?.window?.__IMPECCABLE_LIVE_UI_ROOT__;
|
|
if (explicit && typeof explicit.appendChild === 'function') return explicit;
|
|
return doc?.body || null;
|
|
}
|
|
|
|
export function getLiveUiElementById(id, env = globalThis) {
|
|
const doc = env?.document;
|
|
const root = resolveLiveUiRoot(env);
|
|
if (!id) return null;
|
|
if (root?.getElementById) {
|
|
const found = root.getElementById(id);
|
|
if (found) return found;
|
|
}
|
|
if (root?.querySelector) {
|
|
const found = root.querySelector('#' + escapeCssIdent(id));
|
|
if (found) return found;
|
|
}
|
|
return doc?.getElementById?.(id) || null;
|
|
}
|
|
|
|
export function appendToLiveUiRoot(el, env = globalThis) {
|
|
const root = resolveLiveUiRoot(env);
|
|
if (!root) throw new Error('Impeccable live UI root is not available');
|
|
root.appendChild(el);
|
|
return el;
|
|
}
|
|
|
|
export function appendStyleToLiveUiRoot(styleEl, env = globalThis) {
|
|
const doc = env?.document;
|
|
const root = resolveLiveUiRoot(env);
|
|
if (root && root !== doc?.body) {
|
|
root.appendChild(styleEl);
|
|
} else {
|
|
(doc?.head || doc?.body || root).appendChild(styleEl);
|
|
}
|
|
return styleEl;
|
|
}
|
|
|
|
export function activeElementDeep(doc = globalThis.document) {
|
|
let active = doc?.activeElement || null;
|
|
while (active?.shadowRoot?.activeElement) {
|
|
active = active.shadowRoot.activeElement;
|
|
}
|
|
return active;
|
|
}
|
|
|
|
function escapeCssIdent(value) {
|
|
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
|
return CSS.escape(String(value));
|
|
}
|
|
return String(value).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
|
|
}
|