Created
July 2, 2024 10:18
-
-
Save baptisteArno/dc45ea3aa0bdde8b405acc0cebe1eb57 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function cloneProps(props) { | |
const propKeys = Object.keys(props); | |
return propKeys.reduce((memo, k) => { | |
const prop = props[k]; | |
memo[k] = Object.assign({}, prop); | |
if (isObject$4(prop.value) && !isFunction$3(prop.value) && !Array.isArray(prop.value)) memo[k].value = Object.assign({}, prop.value); | |
if (Array.isArray(prop.value)) memo[k].value = prop.value.slice(0); | |
return memo; | |
}, {}); | |
} | |
function normalizePropDefs(props) { | |
if (!props) return {}; | |
const propKeys = Object.keys(props); | |
return propKeys.reduce((memo, k) => { | |
const v = props[k]; | |
memo[k] = !(isObject$4(v) && "value" in v) ? { | |
value: v | |
} : v; | |
memo[k].attribute || (memo[k].attribute = toAttribute(k)); | |
memo[k].parse = "parse" in memo[k] ? memo[k].parse : typeof memo[k].value !== "string"; | |
return memo; | |
}, {}); | |
} | |
function propValues(props) { | |
const propKeys = Object.keys(props); | |
return propKeys.reduce((memo, k) => { | |
memo[k] = props[k].value; | |
return memo; | |
}, {}); | |
} | |
function initializeProps(element, propDefinition) { | |
const props = cloneProps(propDefinition), | |
propKeys = Object.keys(propDefinition); | |
propKeys.forEach(key => { | |
const prop = props[key], | |
attr = element.getAttribute(prop.attribute), | |
value = element[key]; | |
if (attr) prop.value = prop.parse ? parseAttributeValue(attr) : attr; | |
if (value != null) prop.value = Array.isArray(value) ? value.slice(0) : value; | |
prop.reflect && reflect(element, prop.attribute, prop.value); | |
Object.defineProperty(element, key, { | |
get() { | |
return prop.value; | |
}, | |
set(val) { | |
const oldValue = prop.value; | |
prop.value = val; | |
prop.reflect && reflect(this, prop.attribute, prop.value); | |
for (let i = 0, l = this.__propertyChangedCallbacks.length; i < l; i++) { | |
this.__propertyChangedCallbacks[i](key, val, oldValue); | |
} | |
}, | |
enumerable: true, | |
configurable: true | |
}); | |
}); | |
return props; | |
} | |
function parseAttributeValue(value) { | |
if (!value) return; | |
try { | |
return JSON.parse(value); | |
} catch (err) { | |
return value; | |
} | |
} | |
function reflect(node, attribute, value) { | |
if (value == null || value === false) return node.removeAttribute(attribute); | |
let reflect = JSON.stringify(value); | |
node.__updating[attribute] = true; | |
if (reflect === "true") reflect = ""; | |
node.setAttribute(attribute, reflect); | |
Promise.resolve().then(() => delete node.__updating[attribute]); | |
} | |
function toAttribute(propName) { | |
return propName.replace(/\.?([A-Z]+)/g, (x, y) => "-" + y.toLowerCase()).replace("_", "-").replace(/^-/, ""); | |
} | |
function isObject$4(obj) { | |
return obj != null && (typeof obj === "object" || typeof obj === "function"); | |
} | |
function isFunction$3(val) { | |
return Object.prototype.toString.call(val) === "[object Function]"; | |
} | |
function isConstructor(f) { | |
return typeof f === "function" && f.toString().indexOf("class") === 0; | |
} | |
let currentElement; | |
function createElementType(BaseElement, propDefinition) { | |
const propKeys = Object.keys(propDefinition); | |
return class CustomElement extends BaseElement { | |
static get observedAttributes() { | |
return propKeys.map(k => propDefinition[k].attribute); | |
} | |
constructor() { | |
super(); | |
this.__initialized = false; | |
this.__released = false; | |
this.__releaseCallbacks = []; | |
this.__propertyChangedCallbacks = []; | |
this.__updating = {}; | |
this.props = {}; | |
} | |
connectedCallback() { | |
if (this.__initialized) return; | |
this.__releaseCallbacks = []; | |
this.__propertyChangedCallbacks = []; | |
this.__updating = {}; | |
this.props = initializeProps(this, propDefinition); | |
const props = propValues(this.props), | |
ComponentType = this.Component, | |
outerElement = currentElement; | |
try { | |
currentElement = this; | |
this.__initialized = true; | |
if (isConstructor(ComponentType)) new ComponentType(props, { | |
element: this | |
});else ComponentType(props, { | |
element: this | |
}); | |
} finally { | |
currentElement = outerElement; | |
} | |
} | |
async disconnectedCallback() { | |
// prevent premature releasing when element is only temporarely removed from DOM | |
await Promise.resolve(); | |
if (this.isConnected) return; | |
this.__propertyChangedCallbacks.length = 0; | |
let callback = null; | |
while (callback = this.__releaseCallbacks.pop()) callback(this); | |
delete this.__initialized; | |
this.__released = true; | |
} | |
attributeChangedCallback(name, oldVal, newVal) { | |
if (!this.__initialized) return; | |
if (this.__updating[name]) return; | |
name = this.lookupProp(name); | |
if (name in propDefinition) { | |
if (newVal == null && !this[name]) return; | |
this[name] = propDefinition[name].parse ? parseAttributeValue(newVal) : newVal; | |
} | |
} | |
lookupProp(attrName) { | |
if (!propDefinition) return; | |
return propKeys.find(k => attrName === k || attrName === propDefinition[k].attribute); | |
} | |
get renderRoot() { | |
return this.shadowRoot || this.attachShadow({ | |
mode: "open" | |
}); | |
} | |
addReleaseCallback(fn) { | |
this.__releaseCallbacks.push(fn); | |
} | |
addPropertyChangedCallback(fn) { | |
this.__propertyChangedCallbacks.push(fn); | |
} | |
}; | |
} | |
function register(tag, props = {}, options = {}) { | |
const { | |
BaseElement = HTMLElement, | |
extension | |
} = options; | |
return ComponentType => { | |
if (!tag) throw new Error("tag is required to register a Component"); | |
let ElementType = customElements.get(tag); | |
if (ElementType) { | |
// Consider disabling this in a production mode | |
ElementType.prototype.Component = ComponentType; | |
return ElementType; | |
} | |
ElementType = createElementType(BaseElement, normalizePropDefs(props)); | |
ElementType.prototype.Component = ComponentType; | |
ElementType.prototype.registeredTag = tag; | |
customElements.define(tag, ElementType, extension); | |
return ElementType; | |
}; | |
} | |
const sharedConfig = { | |
context: undefined, | |
registry: undefined | |
}; | |
const equalFn = (a, b) => a === b; | |
const $PROXY = Symbol("solid-proxy"); | |
const $TRACK = Symbol("solid-track"); | |
const $DEVCOMP = Symbol("solid-dev-component"); | |
const signalOptions = { | |
equals: equalFn | |
}; | |
let runEffects = runQueue; | |
const STALE = 1; | |
const PENDING = 2; | |
const UNOWNED = { | |
owned: null, | |
cleanups: null, | |
context: null, | |
owner: null | |
}; | |
var Owner = null; | |
let Transition = null; | |
let Listener = null; | |
let Updates = null; | |
let Effects = null; | |
let ExecCount = 0; | |
function createRoot(fn, detachedOwner) { | |
const listener = Listener, | |
owner = Owner, | |
unowned = fn.length === 0, | |
root = unowned ? UNOWNED : { | |
owned: null, | |
cleanups: null, | |
context: null, | |
owner: detachedOwner === undefined ? owner : detachedOwner | |
}, | |
updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root))); | |
Owner = root; | |
Listener = null; | |
try { | |
return runUpdates(updateFn, true); | |
} finally { | |
Listener = listener; | |
Owner = owner; | |
} | |
} | |
function createSignal(value, options) { | |
options = options ? Object.assign({}, signalOptions, options) : signalOptions; | |
const s = { | |
value, | |
observers: null, | |
observerSlots: null, | |
comparator: options.equals || undefined | |
}; | |
const setter = value => { | |
if (typeof value === "function") { | |
value = value(s.value); | |
} | |
return writeSignal(s, value); | |
}; | |
return [readSignal.bind(s), setter]; | |
} | |
function createRenderEffect(fn, value, options) { | |
const c = createComputation(fn, value, false, STALE); | |
updateComputation(c); | |
} | |
function createEffect(fn, value, options) { | |
runEffects = runUserEffects; | |
const c = createComputation(fn, value, false, STALE); | |
if (!options || !options.render) c.user = true; | |
Effects ? Effects.push(c) : updateComputation(c); | |
} | |
function createMemo(fn, value, options) { | |
options = options ? Object.assign({}, signalOptions, options) : signalOptions; | |
const c = createComputation(fn, value, true, 0); | |
c.observers = null; | |
c.observerSlots = null; | |
c.comparator = options.equals || undefined; | |
updateComputation(c); | |
return readSignal.bind(c); | |
} | |
function batch(fn) { | |
return runUpdates(fn, false); | |
} | |
function untrack(fn) { | |
if (Listener === null) return fn(); | |
const listener = Listener; | |
Listener = null; | |
try { | |
return fn(); | |
} finally { | |
Listener = listener; | |
} | |
} | |
function onMount(fn) { | |
createEffect(() => untrack(fn)); | |
} | |
function onCleanup(fn) { | |
if (Owner === null) ;else if (Owner.cleanups === null) Owner.cleanups = [fn];else Owner.cleanups.push(fn); | |
return fn; | |
} | |
function getListener() { | |
return Listener; | |
} | |
function getOwner() { | |
return Owner; | |
} | |
function runWithOwner(o, fn) { | |
const prev = Owner; | |
const prevListener = Listener; | |
Owner = o; | |
Listener = null; | |
try { | |
return runUpdates(fn, true); | |
} catch (err) { | |
handleError(err); | |
} finally { | |
Owner = prev; | |
Listener = prevListener; | |
} | |
} | |
function createContext$1(defaultValue, options) { | |
const id = Symbol("context"); | |
return { | |
id, | |
Provider: createProvider(id), | |
defaultValue | |
}; | |
} | |
function useContext(context) { | |
let ctx; | |
return (ctx = lookup(Owner, context.id)) !== undefined ? ctx : context.defaultValue; | |
} | |
function children(fn) { | |
const children = createMemo(fn); | |
const memo = createMemo(() => resolveChildren(children())); | |
memo.toArray = () => { | |
const c = memo(); | |
return Array.isArray(c) ? c : c != null ? [c] : []; | |
}; | |
return memo; | |
} | |
function readSignal() { | |
if (this.sources && (this.state)) { | |
if ((this.state) === STALE) updateComputation(this);else { | |
const updates = Updates; | |
Updates = null; | |
runUpdates(() => lookUpstream(this), false); | |
Updates = updates; | |
} | |
} | |
if (Listener) { | |
const sSlot = this.observers ? this.observers.length : 0; | |
if (!Listener.sources) { | |
Listener.sources = [this]; | |
Listener.sourceSlots = [sSlot]; | |
} else { | |
Listener.sources.push(this); | |
Listener.sourceSlots.push(sSlot); | |
} | |
if (!this.observers) { | |
this.observers = [Listener]; | |
this.observerSlots = [Listener.sources.length - 1]; | |
} else { | |
this.observers.push(Listener); | |
this.observerSlots.push(Listener.sources.length - 1); | |
} | |
} | |
return this.value; | |
} | |
function writeSignal(node, value, isComp) { | |
let current = node.value; | |
if (!node.comparator || !node.comparator(current, value)) { | |
node.value = value; | |
if (node.observers && node.observers.length) { | |
runUpdates(() => { | |
for (let i = 0; i < node.observers.length; i += 1) { | |
const o = node.observers[i]; | |
const TransitionRunning = Transition && Transition.running; | |
if (TransitionRunning && Transition.disposed.has(o)) ; | |
if (TransitionRunning ? !o.tState : !o.state) { | |
if (o.pure) Updates.push(o);else Effects.push(o); | |
if (o.observers) markDownstream(o); | |
} | |
if (!TransitionRunning) o.state = STALE; | |
} | |
if (Updates.length > 10e5) { | |
Updates = []; | |
if (false) ; | |
throw new Error(); | |
} | |
}, false); | |
} | |
} | |
return value; | |
} | |
function updateComputation(node) { | |
if (!node.fn) return; | |
cleanNode(node); | |
const owner = Owner, | |
listener = Listener, | |
time = ExecCount; | |
Listener = Owner = node; | |
runComputation(node, node.value, time); | |
Listener = listener; | |
Owner = owner; | |
} | |
function runComputation(node, value, time) { | |
let nextValue; | |
try { | |
nextValue = node.fn(value); | |
} catch (err) { | |
if (node.pure) { | |
{ | |
node.state = STALE; | |
node.owned && node.owned.forEach(cleanNode); | |
node.owned = null; | |
} | |
} | |
node.updatedAt = time + 1; | |
return handleError(err); | |
} | |
if (!node.updatedAt || node.updatedAt <= time) { | |
if (node.updatedAt != null && "observers" in node) { | |
writeSignal(node, nextValue); | |
} else node.value = nextValue; | |
node.updatedAt = time; | |
} | |
} | |
function createComputation(fn, init, pure, state = STALE, options) { | |
const c = { | |
fn, | |
state: state, | |
updatedAt: null, | |
owned: null, | |
sources: null, | |
sourceSlots: null, | |
cleanups: null, | |
value: init, | |
owner: Owner, | |
context: null, | |
pure | |
}; | |
if (Owner === null) ;else if (Owner !== UNOWNED) { | |
{ | |
if (!Owner.owned) Owner.owned = [c];else Owner.owned.push(c); | |
} | |
} | |
return c; | |
} | |
function runTop(node) { | |
if ((node.state) === 0) return; | |
if ((node.state) === PENDING) return lookUpstream(node); | |
if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node); | |
const ancestors = [node]; | |
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) { | |
if (node.state) ancestors.push(node); | |
} | |
for (let i = ancestors.length - 1; i >= 0; i--) { | |
node = ancestors[i]; | |
if ((node.state) === STALE) { | |
updateComputation(node); | |
} else if ((node.state) === PENDING) { | |
const updates = Updates; | |
Updates = null; | |
runUpdates(() => lookUpstream(node, ancestors[0]), false); | |
Updates = updates; | |
} | |
} | |
} | |
function runUpdates(fn, init) { | |
if (Updates) return fn(); | |
let wait = false; | |
if (!init) Updates = []; | |
if (Effects) wait = true;else Effects = []; | |
ExecCount++; | |
try { | |
const res = fn(); | |
completeUpdates(wait); | |
return res; | |
} catch (err) { | |
if (!wait) Effects = null; | |
Updates = null; | |
handleError(err); | |
} | |
} | |
function completeUpdates(wait) { | |
if (Updates) { | |
runQueue(Updates); | |
Updates = null; | |
} | |
if (wait) return; | |
const e = Effects; | |
Effects = null; | |
if (e.length) runUpdates(() => runEffects(e), false); | |
} | |
function runQueue(queue) { | |
for (let i = 0; i < queue.length; i++) runTop(queue[i]); | |
} | |
function runUserEffects(queue) { | |
let i, | |
userLength = 0; | |
for (i = 0; i < queue.length; i++) { | |
const e = queue[i]; | |
if (!e.user) runTop(e);else queue[userLength++] = e; | |
} | |
for (i = 0; i < userLength; i++) runTop(queue[i]); | |
} | |
function lookUpstream(node, ignore) { | |
node.state = 0; | |
for (let i = 0; i < node.sources.length; i += 1) { | |
const source = node.sources[i]; | |
if (source.sources) { | |
const state = source.state; | |
if (state === STALE) { | |
if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source); | |
} else if (state === PENDING) lookUpstream(source, ignore); | |
} | |
} | |
} | |
function markDownstream(node) { | |
for (let i = 0; i < node.observers.length; i += 1) { | |
const o = node.observers[i]; | |
if (!o.state) { | |
o.state = PENDING; | |
if (o.pure) Updates.push(o);else Effects.push(o); | |
o.observers && markDownstream(o); | |
} | |
} | |
} | |
function cleanNode(node) { | |
let i; | |
if (node.sources) { | |
while (node.sources.length) { | |
const source = node.sources.pop(), | |
index = node.sourceSlots.pop(), | |
obs = source.observers; | |
if (obs && obs.length) { | |
const n = obs.pop(), | |
s = source.observerSlots.pop(); | |
if (index < obs.length) { | |
n.sourceSlots[s] = index; | |
obs[index] = n; | |
source.observerSlots[index] = s; | |
} | |
} | |
} | |
} | |
if (node.owned) { | |
for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]); | |
node.owned = null; | |
} | |
if (node.cleanups) { | |
for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i](); | |
node.cleanups = null; | |
} | |
node.state = 0; | |
node.context = null; | |
} | |
function castError(err) { | |
if (err instanceof Error) return err; | |
return new Error(typeof err === "string" ? err : "Unknown error", { | |
cause: err | |
}); | |
} | |
function handleError(err, owner = Owner) { | |
const error = castError(err); | |
throw error; | |
} | |
function lookup(owner, key) { | |
return owner ? owner.context && owner.context[key] !== undefined ? owner.context[key] : lookup(owner.owner, key) : undefined; | |
} | |
function resolveChildren(children) { | |
if (typeof children === "function" && !children.length) return resolveChildren(children()); | |
if (Array.isArray(children)) { | |
const results = []; | |
for (let i = 0; i < children.length; i++) { | |
const result = resolveChildren(children[i]); | |
Array.isArray(result) ? results.push.apply(results, result) : results.push(result); | |
} | |
return results; | |
} | |
return children; | |
} | |
function createProvider(id, options) { | |
return function provider(props) { | |
let res; | |
createRenderEffect(() => res = untrack(() => { | |
Owner.context = { | |
[id]: props.value | |
}; | |
return children(() => props.children); | |
}), undefined); | |
return res; | |
}; | |
} | |
const FALLBACK = Symbol("fallback"); | |
function dispose(d) { | |
for (let i = 0; i < d.length; i++) d[i](); | |
} | |
function mapArray(list, mapFn, options = {}) { | |
let items = [], | |
mapped = [], | |
disposers = [], | |
len = 0, | |
indexes = mapFn.length > 1 ? [] : null; | |
onCleanup(() => dispose(disposers)); | |
return () => { | |
let newItems = list() || [], | |
i, | |
j; | |
newItems[$TRACK]; | |
return untrack(() => { | |
let newLen = newItems.length, | |
newIndices, | |
newIndicesNext, | |
temp, | |
tempdisposers, | |
tempIndexes, | |
start, | |
end, | |
newEnd, | |
item; | |
if (newLen === 0) { | |
if (len !== 0) { | |
dispose(disposers); | |
disposers = []; | |
items = []; | |
mapped = []; | |
len = 0; | |
indexes && (indexes = []); | |
} | |
if (options.fallback) { | |
items = [FALLBACK]; | |
mapped[0] = createRoot(disposer => { | |
disposers[0] = disposer; | |
return options.fallback(); | |
}); | |
len = 1; | |
} | |
} | |
else if (len === 0) { | |
mapped = new Array(newLen); | |
for (j = 0; j < newLen; j++) { | |
items[j] = newItems[j]; | |
mapped[j] = createRoot(mapper); | |
} | |
len = newLen; | |
} else { | |
temp = new Array(newLen); | |
tempdisposers = new Array(newLen); | |
indexes && (tempIndexes = new Array(newLen)); | |
for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++); | |
for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) { | |
temp[newEnd] = mapped[end]; | |
tempdisposers[newEnd] = disposers[end]; | |
indexes && (tempIndexes[newEnd] = indexes[end]); | |
} | |
newIndices = new Map(); | |
newIndicesNext = new Array(newEnd + 1); | |
for (j = newEnd; j >= start; j--) { | |
item = newItems[j]; | |
i = newIndices.get(item); | |
newIndicesNext[j] = i === undefined ? -1 : i; | |
newIndices.set(item, j); | |
} | |
for (i = start; i <= end; i++) { | |
item = items[i]; | |
j = newIndices.get(item); | |
if (j !== undefined && j !== -1) { | |
temp[j] = mapped[i]; | |
tempdisposers[j] = disposers[i]; | |
indexes && (tempIndexes[j] = indexes[i]); | |
j = newIndicesNext[j]; | |
newIndices.set(item, j); | |
} else disposers[i](); | |
} | |
for (j = start; j < newLen; j++) { | |
if (j in temp) { | |
mapped[j] = temp[j]; | |
disposers[j] = tempdisposers[j]; | |
if (indexes) { | |
indexes[j] = tempIndexes[j]; | |
indexes[j](j); | |
} | |
} else mapped[j] = createRoot(mapper); | |
} | |
mapped = mapped.slice(0, len = newLen); | |
items = newItems.slice(0); | |
} | |
return mapped; | |
}); | |
function mapper(disposer) { | |
disposers[j] = disposer; | |
if (indexes) { | |
const [s, set] = createSignal(j); | |
indexes[j] = set; | |
return mapFn(newItems[j], s); | |
} | |
return mapFn(newItems[j]); | |
} | |
}; | |
} | |
function createComponent(Comp, props) { | |
return untrack(() => Comp(props || {})); | |
} | |
function trueFn() { | |
return true; | |
} | |
const propTraps = { | |
get(_, property, receiver) { | |
if (property === $PROXY) return receiver; | |
return _.get(property); | |
}, | |
has(_, property) { | |
if (property === $PROXY) return true; | |
return _.has(property); | |
}, | |
set: trueFn, | |
deleteProperty: trueFn, | |
getOwnPropertyDescriptor(_, property) { | |
return { | |
configurable: true, | |
enumerable: true, | |
get() { | |
return _.get(property); | |
}, | |
set: trueFn, | |
deleteProperty: trueFn | |
}; | |
}, | |
ownKeys(_) { | |
return _.keys(); | |
} | |
}; | |
function resolveSource(s) { | |
return !(s = typeof s === "function" ? s() : s) ? {} : s; | |
} | |
function resolveSources() { | |
for (let i = 0, length = this.length; i < length; ++i) { | |
const v = this[i](); | |
if (v !== undefined) return v; | |
} | |
} | |
function mergeProps$2(...sources) { | |
let proxy = false; | |
for (let i = 0; i < sources.length; i++) { | |
const s = sources[i]; | |
proxy = proxy || !!s && $PROXY in s; | |
sources[i] = typeof s === "function" ? (proxy = true, createMemo(s)) : s; | |
} | |
if (proxy) { | |
return new Proxy({ | |
get(property) { | |
for (let i = sources.length - 1; i >= 0; i--) { | |
const v = resolveSource(sources[i])[property]; | |
if (v !== undefined) return v; | |
} | |
}, | |
has(property) { | |
for (let i = sources.length - 1; i >= 0; i--) { | |
if (property in resolveSource(sources[i])) return true; | |
} | |
return false; | |
}, | |
keys() { | |
const keys = []; | |
for (let i = 0; i < sources.length; i++) keys.push(...Object.keys(resolveSource(sources[i]))); | |
return [...new Set(keys)]; | |
} | |
}, propTraps); | |
} | |
const target = {}; | |
const sourcesMap = {}; | |
const defined = new Set(); | |
for (let i = sources.length - 1; i >= 0; i--) { | |
const source = sources[i]; | |
if (!source) continue; | |
const sourceKeys = Object.getOwnPropertyNames(source); | |
for (let i = 0, length = sourceKeys.length; i < length; i++) { | |
const key = sourceKeys[i]; | |
if (key === "__proto__" || key === "constructor") continue; | |
const desc = Object.getOwnPropertyDescriptor(source, key); | |
if (!defined.has(key)) { | |
if (desc.get) { | |
defined.add(key); | |
Object.defineProperty(target, key, { | |
enumerable: true, | |
configurable: true, | |
get: resolveSources.bind(sourcesMap[key] = [desc.get.bind(source)]) | |
}); | |
} else { | |
if (desc.value !== undefined) defined.add(key); | |
target[key] = desc.value; | |
} | |
} else { | |
const sources = sourcesMap[key]; | |
if (sources) { | |
if (desc.get) { | |
sources.push(desc.get.bind(source)); | |
} else if (desc.value !== undefined) { | |
sources.push(() => desc.value); | |
} | |
} else if (target[key] === undefined) target[key] = desc.value; | |
} | |
} | |
} | |
return target; | |
} | |
function splitProps(props, ...keys) { | |
if ($PROXY in props) { | |
const blocked = new Set(keys.length > 1 ? keys.flat() : keys[0]); | |
const res = keys.map(k => { | |
return new Proxy({ | |
get(property) { | |
return k.includes(property) ? props[property] : undefined; | |
}, | |
has(property) { | |
return k.includes(property) && property in props; | |
}, | |
keys() { | |
return k.filter(property => property in props); | |
} | |
}, propTraps); | |
}); | |
res.push(new Proxy({ | |
get(property) { | |
return blocked.has(property) ? undefined : props[property]; | |
}, | |
has(property) { | |
return blocked.has(property) ? false : property in props; | |
}, | |
keys() { | |
return Object.keys(props).filter(k => !blocked.has(k)); | |
} | |
}, propTraps)); | |
return res; | |
} | |
const otherObject = {}; | |
const objects = keys.map(() => ({})); | |
for (const propName of Object.getOwnPropertyNames(props)) { | |
const desc = Object.getOwnPropertyDescriptor(props, propName); | |
const isDefaultDesc = !desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable; | |
let blocked = false; | |
let objectIndex = 0; | |
for (const k of keys) { | |
if (k.includes(propName)) { | |
blocked = true; | |
isDefaultDesc ? objects[objectIndex][propName] = desc.value : Object.defineProperty(objects[objectIndex], propName, desc); | |
} | |
++objectIndex; | |
} | |
if (!blocked) { | |
isDefaultDesc ? otherObject[propName] = desc.value : Object.defineProperty(otherObject, propName, desc); | |
} | |
} | |
return [...objects, otherObject]; | |
} | |
let counter = 0; | |
function createUniqueId() { | |
const ctx = sharedConfig.context; | |
return ctx ? `${ctx.id}${ctx.count++}` : `cl-${counter++}`; | |
} | |
const narrowedError = name => `Stale read from <${name}>.`; | |
function For(props) { | |
const fallback = "fallback" in props && { | |
fallback: () => props.fallback | |
}; | |
return createMemo(mapArray(() => props.each, props.children, fallback || undefined)); | |
} | |
function Show(props) { | |
const keyed = props.keyed; | |
const condition = createMemo(() => props.when, undefined, { | |
equals: (a, b) => keyed ? a === b : !a === !b | |
}); | |
return createMemo(() => { | |
const c = condition(); | |
if (c) { | |
const child = props.children; | |
const fn = typeof child === "function" && child.length > 0; | |
return fn ? untrack(() => child(keyed ? c : () => { | |
if (!untrack(condition)) throw narrowedError("Show"); | |
return props.when; | |
})) : child; | |
} | |
return props.fallback; | |
}, undefined, undefined); | |
} | |
function Switch(props) { | |
let keyed = false; | |
const equals = (a, b) => a[0] === b[0] && (keyed ? a[1] === b[1] : !a[1] === !b[1]) && a[2] === b[2]; | |
const conditions = children(() => props.children), | |
evalConditions = createMemo(() => { | |
let conds = conditions(); | |
if (!Array.isArray(conds)) conds = [conds]; | |
for (let i = 0; i < conds.length; i++) { | |
const c = conds[i].when; | |
if (c) { | |
keyed = !!conds[i].keyed; | |
return [i, c, conds[i]]; | |
} | |
} | |
return [-1]; | |
}, undefined, { | |
equals | |
}); | |
return createMemo(() => { | |
const [index, when, cond] = evalConditions(); | |
if (index < 0) return props.fallback; | |
const c = cond.children; | |
const fn = typeof c === "function" && c.length > 0; | |
return fn ? untrack(() => c(keyed ? when : () => { | |
if (untrack(evalConditions)[0] !== index) throw narrowedError("Match"); | |
return cond.when; | |
})) : c; | |
}, undefined, undefined); | |
} | |
function Match(props) { | |
return props; | |
} | |
const booleans = ["allowfullscreen", "async", "autofocus", "autoplay", "checked", "controls", "default", "disabled", "formnovalidate", "hidden", "indeterminate", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "seamless", "selected"]; | |
const Properties = /*#__PURE__*/new Set(["className", "value", "readOnly", "formNoValidate", "isMap", "noModule", "playsInline", ...booleans]); | |
const ChildProperties = /*#__PURE__*/new Set(["innerHTML", "textContent", "innerText", "children"]); | |
const Aliases = /*#__PURE__*/Object.assign(Object.create(null), { | |
className: "class", | |
htmlFor: "for" | |
}); | |
const PropAliases = /*#__PURE__*/Object.assign(Object.create(null), { | |
class: "className", | |
formnovalidate: { | |
$: "formNoValidate", | |
BUTTON: 1, | |
INPUT: 1 | |
}, | |
ismap: { | |
$: "isMap", | |
IMG: 1 | |
}, | |
nomodule: { | |
$: "noModule", | |
SCRIPT: 1 | |
}, | |
playsinline: { | |
$: "playsInline", | |
VIDEO: 1 | |
}, | |
readonly: { | |
$: "readOnly", | |
INPUT: 1, | |
TEXTAREA: 1 | |
} | |
}); | |
function getPropAlias(prop, tagName) { | |
const a = PropAliases[prop]; | |
return typeof a === "object" ? a[tagName] ? a["$"] : undefined : a; | |
} | |
const DelegatedEvents = /*#__PURE__*/new Set(["beforeinput", "click", "dblclick", "contextmenu", "focusin", "focusout", "input", "keydown", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "pointerdown", "pointermove", "pointerout", "pointerover", "pointerup", "touchend", "touchmove", "touchstart"]); | |
const SVGElements = /*#__PURE__*/new Set([ | |
"altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hkern", "image", "line", "linearGradient", "marker", "mask", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", | |
"set", "stop", | |
"svg", "switch", "symbol", "text", "textPath", | |
"tref", "tspan", "use", "view", "vkern"]); | |
const SVGNamespace = { | |
xlink: "http://www.w3.org/1999/xlink", | |
xml: "http://www.w3.org/XML/1998/namespace" | |
}; | |
function reconcileArrays(parentNode, a, b) { | |
let bLength = b.length, | |
aEnd = a.length, | |
bEnd = bLength, | |
aStart = 0, | |
bStart = 0, | |
after = a[aEnd - 1].nextSibling, | |
map = null; | |
while (aStart < aEnd || bStart < bEnd) { | |
if (a[aStart] === b[bStart]) { | |
aStart++; | |
bStart++; | |
continue; | |
} | |
while (a[aEnd - 1] === b[bEnd - 1]) { | |
aEnd--; | |
bEnd--; | |
} | |
if (aEnd === aStart) { | |
const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after; | |
while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node); | |
} else if (bEnd === bStart) { | |
while (aStart < aEnd) { | |
if (!map || !map.has(a[aStart])) a[aStart].remove(); | |
aStart++; | |
} | |
} else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) { | |
const node = a[--aEnd].nextSibling; | |
parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling); | |
parentNode.insertBefore(b[--bEnd], node); | |
a[aEnd] = b[bEnd]; | |
} else { | |
if (!map) { | |
map = new Map(); | |
let i = bStart; | |
while (i < bEnd) map.set(b[i], i++); | |
} | |
const index = map.get(a[aStart]); | |
if (index != null) { | |
if (bStart < index && index < bEnd) { | |
let i = aStart, | |
sequence = 1, | |
t; | |
while (++i < aEnd && i < bEnd) { | |
if ((t = map.get(a[i])) == null || t !== index + sequence) break; | |
sequence++; | |
} | |
if (sequence > index - bStart) { | |
const node = a[aStart]; | |
while (bStart < index) parentNode.insertBefore(b[bStart++], node); | |
} else parentNode.replaceChild(b[bStart++], a[aStart++]); | |
} else aStart++; | |
} else a[aStart++].remove(); | |
} | |
} | |
} | |
const $$EVENTS = "_$DX_DELEGATE"; | |
function template(html, isCE, isSVG) { | |
let node; | |
const create = () => { | |
const t = document.createElement("template"); | |
t.innerHTML = html; | |
return isSVG ? t.content.firstChild.firstChild : t.content.firstChild; | |
}; | |
const fn = isCE ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true); | |
fn.cloneNode = fn; | |
return fn; | |
} | |
function delegateEvents(eventNames, document = window.document) { | |
const e = document[$$EVENTS] || (document[$$EVENTS] = new Set()); | |
for (let i = 0, l = eventNames.length; i < l; i++) { | |
const name = eventNames[i]; | |
if (!e.has(name)) { | |
e.add(name); | |
document.addEventListener(name, eventHandler); | |
} | |
} | |
} | |
function setAttribute(node, name, value) { | |
if (value == null) node.removeAttribute(name);else node.setAttribute(name, value); | |
} | |
function setAttributeNS(node, namespace, name, value) { | |
if (value == null) node.removeAttributeNS(namespace, name);else node.setAttributeNS(namespace, name, value); | |
} | |
function className(node, value) { | |
if (value == null) node.removeAttribute("class");else node.className = value; | |
} | |
function addEventListener(node, name, handler, delegate) { | |
if (delegate) { | |
if (Array.isArray(handler)) { | |
node[`$$${name}`] = handler[0]; | |
node[`$$${name}Data`] = handler[1]; | |
} else node[`$$${name}`] = handler; | |
} else if (Array.isArray(handler)) { | |
const handlerFn = handler[0]; | |
node.addEventListener(name, handler[0] = e => handlerFn.call(node, handler[1], e)); | |
} else node.addEventListener(name, handler); | |
} | |
function classList(node, value, prev = {}) { | |
const classKeys = Object.keys(value || {}), | |
prevKeys = Object.keys(prev); | |
let i, len; | |
for (i = 0, len = prevKeys.length; i < len; i++) { | |
const key = prevKeys[i]; | |
if (!key || key === "undefined" || value[key]) continue; | |
toggleClassKey(node, key, false); | |
delete prev[key]; | |
} | |
for (i = 0, len = classKeys.length; i < len; i++) { | |
const key = classKeys[i], | |
classValue = !!value[key]; | |
if (!key || key === "undefined" || prev[key] === classValue || !classValue) continue; | |
toggleClassKey(node, key, true); | |
prev[key] = classValue; | |
} | |
return prev; | |
} | |
function style(node, value, prev) { | |
if (!value) return prev ? setAttribute(node, "style") : value; | |
const nodeStyle = node.style; | |
if (typeof value === "string") return nodeStyle.cssText = value; | |
typeof prev === "string" && (nodeStyle.cssText = prev = undefined); | |
prev || (prev = {}); | |
value || (value = {}); | |
let v, s; | |
for (s in prev) { | |
value[s] == null && nodeStyle.removeProperty(s); | |
delete prev[s]; | |
} | |
for (s in value) { | |
v = value[s]; | |
if (v !== prev[s]) { | |
nodeStyle.setProperty(s, v); | |
prev[s] = v; | |
} | |
} | |
return prev; | |
} | |
function spread(node, props = {}, isSVG, skipChildren) { | |
const prevProps = {}; | |
if (!skipChildren) { | |
createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children)); | |
} | |
createRenderEffect(() => props.ref && props.ref(node)); | |
createRenderEffect(() => assign(node, props, isSVG, true, prevProps, true)); | |
return prevProps; | |
} | |
function use(fn, element, arg) { | |
return untrack(() => fn(element, arg)); | |
} | |
function insert(parent, accessor, marker, initial) { | |
if (marker !== undefined && !initial) initial = []; | |
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker); | |
createRenderEffect(current => insertExpression(parent, accessor(), current, marker), initial); | |
} | |
function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = false) { | |
props || (props = {}); | |
for (const prop in prevProps) { | |
if (!(prop in props)) { | |
if (prop === "children") continue; | |
prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef); | |
} | |
} | |
for (const prop in props) { | |
if (prop === "children") { | |
if (!skipChildren) insertExpression(node, props.children); | |
continue; | |
} | |
const value = props[prop]; | |
prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef); | |
} | |
} | |
function toPropertyName(name) { | |
return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase()); | |
} | |
function toggleClassKey(node, key, value) { | |
const classNames = key.trim().split(/\s+/); | |
for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value); | |
} | |
function assignProp(node, prop, value, prev, isSVG, skipRef) { | |
let isCE, isProp, isChildProp, propAlias, forceProp; | |
if (prop === "style") return style(node, value, prev); | |
if (prop === "classList") return classList(node, value, prev); | |
if (value === prev) return prev; | |
if (prop === "ref") { | |
if (!skipRef) value(node); | |
} else if (prop.slice(0, 3) === "on:") { | |
const e = prop.slice(3); | |
prev && node.removeEventListener(e, prev); | |
value && node.addEventListener(e, value); | |
} else if (prop.slice(0, 10) === "oncapture:") { | |
const e = prop.slice(10); | |
prev && node.removeEventListener(e, prev, true); | |
value && node.addEventListener(e, value, true); | |
} else if (prop.slice(0, 2) === "on") { | |
const name = prop.slice(2).toLowerCase(); | |
const delegate = DelegatedEvents.has(name); | |
if (!delegate && prev) { | |
const h = Array.isArray(prev) ? prev[0] : prev; | |
node.removeEventListener(name, h); | |
} | |
if (delegate || value) { | |
addEventListener(node, name, value, delegate); | |
delegate && delegateEvents([name]); | |
} | |
} else if (prop.slice(0, 5) === "attr:") { | |
setAttribute(node, prop.slice(5), value); | |
} else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-"))) { | |
if (forceProp) { | |
prop = prop.slice(5); | |
isProp = true; | |
} | |
if (prop === "class" || prop === "className") className(node, value);else if (isCE && !isProp && !isChildProp) node[toPropertyName(prop)] = value;else node[propAlias || prop] = value; | |
} else { | |
const ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]]; | |
if (ns) setAttributeNS(node, ns, prop, value);else setAttribute(node, Aliases[prop] || prop, value); | |
} | |
return value; | |
} | |
function eventHandler(e) { | |
const key = `$$${e.type}`; | |
let node = e.composedPath && e.composedPath()[0] || e.target; | |
if (e.target !== node) { | |
Object.defineProperty(e, "target", { | |
configurable: true, | |
value: node | |
}); | |
} | |
Object.defineProperty(e, "currentTarget", { | |
configurable: true, | |
get() { | |
return node || document; | |
} | |
}); | |
while (node) { | |
const handler = node[key]; | |
if (handler && !node.disabled) { | |
const data = node[`${key}Data`]; | |
data !== undefined ? handler.call(node, data, e) : handler.call(node, e); | |
if (e.cancelBubble) return; | |
} | |
node = node._$host || node.parentNode || node.host; | |
} | |
} | |
function insertExpression(parent, value, current, marker, unwrapArray) { | |
while (typeof current === "function") current = current(); | |
if (value === current) return current; | |
const t = typeof value, | |
multi = marker !== undefined; | |
parent = multi && current[0] && current[0].parentNode || parent; | |
if (t === "string" || t === "number") { | |
if (t === "number") value = value.toString(); | |
if (multi) { | |
let node = current[0]; | |
if (node && node.nodeType === 3) { | |
node.data = value; | |
} else node = document.createTextNode(value); | |
current = cleanChildren(parent, current, marker, node); | |
} else { | |
if (current !== "" && typeof current === "string") { | |
current = parent.firstChild.data = value; | |
} else current = parent.textContent = value; | |
} | |
} else if (value == null || t === "boolean") { | |
current = cleanChildren(parent, current, marker); | |
} else if (t === "function") { | |
createRenderEffect(() => { | |
let v = value(); | |
while (typeof v === "function") v = v(); | |
current = insertExpression(parent, v, current, marker); | |
}); | |
return () => current; | |
} else if (Array.isArray(value)) { | |
const array = []; | |
const currentArray = current && Array.isArray(current); | |
if (normalizeIncomingArray(array, value, current, unwrapArray)) { | |
createRenderEffect(() => current = insertExpression(parent, array, current, marker, true)); | |
return () => current; | |
} | |
if (array.length === 0) { | |
current = cleanChildren(parent, current, marker); | |
if (multi) return current; | |
} else if (currentArray) { | |
if (current.length === 0) { | |
appendNodes(parent, array, marker); | |
} else reconcileArrays(parent, current, array); | |
} else { | |
current && cleanChildren(parent); | |
appendNodes(parent, array); | |
} | |
current = array; | |
} else if (value.nodeType) { | |
if (Array.isArray(current)) { | |
if (multi) return current = cleanChildren(parent, current, marker, value); | |
cleanChildren(parent, current, null, value); | |
} else if (current == null || current === "" || !parent.firstChild) { | |
parent.appendChild(value); | |
} else parent.replaceChild(value, parent.firstChild); | |
current = value; | |
} else console.warn(`Unrecognized value. Skipped inserting`, value); | |
return current; | |
} | |
function normalizeIncomingArray(normalized, array, current, unwrap) { | |
let dynamic = false; | |
for (let i = 0, len = array.length; i < len; i++) { | |
let item = array[i], | |
prev = current && current[i], | |
t; | |
if (item == null || item === true || item === false) ; else if ((t = typeof item) === "object" && item.nodeType) { | |
normalized.push(item); | |
} else if (Array.isArray(item)) { | |
dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic; | |
} else if (t === "function") { | |
if (unwrap) { | |
while (typeof item === "function") item = item(); | |
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic; | |
} else { | |
normalized.push(item); | |
dynamic = true; | |
} | |
} else { | |
const value = String(item); | |
if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);else normalized.push(document.createTextNode(value)); | |
} | |
} | |
return dynamic; | |
} | |
function appendNodes(parent, array, marker = null) { | |
for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker); | |
} | |
function cleanChildren(parent, current, marker, replacement) { | |
if (marker === undefined) return parent.textContent = ""; | |
const node = replacement || document.createTextNode(""); | |
if (current.length) { | |
let inserted = false; | |
for (let i = current.length - 1; i >= 0; i--) { | |
const el = current[i]; | |
if (node !== el) { | |
const isParent = el.parentNode === parent; | |
if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);else isParent && el.remove(); | |
} else inserted = true; | |
} | |
} else parent.insertBefore(node, marker); | |
return [node]; | |
} | |
const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; | |
function createElement(tagName, isSVG = false) { | |
return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName); | |
} | |
function Portal(props) { | |
const { | |
useShadow | |
} = props, | |
marker = document.createTextNode(""), | |
mount = () => props.mount || document.body, | |
owner = getOwner(); | |
let content; | |
let hydrating = !!sharedConfig.context; | |
createEffect(() => { | |
content || (content = runWithOwner(owner, () => createMemo(() => props.children))); | |
const el = mount(); | |
if (el instanceof HTMLHeadElement) { | |
const [clean, setClean] = createSignal(false); | |
const cleanup = () => setClean(true); | |
createRoot(dispose => insert(el, () => !clean() ? content() : dispose(), null)); | |
onCleanup(cleanup); | |
} else { | |
const container = createElement(props.isSVG ? "g" : "div", props.isSVG), | |
renderRoot = useShadow && container.attachShadow ? container.attachShadow({ | |
mode: "open" | |
}) : container; | |
Object.defineProperty(container, "_$host", { | |
get() { | |
return marker.parentNode; | |
}, | |
configurable: true | |
}); | |
insert(renderRoot, content); | |
el.appendChild(container); | |
props.ref && props.ref(container); | |
onCleanup(() => el.removeChild(container)); | |
} | |
}, undefined, { | |
render: !hydrating | |
}); | |
return marker; | |
} | |
function Dynamic(props) { | |
const [p, others] = splitProps(props, ["component"]); | |
const cached = createMemo(() => p.component); | |
return createMemo(() => { | |
const component = cached(); | |
switch (typeof component) { | |
case "function": | |
Object.assign(component, { | |
[$DEVCOMP]: true | |
}); | |
return untrack(() => component(others)); | |
case "string": | |
const isSvg = SVGElements.has(component); | |
const el = createElement(component, isSvg); | |
spread(el, others, isSvg); | |
return el; | |
} | |
}); | |
} | |
function createProps$1(raw) { | |
const keys = Object.keys(raw); | |
const props = {}; | |
for (let i = 0; i < keys.length; i++) { | |
const [get, set] = createSignal(raw[keys[i]]); | |
Object.defineProperty(props, keys[i], { | |
get, | |
set(v) { | |
set(() => v); | |
} | |
}); | |
} | |
return props; | |
} | |
function lookupContext(el) { | |
if (el.assignedSlot && el.assignedSlot._$owner) | |
return el.assignedSlot._$owner; | |
let next = el.parentNode; | |
while (next && | |
!next._$owner && | |
!(next.assignedSlot && next.assignedSlot._$owner)) | |
next = next.parentNode; | |
return next && next.assignedSlot | |
? next.assignedSlot._$owner | |
: el._$owner; | |
} | |
function withSolid(ComponentType) { | |
return (rawProps, options) => { | |
const { element } = options; | |
return createRoot((dispose) => { | |
const props = createProps$1(rawProps); | |
element.addPropertyChangedCallback((key, val) => (props[key] = val)); | |
element.addReleaseCallback(() => { | |
element.renderRoot.textContent = ""; | |
dispose(); | |
}); | |
const comp = ComponentType(props, options); | |
return insert(element.renderRoot, comp); | |
}, lookupContext(element)); | |
}; | |
} | |
function customElement(tag, props, ComponentType) { | |
if (arguments.length === 2) { | |
ComponentType = props; | |
props = {}; | |
} | |
return register(tag, props)(withSolid(ComponentType)); | |
} | |
const defaultBotProps = { | |
typebot: undefined, | |
onNewInputBlock: undefined, | |
onAnswer: undefined, | |
onEnd: undefined, | |
onInit: undefined, | |
onNewLogs: undefined, | |
isPreview: undefined, | |
startFrom: undefined, | |
prefilledVariables: undefined, | |
apiHost: undefined, | |
resultId: undefined, | |
sessionId: undefined | |
}; | |
const defaultPopupProps = { | |
...defaultBotProps, | |
onClose: undefined, | |
onOpen: undefined, | |
theme: undefined, | |
autoShowDelay: undefined, | |
isOpen: undefined, | |
defaultOpen: undefined | |
}; | |
const defaultBubbleProps = { | |
...defaultBotProps, | |
onClose: undefined, | |
onOpen: undefined, | |
theme: undefined, | |
previewMessage: undefined, | |
onPreviewMessageClick: undefined, | |
autoShowDelay: undefined | |
}; | |
var css_248z$1 = "/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:\"\"}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-right-2{right:-8px}.-top-2{top:-8px}.bottom-5{bottom:20px}.left-0{left:0}.left-5{left:20px}.right-0{right:0}.right-2{right:8px}.right-5{right:20px}.top-0{top:0}.top-2{top:8px}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.m-2{margin:8px}.m-auto{margin:auto}.mx-4{margin-left:16px;margin-right:16px}.my-2{margin-bottom:8px;margin-top:8px}.-mr-1{margin-right:-4px}.-mt-1{margin-top:-4px}.ml-2{margin-left:8px}.mt-1{margin-top:4px}.mt-4{margin-top:16px}.\\!block{display:block!important}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-10{height:40px}.h-2{height:8px}.h-2\\.5{height:10px}.h-3{height:12px}.h-4{height:16px}.h-5{height:20px}.h-6{height:24px}.h-8{height:32px}.h-9{height:36px}.h-\\[56px\\]{height:56px}.h-\\[58px\\]{height:58px}.h-\\[80vh\\]{height:80vh}.h-full{height:100%}.h-screen{height:100vh}.max-h-80{max-height:320px}.max-h-\\[464px\\]{max-height:464px}.min-h-full{min-height:100%}.w-10{width:40px}.w-2{width:8px}.w-3{width:12px}.w-4{width:16px}.w-5{width:20px}.w-6{width:24px}.w-8{width:32px}.w-\\[58px\\]{width:58px}.w-\\[60\\%\\]{width:60%}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\\[250px\\]{min-width:250px}.max-w-\\[256px\\]{max-width:256px}.max-w-\\[350px\\]{max-width:350px}.max-w-\\[90\\%\\]{max-width:90%}.max-w-full{max-width:100%}.max-w-lg{max-width:512px}.max-w-xs{max-width:320px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-rotate-180{--tw-rotate:-180deg}.-rotate-180,.rotate-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-0{--tw-rotate:0deg}.scale-0{--tw-scale-x:0;--tw-scale-y:0}.scale-0,.scale-100{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.animate-fade-in{animation:fade-in .3s ease-out}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:4px}.gap-2{gap:8px}.gap-3{gap:12px}.gap-4{gap:16px}.gap-6{gap:24px}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-y-scroll{overflow-y:scroll}.scroll-smooth{scroll-behavior:smooth}.text-ellipsis{text-overflow:ellipsis}.whitespace-pre-wrap{white-space:pre-wrap}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:8px}.rounded-md{border-radius:6px}.border{border-width:1px}.border-2{border-width:2px}.border-dashed{border-style:dashed}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.bg-\\[rgba\\(0\\2c 0\\2c 0\\2c 0\\.5\\)\\]{background-color:rgba(0,0,0,.5)}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-black\\/20{background-color:rgba(0,0,0,.2)}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-green-400{--tw-bg-opacity:1;background-color:rgb(74 222 128/var(--tw-bg-opacity))}.bg-orange-400{--tw-bg-opacity:1;background-color:rgb(251 146 60/var(--tw-bg-opacity))}.bg-pink-400{--tw-bg-opacity:1;background-color:rgb(244 114 182/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-50{--tw-bg-opacity:0.5}.fill-transparent{fill:transparent}.stroke-2{stroke-width:2}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\\.5{padding:2px}.p-1{padding:4px}.p-2{padding:8px}.p-3{padding:12px}.p-4{padding:16px}.p-\\[1px\\]{padding:1px}.px-2{padding-left:8px;padding-right:8px}.px-3{padding-left:12px;padding-right:12px}.px-4{padding-left:16px;padding-right:16px}.px-8{padding-left:32px;padding-right:32px}.px-\\[15px\\]{padding-left:15px;padding-right:15px}.py-1{padding-bottom:4px;padding-top:4px}.py-2{padding-bottom:8px;padding-top:8px}.py-4{padding-bottom:16px;padding-top:16px}.py-6{padding-bottom:24px;padding-top:24px}.py-\\[7px\\]{padding-bottom:7px;padding-top:7px}.pb-0{padding-bottom:0}.pl-2{padding-left:8px}.pl-4{padding-left:16px}.pr-1{padding-right:4px}.pr-2{padding-right:8px}.pr-4{padding-right:16px}.pt-10{padding-top:40px}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-2xl{font-size:24px;line-height:32px}.text-4xl{font-size:36px;line-height:40px}.text-base{font-size:16px;line-height:24px}.text-sm{font-size:14px;line-height:20px}.text-xl{font-size:20px;line-height:28px}.text-xs{font-size:12px;line-height:16px}.font-normal{font-weight:400}.font-semibold{font-weight:600}.italic{font-style:italic}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.brightness-150{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.brightness-150{--tw-brightness:brightness(1.5)}.brightness-200{--tw-brightness:brightness(2)}.brightness-200,.brightness-95{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.brightness-95{--tw-brightness:brightness(.95)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-200{transition-duration:.2s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.scrollable-container::-webkit-scrollbar{display:none}.scrollable-container{-ms-overflow-style:none;scrollbar-width:none}.text-fade-in{transition:opacity .4s ease-in .2s}.bubble-typing{transition:width .4s ease-out,height .4s ease-out}.bubble1,.bubble2,.bubble3{background-color:var(--typebot-host-bubble-color);opacity:.5}.bubble1,.bubble2{animation:chatBubbles 1s ease-in-out infinite}.bubble2{animation-delay:.3s}.bubble3{animation:chatBubbles 1s ease-in-out infinite;animation-delay:.5s}@keyframes chatBubbles{0%{transform:translateY(2.5)}50%{transform:translateY(-2.5px)}to{transform:translateY(0)}}button,input,textarea{font-weight:300}a{text-decoration:underline}ol,ul{margin-inline-end:0;margin-inline-start:0;padding-inline-start:40px}ol{list-style-type:decimal}ul{list-style-type:disc}li:not(:last-child){margin-bottom:8px}pre{word-wrap:break-word;max-height:100%;max-width:100%;overflow:auto;overflow-wrap:break-word;white-space:pre-wrap}.slate-bold{font-weight:700}.slate-italic{font-style:oblique}.slate-underline{text-decoration:underline}.text-input::-moz-placeholder{color:var(--typebot-input-placeholder-color)!important;opacity:1!important}.text-input::placeholder{color:var(--typebot-input-placeholder-color)!important;opacity:1!important}.typebot-container{background-color:var(--typebot-container-bg-color);background-image:var(--typebot-container-bg-image);background-position:50%;background-size:cover;container-type:inline-size;font-family:var(--typebot-container-font-family),-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\"}.typebot-chat-view{-webkit-backdrop-filter:blur(var(--typebot-chat-container-blur));backdrop-filter:blur(var(--typebot-chat-container-blur));background-color:rgba(var(--typebot-chat-container-bg-rgb),var(--typebot-chat-container-opacity));border-color:rgba(var(--typebot-chat-container-border-rgb),var(--typebot-chat-container-border-opacity));border-width:var(--typebot-chat-container-border-width);box-shadow:var(--typebot-chat-container-box-shadow);color:rgb(var(--typebot-chat-container-color));max-width:var(--typebot-chat-container-max-width);min-height:100%;padding-left:20px;padding-right:20px}@container (min-width: 480px){.typebot-chat-view{border-radius:var(--typebot-chat-container-border-radius);max-height:var(--typebot-chat-container-max-height);min-height:var(--typebot-chat-container-max-height)}}.typebot-button{-webkit-backdrop-filter:blur(var(--typebot-button-blur));backdrop-filter:blur(var(--typebot-button-blur));background-color:rgba(var(--typebot-button-bg-rgb),var(--typebot-button-opacity));border-color:rgba(var(--typebot-button-border-rgb),var(--typebot-button-border-opacity));border-radius:var(--typebot-button-border-radius);border-width:var(--typebot-button-border-width);box-shadow:var(--typebot-button-box-shadow);color:var(--typebot-button-color);transition:all .3s ease}.typebot-selectable{background-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.08));border-color:rgba(var(--typebot-button-border-rgb),calc(var(--selectable-alpha-ratio)*.25));border-radius:var(--typebot-button-border-radius);border-width:var(--typebot-button-border-width);color:rgb(var(--typebot-chat-container-color));transition:all .3s ease}.typebot-selectable:hover{background-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.12));border-color:rgba(var(--typebot-button-border-rgb),calc(var(--selectable-alpha-ratio)*.3))}.typebot-selectable.selected{background-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.18));border-color:rgba(var(--typebot-button-border-rgb),calc(var(--selectable-alpha-ratio)*.35))}.typebot-checkbox{background-color:rgba(var(--typebot-checkbox-bg-rgb));border:1px solid rgba(var(--typebot-button-bg-rgb),var(--typebot-button-opacity));border-radius:var(--typebot-button-border-radius);border-radius:2px;color:var(--typebot-button-color);padding:1px;transition:all .3s ease}.typebot-checkbox.checked{background-color:rgb(var(--typebot-button-bg-rgb))}.typebot-host-bubble{color:var(--typebot-host-bubble-color)}.typebot-host-bubble>.bubble-typing{-webkit-backdrop-filter:blur(var(--typebot-host-bubble-blur));backdrop-filter:blur(var(--typebot-host-bubble-blur));background-color:rgba(var(--typebot-host-bubble-bg-rgb),var(--typebot-host-bubble-opacity));border-color:rgba(var(--typebot-host-bubble-border-rgb),var(--typebot-host-bubble-border-opacity));border-radius:var(--typebot-host-bubble-border-radius);border-width:var(--typebot-host-bubble-border-width);box-shadow:var(--typebot-host-bubble-box-shadow)}.typebot-host-bubble iframe,.typebot-host-bubble img,.typebot-host-bubble video{border-radius:6px}.typebot-guest-bubble{-webkit-backdrop-filter:blur(var(--typebot-guest-bubble-blur));backdrop-filter:blur(var(--typebot-guest-bubble-blur));background-color:rgba(var(--typebot-guest-bubble-bg-rgb),var(--typebot-guest-bubble-opacity));border-color:rgba(var(--typebot-guest-bubble-border-rgb),var(--typebot-guest-bubble-border-opacity));border-width:var(--typebot-guest-bubble-border-width);box-shadow:var(--typebot-guest-bubble-box-shadow);color:var(--typebot-guest-bubble-color)}.typebot-guest-bubble,.typebot-guest-bubble-image-attachment{border-radius:var(--typebot-guest-bubble-border-radius)}.typebot-input{-webkit-backdrop-filter:blur(var(--typebot-input-blur));backdrop-filter:blur(var(--typebot-input-blur));background-color:rgba(var(--typebot-input-bg-rgb),var(--typebot-input-opacity));border-color:rgba(var(--typebot-input-border-rgb),var(--typebot-input-border-opacity));border-radius:var(--typebot-input-border-radius);border-width:var(--typebot-input-border-width);box-shadow:var(--typebot-input-box-shadow);transition:filter .1s ease}.typebot-input,.typebot-input-error-message{color:var(--typebot-input-color)}.typebot-input-form .typebot-button{box-shadow:var(--typebot-input-box-shadow)}.typebot-button>.send-icon{fill:var(--typebot-button-color)}.ping span{background-color:rgb(var(--typebot-button-bg-rgb))}.rating-icon-container svg{stroke:rgb(var(--typebot-button-bg-rgb));fill:var(--typebot-host-bubble-bg-color);height:42px;transition:fill .1s ease-out;width:42px}.rating-icon-container.selected svg{fill:rgb(var(--typebot-button-bg-rgb))}.rating-icon-container:hover svg{filter:brightness(.9)}.rating-icon-container:active svg{filter:brightness(.75)}.upload-progress-bar{border-radius:var(--typebot-input-border-radius)}.total-files-indicator,.upload-progress-bar{background-color:rgb(var(--typebot-button-bg-rgb))}.total-files-indicator{color:var(--typebot-button-color);font-size:10px}.typebot-upload-input{border-radius:var(--typebot-input-border-radius);transition:border-color .1s ease-out}.typebot-upload-input.dragging-over{border-color:rgb(var(--typebot-button-bg-rgb))}.secondary-button{background-color:var(--typebot-host-bubble-bg-color);border-radius:var(--typebot-button-border-radius);color:var(--typebot-host-bubble-color)}.typebot-country-select{border-radius:var(--typebot-button-border-radius);color:var(--typebot-input-color)}.typebot-country-select,.typebot-date-input{background-color:var(--typebot-input-bg-color)}.typebot-date-input{color:var(--typebot-input-color);color-scheme:light}.typebot-date-input,.typebot-popup-blocked-toast{border-radius:var(--typebot-input-border-radius)}.typebot-picture-button{background-color:rgb(var(--typebot-button-bg-rgb));border-radius:var(--typebot-button-border-radius);color:var(--typebot-button-color);transition:all .3s ease;width:236px}.typebot-picture-button>img,.typebot-selectable-picture>img{border-radius:var(--typebot-button-border-radius) var(--typebot-button-border-radius) 0 0;height:100%;max-height:200px;min-width:200px;-o-object-fit:cover;object-fit:cover;width:100%}.typebot-picture-button.has-svg>img,.typebot-selectable-picture.has-svg>img{max-height:128px;-o-object-fit:contain;object-fit:contain;padding:1rem}.typebot-selectable-picture{background-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.08));border:1px solid rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.25));border-radius:var(--typebot-button-border-radius);color:rgb(var(--typebot-chat-container-color));transition:all .3s ease;width:236px}.typebot-selectable-picture:hover{background-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.12));border-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.3))}.typebot-selectable-picture.selected{background-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.18));border-color:rgba(var(--typebot-button-bg-rgb),calc(var(--selectable-alpha-ratio)*.35))}select option{background-color:var(--typebot-input-bg-color);color:var(--typebot-input-color)}.typebot-progress-bar-container{background-color:rgba(var(--typebot-progress-bar-bg-rgb),calc(var(--selectable-alpha-ratio)*.12));bottom:var(--typebot-progress-bar-bottom);height:var(--typebot-progress-bar-height);left:0;position:var(--typebot-progress-bar-position);top:var(--typebot-progress-bar-top);width:100%;z-index:42424242}.typebot-progress-bar-container>.typebot-progress-bar{background-color:var(--typebot-progress-bar-color);height:100%;position:absolute;transition:width .25s ease}@keyframes fadeInFromTop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOutFromTop{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes fadeInFromBottom{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOutFromBottom{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(4px)}}[data-scope=menu][data-part=content]{-webkit-backdrop-filter:blur(var(--typebot-input-blur));backdrop-filter:blur(var(--typebot-input-blur));border-color:rgba(var(--typebot-input-border-rgb),var(--typebot-input-border-opacity));border-width:var(--typebot-input-border-width);box-shadow:var(--typebot-input-box-shadow);color:var(--typebot-input-color)}[data-scope=menu][data-part=content],[data-scope=menu][data-part=item]{background-color:rgba(var(--typebot-input-bg-rgb),var(--typebot-input-opacity));border-radius:var(--typebot-input-border-radius)}[data-scope=menu][data-part=item]{cursor:pointer}[data-scope=menu][data-part=content][data-state=open]{animation:fadeInFromTop .15s ease-out forwards}[data-scope=menu][data-part=content][data-state=closed]{animation:fadeOutFromTop 50ms ease-out forwards}[data-scope=toast][data-part=group]{width:100%}[data-scope=toast][data-part=root]{background-color:rgba(var(--typebot-input-bg-rgb),var(--typebot-input-opacity));border-radius:var(--typebot-chat-container-border-radius);box-shadow:var(--typebot-input-box-shadow);color:var(--typebot-input-color);display:flex;flex-direction:column;gap:4px;max-width:60vw;padding:16px 32px 16px 16px}[data-scope=toast][data-part=title]{font-weight:600}[data-scope=toast][data-part=description]{font-size:14px;line-height:20px}[data-scope=toast][data-part=root][data-state=open]{animation:fadeInFromBottom .15s ease-out forwards}[data-scope=toast][data-part=root][data-state=closed]{animation:fadeOutFromBottom 50ms ease-out forwards}[data-scope=progress][data-part=root]{height:100%;width:100%}[data-scope=progress][data-part=circle]{--size:40px;--thickness:4px;--radius:18px;--circomference:113.09724px}[data-scope=progress][data-part=circle-range]{stroke:#fff;--transition-prop:stroke-dasharray,stroke,stroke-dashoffset;--transition-duration:0.2s;transition-duration:.2s;transition-property:stroke-dasharray,stroke,stroke-dashoffset}[data-scope=progress][data-part=circle-track]{stroke:rgba(0,0,0,.5)}.hover\\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.hover\\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\\:brightness-90:hover{--tw-brightness:brightness(.9)}.hover\\:brightness-90:hover,.hover\\:brightness-95:hover{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\\:brightness-95:hover{--tw-brightness:brightness(.95)}.hover\\:backdrop-brightness-95:hover{--tw-backdrop-brightness:brightness(.95);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.focus\\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.active\\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\\:brightness-75:active{--tw-brightness:brightness(.75)}.active\\:brightness-75:active,.active\\:brightness-90:active{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.active\\:brightness-90:active{--tw-brightness:brightness(.9)}.disabled\\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\\:opacity-50:disabled{opacity:.5}.disabled\\:brightness-100:disabled{--tw-brightness:brightness(1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.group:hover .group-hover\\:opacity-100{opacity:1}.data-\\[state\\=open\\]\\:backdrop-brightness-90[data-state=open]{--tw-backdrop-brightness:brightness(.9);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}@media (min-width:640px){.sm\\:right-5{right:20px}.sm\\:my-8{margin-bottom:32px;margin-top:32px}.sm\\:p-0{padding:0}.sm\\:opacity-0{opacity:0}}"; | |
const sendRequest = async params => { | |
let response; | |
try { | |
const url = typeof params === 'string' ? params : params.url; | |
response = await fetch(url, { | |
method: typeof params === 'string' ? 'GET' : params.method, | |
mode: 'cors', | |
headers: typeof params !== 'string' && isDefined(params.body) ? { | |
'Content-Type': 'application/json' | |
} : undefined, | |
body: typeof params !== 'string' && isDefined(params.body) ? JSON.stringify(params.body) : undefined | |
}); | |
const data = await response.json(); | |
if (!response.ok) throw 'error' in data ? data.error : data; | |
return { | |
data, | |
response | |
}; | |
} catch (e) { | |
console.error(e); | |
return { | |
error: e, | |
response | |
}; | |
} | |
}; | |
const isDefined = value => value !== undefined && value !== null; | |
const isNotDefined = value => value === undefined || value === null; | |
const isEmpty$2 = value => value === undefined || value === null || value === ''; | |
const isNotEmpty = value => value !== undefined && value !== null && value !== ''; | |
const injectCustomHeadCode = customHeadCode => { | |
const headCodes = customHeadCode.split('</noscript>'); | |
headCodes.forEach(headCode => { | |
const [codeToInject, noScriptContentToInject] = headCode.split('<noscript>'); | |
const fragment = document.createRange().createContextualFragment(codeToInject); | |
document.head.append(fragment); | |
if (isNotDefined(noScriptContentToInject)) return; | |
const noScriptElement = document.createElement('noscript'); | |
const noScriptContentFragment = document.createRange().createContextualFragment(noScriptContentToInject); | |
noScriptElement.append(noScriptContentFragment); | |
document.head.append(noScriptElement); | |
}); | |
}; | |
const isSvgSrc = src => src?.startsWith('data:image/svg') || src?.endsWith('.svg'); | |
const hexToRgb = hex => { | |
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; | |
hex = hex.replace(shorthandRegex, (_m, r, g, b) => { | |
return r + r + g + g + b + b; | |
}); | |
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); | |
return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [0, 0, 0]; | |
}; | |
const isLight = hexColor => (([r, g, b]) => (r * 299 + g * 587 + b * 114) / 1000 > 155)(hexToRgb(hexColor)); | |
function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;t<e.length;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);else for(t in e)e[t]&&(n&&(n+=" "),n+=t);return n}function clsx$1(){for(var e,t,f=0,n="";f<arguments.length;)(e=arguments[f++])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n} | |
const _tmpl$$X = /*#__PURE__*/template(`<svg part="button-icon" viewBox="0 0 24 24"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z">`), | |
_tmpl$2$q = /*#__PURE__*/template(`<img part="button-icon" alt="Bubble button icon">`), | |
_tmpl$3$g = /*#__PURE__*/template(`<span part="button-icon">`), | |
_tmpl$4$9 = /*#__PURE__*/template(`<svg part="button-icon" viewBox="0 0 24 24"><path fill-rule="evenodd" clip-rule="evenodd" d="M18.601 8.39897C18.269 8.06702 17.7309 8.06702 17.3989 8.39897L12 13.7979L6.60099 8.39897C6.26904 8.06702 5.73086 8.06702 5.39891 8.39897C5.06696 8.73091 5.06696 9.2691 5.39891 9.60105L11.3989 15.601C11.7309 15.933 12.269 15.933 12.601 15.601L18.601 9.60105C18.9329 9.2691 18.9329 8.73091 18.601 8.39897Z">`), | |
_tmpl$5$6 = /*#__PURE__*/template(`<img part="button-icon" alt="Bubble button close icon">`), | |
_tmpl$6$3 = /*#__PURE__*/template(`<button part="button" aria-label="Open chatbot">`); | |
const defaultButtonColor = '#0042DA'; | |
const defaultDarkIconColor = '#27272A'; | |
const defaultLightIconColor = '#fff'; | |
const isImageSrc = src => src.startsWith('http') || src.startsWith('data:image/svg+xml'); | |
const BubbleButton = props => (() => { | |
const _el$ = _tmpl$6$3(); | |
_el$.$$click = () => props.toggleBot(); | |
_el$.style.setProperty("z-index", "42424242"); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return isNotDefined(props.customIconSrc); | |
}, | |
keyed: true, | |
get children() { | |
const _el$2 = _tmpl$$X(); | |
createRenderEffect(_p$ => { | |
const _v$ = props.iconColor ?? (isLight(props.backgroundColor ?? defaultButtonColor) ? defaultDarkIconColor : defaultLightIconColor), | |
_v$2 = clsx$1('stroke-2 fill-transparent absolute duration-200 transition w-[60%]', props.isBotOpened ? 'scale-0 opacity-0' : 'scale-100 opacity-100'); | |
_v$ !== _p$._v$ && ((_p$._v$ = _v$) != null ? _el$2.style.setProperty("stroke", _v$) : _el$2.style.removeProperty("stroke")); | |
_v$2 !== _p$._v$2 && setAttribute(_el$2, "class", _p$._v$2 = _v$2); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined | |
}); | |
return _el$2; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return createMemo(() => !!props.customIconSrc)() && isImageSrc(props.customIconSrc); | |
}, | |
get children() { | |
const _el$3 = _tmpl$2$q(); | |
createRenderEffect(_p$ => { | |
const _v$3 = props.customIconSrc, | |
_v$4 = clsx$1('duration-200 transition', props.isBotOpened ? 'scale-0 opacity-0' : 'scale-100 opacity-100', isSvgSrc(props.customIconSrc) ? 'w-[60%]' : 'w-full h-full', isSvgSrc(props.customIconSrc) ? '' : 'object-cover rounded-full'); | |
_v$3 !== _p$._v$3 && setAttribute(_el$3, "src", _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && className(_el$3, _p$._v$4 = _v$4); | |
return _p$; | |
}, { | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$3; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return createMemo(() => !!props.customIconSrc)() && !isImageSrc(props.customIconSrc); | |
}, | |
get children() { | |
const _el$4 = _tmpl$3$g(); | |
_el$4.style.setProperty("font-family", "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"); | |
insert(_el$4, () => props.customIconSrc); | |
createRenderEffect(() => className(_el$4, clsx$1('text-4xl duration-200 transition', props.isBotOpened ? 'scale-0 opacity-0' : 'scale-100 opacity-100'))); | |
return _el$4; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return isNotDefined(props.customCloseIconSrc); | |
}, | |
get children() { | |
const _el$5 = _tmpl$4$9(); | |
createRenderEffect(_p$ => { | |
const _v$5 = props.iconColor ?? (isLight(props.backgroundColor ?? defaultButtonColor) ? defaultDarkIconColor : defaultLightIconColor), | |
_v$6 = clsx$1('absolute duration-200 transition w-[60%]', props.isBotOpened ? 'scale-100 rotate-0 opacity-100' : 'scale-0 -rotate-180 opacity-0'); | |
_v$5 !== _p$._v$5 && ((_p$._v$5 = _v$5) != null ? _el$5.style.setProperty("fill", _v$5) : _el$5.style.removeProperty("fill")); | |
_v$6 !== _p$._v$6 && setAttribute(_el$5, "class", _p$._v$6 = _v$6); | |
return _p$; | |
}, { | |
_v$5: undefined, | |
_v$6: undefined | |
}); | |
return _el$5; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return createMemo(() => !!props.customCloseIconSrc)() && isImageSrc(props.customCloseIconSrc); | |
}, | |
get children() { | |
const _el$6 = _tmpl$5$6(); | |
createRenderEffect(_p$ => { | |
const _v$7 = props.customCloseIconSrc, | |
_v$8 = clsx$1('absolute duration-200 transition', props.isBotOpened ? 'scale-100 rotate-0 opacity-100' : 'scale-0 -rotate-180 opacity-0', isSvgSrc(props.customCloseIconSrc) ? 'w-[60%]' : 'w-full h-full', isSvgSrc(props.customCloseIconSrc) ? '' : 'object-cover rounded-full'); | |
_v$7 !== _p$._v$7 && setAttribute(_el$6, "src", _p$._v$7 = _v$7); | |
_v$8 !== _p$._v$8 && className(_el$6, _p$._v$8 = _v$8); | |
return _p$; | |
}, { | |
_v$7: undefined, | |
_v$8: undefined | |
}); | |
return _el$6; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return createMemo(() => !!props.customCloseIconSrc)() && !isImageSrc(props.customCloseIconSrc); | |
}, | |
get children() { | |
const _el$7 = _tmpl$3$g(); | |
_el$7.style.setProperty("font-family", "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"); | |
insert(_el$7, () => props.customCloseIconSrc); | |
createRenderEffect(() => className(_el$7, clsx$1('absolute text-4xl duration-200 transition', props.isBotOpened ? 'scale-100 rotate-0 opacity-100' : 'scale-0 -rotate-180 opacity-0'))); | |
return _el$7; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$9 = clsx$1(`fixed bottom-5 shadow-md rounded-full hover:scale-110 active:scale-95 transition-transform duration-200 flex justify-center items-center animate-fade-in`, props.placement === 'left' ? ' left-5' : ' right-5'), | |
_v$10 = props.backgroundColor ?? defaultButtonColor, | |
_v$11 = props.buttonSize, | |
_v$12 = props.buttonSize; | |
_v$9 !== _p$._v$9 && className(_el$, _p$._v$9 = _v$9); | |
_v$10 !== _p$._v$10 && ((_p$._v$10 = _v$10) != null ? _el$.style.setProperty("background-color", _v$10) : _el$.style.removeProperty("background-color")); | |
_v$11 !== _p$._v$11 && ((_p$._v$11 = _v$11) != null ? _el$.style.setProperty("width", _v$11) : _el$.style.removeProperty("width")); | |
_v$12 !== _p$._v$12 && ((_p$._v$12 = _v$12) != null ? _el$.style.setProperty("height", _v$12) : _el$.style.removeProperty("height")); | |
return _p$; | |
}, { | |
_v$9: undefined, | |
_v$10: undefined, | |
_v$11: undefined, | |
_v$12: undefined | |
}); | |
return _el$; | |
})(); | |
delegateEvents(["click"]); | |
const _tmpl$$W = /*#__PURE__*/template(`<div part="preview-message"><p>`), | |
_tmpl$2$p = /*#__PURE__*/template(`<img class="rounded-full w-8 h-8 object-cover" alt="Bot avatar" elementtiming="Bot avatar" fetchpriority="high">`), | |
_tmpl$3$f = /*#__PURE__*/template(`<button part="preview-message-close-button" aria-label="Close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18">`); | |
const defaultBackgroundColor$1 = '#F7F8FF'; | |
const defaultTextColor = '#303235'; | |
const PreviewMessage = props => { | |
const [isPreviewMessageHovered, setIsPreviewMessageHovered] = createSignal(false); | |
const [touchStartPosition, setTouchStartPosition] = createSignal({ | |
x: 0, | |
y: 0 | |
}); | |
const handleTouchStart = e => { | |
setTouchStartPosition({ | |
x: e.touches[0].clientX, | |
y: e.touches[0].clientY | |
}); | |
}; | |
const handleTouchEnd = e => { | |
const x = e.changedTouches[0].clientX; | |
const y = e.changedTouches[0].clientY; | |
if (Math.abs(x - touchStartPosition().x) > 10 || y - touchStartPosition().y > 10) props.onCloseClick(); | |
setTouchStartPosition({ | |
x: 0, | |
y: 0 | |
}); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$W(), | |
_el$2 = _el$.firstChild; | |
_el$.$$touchend = handleTouchEnd; | |
_el$.$$touchstart = handleTouchStart; | |
_el$.addEventListener("mouseleave", () => setIsPreviewMessageHovered(false)); | |
_el$.addEventListener("mouseenter", () => setIsPreviewMessageHovered(true)); | |
_el$.$$click = () => props.onClick(); | |
_el$.style.setProperty("z-index", "42424242"); | |
insert(_el$, createComponent(CloseButton, { | |
get isHovered() { | |
return isPreviewMessageHovered(); | |
}, | |
get previewMessageTheme() { | |
return props.previewMessageTheme; | |
}, | |
get onClick() { | |
return props.onCloseClick; | |
} | |
}), _el$2); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.avatarUrl; | |
}, | |
keyed: true, | |
children: avatarUrl => (() => { | |
const _el$3 = _tmpl$2$p(); | |
setAttribute(_el$3, "src", avatarUrl); | |
return _el$3; | |
})() | |
}), _el$2); | |
insert(_el$2, () => props.message); | |
createRenderEffect(_p$ => { | |
const _v$ = 'fixed max-w-[256px] rounded-md duration-200 flex items-center gap-4 shadow-md animate-fade-in cursor-pointer hover:shadow-lg p-4' + (props.placement === 'left' ? ' left-5' : ' right-5'), | |
_v$2 = props.previewMessageTheme?.backgroundColor ?? defaultBackgroundColor$1, | |
_v$3 = props.previewMessageTheme?.textColor ?? defaultTextColor, | |
_v$4 = `calc(${props.buttonSize} + 32px)`; | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$.style.setProperty("background-color", _v$2) : _el$.style.removeProperty("background-color")); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$.style.setProperty("color", _v$3) : _el$.style.removeProperty("color")); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$.style.setProperty("bottom", _v$4) : _el$.style.removeProperty("bottom")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
const CloseButton = props => (() => { | |
const _el$4 = _tmpl$3$f(); | |
_el$4.$$click = e => { | |
e.stopPropagation(); | |
return props.onClick(); | |
}; | |
createRenderEffect(_p$ => { | |
const _v$5 = `absolute -top-2 -right-2 rounded-full w-6 h-6 p-1 hover:brightness-95 active:brightness-90 transition-all border ` + (props.isHovered ? 'opacity-100' : 'opacity-0'), | |
_v$6 = props.previewMessageTheme?.closeButtonBackgroundColor ?? defaultBackgroundColor$1, | |
_v$7 = props.previewMessageTheme?.closeButtonIconColor ?? defaultTextColor; | |
_v$5 !== _p$._v$5 && className(_el$4, _p$._v$5 = _v$5); | |
_v$6 !== _p$._v$6 && ((_p$._v$6 = _v$6) != null ? _el$4.style.setProperty("background-color", _v$6) : _el$4.style.removeProperty("background-color")); | |
_v$7 !== _p$._v$7 && ((_p$._v$7 = _v$7) != null ? _el$4.style.setProperty("color", _v$7) : _el$4.style.removeProperty("color")); | |
return _p$; | |
}, { | |
_v$5: undefined, | |
_v$6: undefined, | |
_v$7: undefined | |
}); | |
return _el$4; | |
})(); | |
delegateEvents(["click", "touchstart", "touchend"]); | |
const _tmpl$$V = /*#__PURE__*/template(`<svg viewBox="0 0 800 800" width="16"><rect width="800" height="800" rx="80" fill="#0042DA"></rect><rect x="650" y="293" width="85.4704" height="384.617" rx="20" transform="rotate(90 650 293)" fill="#FF8E20"></rect><path fill-rule="evenodd" clip-rule="evenodd" d="M192.735 378.47C216.337 378.47 235.47 359.337 235.47 335.735C235.47 312.133 216.337 293 192.735 293C169.133 293 150 312.133 150 335.735C150 359.337 169.133 378.47 192.735 378.47Z" fill="#FF8E20"></path><rect x="150" y="506.677" width="85.4704" height="384.617" rx="20" transform="rotate(-90 150 506.677)" fill="white"></rect><path fill-rule="evenodd" clip-rule="evenodd" d="M607.265 421.206C583.663 421.206 564.53 440.34 564.53 463.942C564.53 487.544 583.663 506.677 607.265 506.677C630.867 506.677 650 487.544 650 463.942C650 440.34 630.867 421.206 607.265 421.206Z" fill="white">`); | |
const TypebotLogo = () => { | |
return _tmpl$$V(); | |
}; | |
const _tmpl$$U = /*#__PURE__*/template(`<a href="https://www.typebot.io/?utm_source=litebadge" target="_blank" rel="noopener noreferrer" class="lite-badge" id="lite-badge"><span>Made with Typebot`); | |
const LiteBadge = props => { | |
let liteBadge; | |
let observer; | |
const appendBadgeIfNecessary = mutations => { | |
mutations.forEach(mutation => { | |
mutation.removedNodes.forEach(removedNode => { | |
if ('id' in removedNode && liteBadge && removedNode.id == 'lite-badge') { | |
console.log("Sorry, you can't remove the brand 😅"); | |
props.botContainer?.append(liteBadge); | |
} | |
}); | |
}); | |
}; | |
onMount(() => { | |
if (!document || !props.botContainer) return; | |
observer = new MutationObserver(appendBadgeIfNecessary); | |
observer.observe(props.botContainer, { | |
subtree: false, | |
childList: true | |
}); | |
}); | |
onCleanup(() => { | |
if (observer) observer.disconnect(); | |
}); | |
return (() => { | |
const _el$ = _tmpl$$U(), | |
_el$2 = _el$.firstChild; | |
const _ref$ = liteBadge; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : liteBadge = _el$; | |
insert(_el$, createComponent(TypebotLogo, {}), _el$2); | |
return _el$; | |
})(); | |
}; | |
const getRuntimeVariable = (key, defaultValue) => { | |
if (typeof window !== 'undefined') return window.__ENV ? window.__ENV[key] ?? defaultValue : undefined; | |
if (typeof process === 'undefined') return undefined; | |
return process.env[key] ?? defaultValue; | |
}; | |
const chatApiCloudFallbackHost = 'https://typebot.io'; | |
const guessApiHost = ({ | |
ignoreChatApiUrl | |
} = { | |
ignoreChatApiUrl: false | |
}) => { | |
const chatApiUrl = getRuntimeVariable('NEXT_PUBLIC_CHAT_API_URL'); | |
const newChatApiOnUrls = getRuntimeVariable('NEXT_PUBLIC_USE_EXPERIMENTAL_CHAT_API_ON')?.split(','); | |
if (!ignoreChatApiUrl && chatApiUrl && (!newChatApiOnUrls || newChatApiOnUrls.some(url => url === window.location.href))) { | |
return chatApiUrl; | |
} | |
const viewerUrls = getRuntimeVariable('NEXT_PUBLIC_VIEWER_URL')?.split(','); | |
const matchedUrl = viewerUrls?.find(url => window.location.href.startsWith(url)); | |
return matchedUrl ?? viewerUrls?.[0] ?? chatApiCloudFallbackHost; | |
}; | |
const setPaymentInProgressInStorage = state => { | |
sessionStorage.setItem('typebotPaymentInProgress', JSON.stringify(state)); | |
}; | |
const getPaymentInProgressInStorage = () => sessionStorage.getItem('typebotPaymentInProgress'); | |
const removePaymentInProgressFromStorage = () => { | |
sessionStorage.removeItem('typebotPaymentInProgress'); | |
}; | |
// eslint-lint-disable-next-line @typescript-eslint/naming-convention | |
class HTTPError extends Error { | |
constructor(response, request, options) { | |
const code = (response.status || response.status === 0) ? response.status : ''; | |
const title = response.statusText || ''; | |
const status = `${code} ${title}`.trim(); | |
const reason = status ? `status code ${status}` : 'an unknown error'; | |
super(`Request failed with ${reason}`); | |
Object.defineProperty(this, "response", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
Object.defineProperty(this, "request", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
Object.defineProperty(this, "options", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
this.name = 'HTTPError'; | |
this.response = response; | |
this.request = request; | |
this.options = options; | |
} | |
} | |
class TimeoutError extends Error { | |
constructor(request) { | |
super('Request timed out'); | |
Object.defineProperty(this, "request", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
this.name = 'TimeoutError'; | |
this.request = request; | |
} | |
} | |
// eslint-disable-next-line @typescript-eslint/ban-types | |
const isObject$3 = (value) => value !== null && typeof value === 'object'; | |
const validateAndMerge = (...sources) => { | |
for (const source of sources) { | |
if ((!isObject$3(source) || Array.isArray(source)) && source !== undefined) { | |
throw new TypeError('The `options` argument must be an object'); | |
} | |
} | |
return deepMerge$1({}, ...sources); | |
}; | |
const mergeHeaders = (source1 = {}, source2 = {}) => { | |
const result = new globalThis.Headers(source1); | |
const isHeadersInstance = source2 instanceof globalThis.Headers; | |
const source = new globalThis.Headers(source2); | |
for (const [key, value] of source.entries()) { | |
if ((isHeadersInstance && value === 'undefined') || value === undefined) { | |
result.delete(key); | |
} | |
else { | |
result.set(key, value); | |
} | |
} | |
return result; | |
}; | |
// TODO: Make this strongly-typed (no `any`). | |
const deepMerge$1 = (...sources) => { | |
let returnValue = {}; | |
let headers = {}; | |
for (const source of sources) { | |
if (Array.isArray(source)) { | |
if (!Array.isArray(returnValue)) { | |
returnValue = []; | |
} | |
returnValue = [...returnValue, ...source]; | |
} | |
else if (isObject$3(source)) { | |
for (let [key, value] of Object.entries(source)) { | |
if (isObject$3(value) && key in returnValue) { | |
value = deepMerge$1(returnValue[key], value); | |
} | |
returnValue = { ...returnValue, [key]: value }; | |
} | |
if (isObject$3(source.headers)) { | |
headers = mergeHeaders(headers, source.headers); | |
returnValue.headers = headers; | |
} | |
} | |
} | |
return returnValue; | |
}; | |
const supportsRequestStreams = (() => { | |
let duplexAccessed = false; | |
let hasContentType = false; | |
const supportsReadableStream = typeof globalThis.ReadableStream === 'function'; | |
const supportsRequest = typeof globalThis.Request === 'function'; | |
if (supportsReadableStream && supportsRequest) { | |
hasContentType = new globalThis.Request('https://empty.invalid', { | |
body: new globalThis.ReadableStream(), | |
method: 'POST', | |
// @ts-expect-error - Types are outdated. | |
get duplex() { | |
duplexAccessed = true; | |
return 'half'; | |
}, | |
}).headers.has('Content-Type'); | |
} | |
return duplexAccessed && !hasContentType; | |
})(); | |
const supportsAbortController = typeof globalThis.AbortController === 'function'; | |
const supportsResponseStreams = typeof globalThis.ReadableStream === 'function'; | |
const supportsFormData = typeof globalThis.FormData === 'function'; | |
const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete']; | |
const responseTypes = { | |
json: 'application/json', | |
text: 'text/*', | |
formData: 'multipart/form-data', | |
arrayBuffer: '*/*', | |
blob: '*/*', | |
}; | |
// The maximum value of a 32bit int (see issue #117) | |
const maxSafeTimeout = 2_147_483_647; | |
const stop = Symbol('stop'); | |
const kyOptionKeys = { | |
json: true, | |
parseJson: true, | |
searchParams: true, | |
prefixUrl: true, | |
retry: true, | |
timeout: true, | |
hooks: true, | |
throwHttpErrors: true, | |
onDownloadProgress: true, | |
fetch: true, | |
}; | |
const requestOptionsRegistry = { | |
method: true, | |
headers: true, | |
body: true, | |
mode: true, | |
credentials: true, | |
cache: true, | |
redirect: true, | |
referrer: true, | |
referrerPolicy: true, | |
integrity: true, | |
keepalive: true, | |
signal: true, | |
window: true, | |
dispatcher: true, | |
duplex: true, | |
priority: true, | |
}; | |
const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input; | |
const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace']; | |
const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504]; | |
const retryAfterStatusCodes = [413, 429, 503]; | |
const defaultRetryOptions = { | |
limit: 2, | |
methods: retryMethods, | |
statusCodes: retryStatusCodes, | |
afterStatusCodes: retryAfterStatusCodes, | |
maxRetryAfter: Number.POSITIVE_INFINITY, | |
backoffLimit: Number.POSITIVE_INFINITY, | |
delay: attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000, | |
}; | |
const normalizeRetryOptions = (retry = {}) => { | |
if (typeof retry === 'number') { | |
return { | |
...defaultRetryOptions, | |
limit: retry, | |
}; | |
} | |
if (retry.methods && !Array.isArray(retry.methods)) { | |
throw new Error('retry.methods must be an array'); | |
} | |
if (retry.statusCodes && !Array.isArray(retry.statusCodes)) { | |
throw new Error('retry.statusCodes must be an array'); | |
} | |
return { | |
...defaultRetryOptions, | |
...retry, | |
afterStatusCodes: retryAfterStatusCodes, | |
}; | |
}; | |
// `Promise.race()` workaround (#91) | |
async function timeout(request, init, abortController, options) { | |
return new Promise((resolve, reject) => { | |
const timeoutId = setTimeout(() => { | |
if (abortController) { | |
abortController.abort(); | |
} | |
reject(new TimeoutError(request)); | |
}, options.timeout); | |
void options | |
.fetch(request, init) | |
.then(resolve) | |
.catch(reject) | |
.then(() => { | |
clearTimeout(timeoutId); | |
}); | |
}); | |
} | |
// https://github.com/sindresorhus/delay/tree/ab98ae8dfcb38e1593286c94d934e70d14a4e111 | |
async function delay$1(ms, { signal }) { | |
return new Promise((resolve, reject) => { | |
if (signal) { | |
signal.throwIfAborted(); | |
signal.addEventListener('abort', abortHandler, { once: true }); | |
} | |
function abortHandler() { | |
clearTimeout(timeoutId); | |
reject(signal.reason); | |
} | |
const timeoutId = setTimeout(() => { | |
signal?.removeEventListener('abort', abortHandler); | |
resolve(); | |
}, ms); | |
}); | |
} | |
const findUnknownOptions = (request, options) => { | |
const unknownOptions = {}; | |
for (const key in options) { | |
if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && !(key in request)) { | |
unknownOptions[key] = options[key]; | |
} | |
} | |
return unknownOptions; | |
}; | |
class Ky { | |
static create(input, options) { | |
const ky = new Ky(input, options); | |
const function_ = async () => { | |
if (typeof ky._options.timeout === 'number' && ky._options.timeout > maxSafeTimeout) { | |
throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`); | |
} | |
// Delay the fetch so that body method shortcuts can set the Accept header | |
await Promise.resolve(); | |
let response = await ky._fetch(); | |
for (const hook of ky._options.hooks.afterResponse) { | |
// eslint-disable-next-line no-await-in-loop | |
const modifiedResponse = await hook(ky.request, ky._options, ky._decorateResponse(response.clone())); | |
if (modifiedResponse instanceof globalThis.Response) { | |
response = modifiedResponse; | |
} | |
} | |
ky._decorateResponse(response); | |
if (!response.ok && ky._options.throwHttpErrors) { | |
let error = new HTTPError(response, ky.request, ky._options); | |
for (const hook of ky._options.hooks.beforeError) { | |
// eslint-disable-next-line no-await-in-loop | |
error = await hook(error); | |
} | |
throw error; | |
} | |
// If `onDownloadProgress` is passed, it uses the stream API internally | |
/* istanbul ignore next */ | |
if (ky._options.onDownloadProgress) { | |
if (typeof ky._options.onDownloadProgress !== 'function') { | |
throw new TypeError('The `onDownloadProgress` option must be a function'); | |
} | |
if (!supportsResponseStreams) { | |
throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.'); | |
} | |
return ky._stream(response.clone(), ky._options.onDownloadProgress); | |
} | |
return response; | |
}; | |
const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase()); | |
const result = (isRetriableMethod ? ky._retry(function_) : function_()); | |
for (const [type, mimeType] of Object.entries(responseTypes)) { | |
result[type] = async () => { | |
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | |
ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType); | |
const awaitedResult = await result; | |
const response = awaitedResult.clone(); | |
if (type === 'json') { | |
if (response.status === 204) { | |
return ''; | |
} | |
const arrayBuffer = await response.clone().arrayBuffer(); | |
const responseSize = arrayBuffer.byteLength; | |
if (responseSize === 0) { | |
return ''; | |
} | |
if (options.parseJson) { | |
return options.parseJson(await response.text()); | |
} | |
} | |
return response[type](); | |
}; | |
} | |
return result; | |
} | |
// eslint-disable-next-line complexity | |
constructor(input, options = {}) { | |
Object.defineProperty(this, "request", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
Object.defineProperty(this, "abortController", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
Object.defineProperty(this, "_retryCount", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: 0 | |
}); | |
Object.defineProperty(this, "_input", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
Object.defineProperty(this, "_options", { | |
enumerable: true, | |
configurable: true, | |
writable: true, | |
value: void 0 | |
}); | |
this._input = input; | |
const credentials = this._input instanceof Request && 'credentials' in Request.prototype | |
? this._input.credentials | |
: undefined; | |
this._options = { | |
...(credentials && { credentials }), // For exactOptionalPropertyTypes | |
...options, | |
headers: mergeHeaders(this._input.headers, options.headers), | |
hooks: deepMerge$1({ | |
beforeRequest: [], | |
beforeRetry: [], | |
beforeError: [], | |
afterResponse: [], | |
}, options.hooks), | |
method: normalizeRequestMethod(options.method ?? this._input.method), | |
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | |
prefixUrl: String(options.prefixUrl || ''), | |
retry: normalizeRetryOptions(options.retry), | |
throwHttpErrors: options.throwHttpErrors !== false, | |
timeout: options.timeout ?? 10_000, | |
fetch: options.fetch ?? globalThis.fetch.bind(globalThis), | |
}; | |
if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) { | |
throw new TypeError('`input` must be a string, URL, or Request'); | |
} | |
if (this._options.prefixUrl && typeof this._input === 'string') { | |
if (this._input.startsWith('/')) { | |
throw new Error('`input` must not begin with a slash when using `prefixUrl`'); | |
} | |
if (!this._options.prefixUrl.endsWith('/')) { | |
this._options.prefixUrl += '/'; | |
} | |
this._input = this._options.prefixUrl + this._input; | |
} | |
if (supportsAbortController) { | |
this.abortController = new globalThis.AbortController(); | |
if (this._options.signal) { | |
const originalSignal = this._options.signal; | |
this._options.signal.addEventListener('abort', () => { | |
this.abortController.abort(originalSignal.reason); | |
}); | |
} | |
this._options.signal = this.abortController.signal; | |
} | |
if (supportsRequestStreams) { | |
// @ts-expect-error - Types are outdated. | |
this._options.duplex = 'half'; | |
} | |
this.request = new globalThis.Request(this._input, this._options); | |
if (this._options.searchParams) { | |
// eslint-disable-next-line unicorn/prevent-abbreviations | |
const textSearchParams = typeof this._options.searchParams === 'string' | |
? this._options.searchParams.replace(/^\?/, '') | |
: new URLSearchParams(this._options.searchParams).toString(); | |
// eslint-disable-next-line unicorn/prevent-abbreviations | |
const searchParams = '?' + textSearchParams; | |
const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams); | |
// To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one | |
if (((supportsFormData && this._options.body instanceof globalThis.FormData) | |
|| this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) { | |
this.request.headers.delete('content-type'); | |
} | |
// The spread of `this.request` is required as otherwise it misses the `duplex` option for some reason and throws. | |
this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options); | |
} | |
if (this._options.json !== undefined) { | |
this._options.body = JSON.stringify(this._options.json); | |
this.request.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json'); | |
this.request = new globalThis.Request(this.request, { body: this._options.body }); | |
} | |
} | |
_calculateRetryDelay(error) { | |
this._retryCount++; | |
if (this._retryCount <= this._options.retry.limit && !(error instanceof TimeoutError)) { | |
if (error instanceof HTTPError) { | |
if (!this._options.retry.statusCodes.includes(error.response.status)) { | |
return 0; | |
} | |
const retryAfter = error.response.headers.get('Retry-After'); | |
if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) { | |
let after = Number(retryAfter); | |
if (Number.isNaN(after)) { | |
after = Date.parse(retryAfter) - Date.now(); | |
} | |
else { | |
after *= 1000; | |
} | |
if (this._options.retry.maxRetryAfter !== undefined && after > this._options.retry.maxRetryAfter) { | |
return 0; | |
} | |
return after; | |
} | |
if (error.response.status === 413) { | |
return 0; | |
} | |
} | |
const retryDelay = this._options.retry.delay(this._retryCount); | |
return Math.min(this._options.retry.backoffLimit, retryDelay); | |
} | |
return 0; | |
} | |
_decorateResponse(response) { | |
if (this._options.parseJson) { | |
response.json = async () => this._options.parseJson(await response.text()); | |
} | |
return response; | |
} | |
async _retry(function_) { | |
try { | |
return await function_(); | |
} | |
catch (error) { | |
const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout); | |
if (ms !== 0 && this._retryCount > 0) { | |
await delay$1(ms, { signal: this._options.signal }); | |
for (const hook of this._options.hooks.beforeRetry) { | |
// eslint-disable-next-line no-await-in-loop | |
const hookResult = await hook({ | |
request: this.request, | |
options: this._options, | |
error: error, | |
retryCount: this._retryCount, | |
}); | |
// If `stop` is returned from the hook, the retry process is stopped | |
if (hookResult === stop) { | |
return; | |
} | |
} | |
return this._retry(function_); | |
} | |
throw error; | |
} | |
} | |
async _fetch() { | |
for (const hook of this._options.hooks.beforeRequest) { | |
// eslint-disable-next-line no-await-in-loop | |
const result = await hook(this.request, this._options); | |
if (result instanceof Request) { | |
this.request = result; | |
break; | |
} | |
if (result instanceof Response) { | |
return result; | |
} | |
} | |
const nonRequestOptions = findUnknownOptions(this.request, this._options); | |
if (this._options.timeout === false) { | |
return this._options.fetch(this.request.clone(), nonRequestOptions); | |
} | |
return timeout(this.request.clone(), nonRequestOptions, this.abortController, this._options); | |
} | |
/* istanbul ignore next */ | |
_stream(response, onDownloadProgress) { | |
const totalBytes = Number(response.headers.get('content-length')) || 0; | |
let transferredBytes = 0; | |
if (response.status === 204) { | |
if (onDownloadProgress) { | |
onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array()); | |
} | |
return new globalThis.Response(null, { | |
status: response.status, | |
statusText: response.statusText, | |
headers: response.headers, | |
}); | |
} | |
return new globalThis.Response(new globalThis.ReadableStream({ | |
async start(controller) { | |
const reader = response.body.getReader(); | |
if (onDownloadProgress) { | |
onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array()); | |
} | |
async function read() { | |
const { done, value } = await reader.read(); | |
if (done) { | |
controller.close(); | |
return; | |
} | |
if (onDownloadProgress) { | |
transferredBytes += value.byteLength; | |
const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes; | |
onDownloadProgress({ percent, transferredBytes, totalBytes }, value); | |
} | |
controller.enqueue(value); | |
await read(); | |
} | |
await read(); | |
}, | |
}), { | |
status: response.status, | |
statusText: response.statusText, | |
headers: response.headers, | |
}); | |
} | |
} | |
/*! MIT License © Sindre Sorhus */ | |
const createInstance = (defaults) => { | |
// eslint-disable-next-line @typescript-eslint/promise-function-async | |
const ky = (input, options) => Ky.create(input, validateAndMerge(defaults, options)); | |
for (const method of requestMethods) { | |
// eslint-disable-next-line @typescript-eslint/promise-function-async | |
ky[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method })); | |
} | |
ky.create = (newDefaults) => createInstance(validateAndMerge(newDefaults)); | |
ky.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults)); | |
ky.stop = stop; | |
return ky; | |
}; | |
const ky = createInstance(); | |
var ky$1 = ky; | |
class CorsError extends Error { | |
constructor(origin) { | |
super('This bot can only be executed on ' + origin); | |
} | |
} | |
async function startChatQuery({ | |
typebot, | |
isPreview, | |
apiHost, | |
prefilledVariables, | |
resultId, | |
stripeRedirectStatus, | |
startFrom, | |
sessionId | |
}) { | |
if (isNotDefined(typebot)) throw new Error('Typebot ID is required to get initial messages'); | |
const paymentInProgressStateStr = getPaymentInProgressInStorage() ?? undefined; | |
const paymentInProgressState = paymentInProgressStateStr ? JSON.parse(paymentInProgressStateStr) : undefined; | |
if (paymentInProgressState) { | |
removePaymentInProgressFromStorage(); | |
try { | |
const data = await ky$1.post(`${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sessions/${paymentInProgressState.sessionId}/continueChat`, { | |
json: { | |
message: paymentInProgressState ? stripeRedirectStatus === 'failed' ? 'fail' : 'Success' : undefined | |
}, | |
timeout: false | |
}).json(); | |
return { | |
data: { | |
...data, | |
...paymentInProgressState | |
} | |
}; | |
} catch (error) { | |
return { | |
error | |
}; | |
} | |
} | |
const typebotId = typeof typebot === 'string' ? typebot : typebot.id; | |
if (isPreview) { | |
try { | |
const data = await ky$1.post(`${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/typebots/${typebotId}/preview/startChat`, { | |
json: { | |
isStreamEnabled: true, | |
startFrom, | |
typebot, | |
prefilledVariables, | |
sessionId | |
}, | |
timeout: false | |
}).json(); | |
return { | |
data | |
}; | |
} catch (error) { | |
return { | |
error | |
}; | |
} | |
} | |
try { | |
const iframeReferrerOrigin = parent !== window && isNotEmpty(document.referrer) ? new URL(document.referrer).origin : undefined; | |
const response = await ky$1.post(`${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/typebots/${typebotId}/startChat`, { | |
headers: { | |
'x-typebot-iframe-referrer-origin': iframeReferrerOrigin | |
}, | |
json: { | |
isStreamEnabled: true, | |
prefilledVariables, | |
resultId, | |
isOnlyRegistering: false | |
}, | |
timeout: false | |
}); | |
const corsAllowOrigin = response.headers.get('access-control-allow-origin'); | |
if (iframeReferrerOrigin && corsAllowOrigin && corsAllowOrigin !== '*' && !iframeReferrerOrigin.includes(corsAllowOrigin)) throw new CorsError(corsAllowOrigin); | |
return { | |
data: await response.json() | |
}; | |
} catch (error) { | |
return { | |
error | |
}; | |
} | |
} | |
const continueChatQuery = async ({ | |
apiHost, | |
message, | |
sessionId | |
}) => { | |
try { | |
const data = await ky$1.post(`${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sessions/${sessionId}/continueChat`, { | |
json: { | |
message | |
}, | |
timeout: false | |
}).json(); | |
return { | |
data | |
}; | |
} catch (error) { | |
return { | |
error | |
}; | |
} | |
}; | |
const [isMobile, setIsMobile] = createSignal(); | |
const _tmpl$$T = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="19px" color="white"><path d="M476.59 227.05l-.16-.07L49.35 49.84A23.56 23.56 0 0027.14 52 24.65 24.65 0 0016 72.59v113.29a24 24 0 0019.52 23.57l232.93 43.07a4 4 0 010 7.86L35.53 303.45A24 24 0 0016 327v113.31A23.57 23.57 0 0026.59 460a23.94 23.94 0 0013.22 4 24.55 24.55 0 009.52-1.93L476.4 285.94l.19-.09a32 32 0 000-58.8z">`); | |
const SendIcon = props => (() => { | |
const _el$ = _tmpl$$T(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
const _tmpl$$S = /*#__PURE__*/template(`<svg><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">`); | |
const Spinner = props => (() => { | |
const _el$ = _tmpl$$S(); | |
spread(_el$, mergeProps$2(props, { | |
get ["class"]() { | |
return 'animate-spin h-6 w-6 ' + props.class; | |
}, | |
"xmlns": "http://www.w3.org/2000/svg", | |
"fill": "none", | |
"viewBox": "0 0 24 24", | |
"data-testid": "loading-spinner" | |
}), true, true); | |
return _el$; | |
})(); | |
const _tmpl$$R = /*#__PURE__*/template(`<button>`); | |
const Button = props => { | |
const childrenReturn = children(() => props.children); | |
const [local, buttonProps] = splitProps(props, ['disabled', 'class']); | |
return (() => { | |
const _el$ = _tmpl$$R(); | |
spread(_el$, mergeProps$2(buttonProps, { | |
get disabled() { | |
return props.isDisabled || props.isLoading; | |
}, | |
get ["class"]() { | |
return 'py-2 px-4 font-semibold focus:outline-none filter hover:brightness-90 active:brightness-75 disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 flex justify-center' + (props.variant === 'secondary' ? ' secondary-button' : ' typebot-button') + ' ' + local.class; | |
} | |
}), false, true); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return !props.isLoading; | |
}, | |
get fallback() { | |
return createComponent(Spinner, {}); | |
}, | |
get children() { | |
return childrenReturn(); | |
} | |
})); | |
return _el$; | |
})(); | |
}; | |
const SendButton = props => { | |
const [local, others] = splitProps(props, ['disableIcon']); | |
const showIcon = isMobile() && !local.disableIcon || !props.children || typeof props.children === 'string' && isEmpty$2(props.children); | |
return createComponent(Button, mergeProps$2({ | |
type: "submit" | |
}, others, { | |
get ["class"]() { | |
return clsx$1('flex items-center', props.class); | |
}, | |
"aria-label": showIcon ? 'Send' : undefined, | |
get children() { | |
return createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
when: showIcon, | |
get children() { | |
return createComponent(SendIcon, { | |
get ["class"]() { | |
return 'send-icon flex w-6 h-6 ' + (local.disableIcon ? 'hidden' : ''); | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
when: !showIcon, | |
get children() { | |
return props.children; | |
} | |
})]; | |
} | |
}); | |
} | |
})); | |
}; | |
const _tmpl$$Q = /*#__PURE__*/template(`<div class="flex items-center gap-1"><div class="w-2 h-2 rounded-full bubble1"></div><div class="w-2 h-2 rounded-full bubble2"></div><div class="w-2 h-2 rounded-full bubble3">`); | |
const TypingBubble = () => _tmpl$$Q(); | |
const _tmpl$$P = /*#__PURE__*/template(`<input class="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input" type="text">`); | |
const ShortTextInput = props => { | |
const [local, others] = splitProps(props, ['ref', 'onInput']); | |
return (() => { | |
const _el$ = _tmpl$$P(); | |
_el$.$$input = e => local.onInput(e.currentTarget.value); | |
const _ref$ = props.ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$; | |
_el$.style.setProperty("font-size", "16px"); | |
spread(_el$, others, false, false); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["input"]); | |
const _tmpl$$O = /*#__PURE__*/template(`<textarea class="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input" rows="6" data-testid="textarea" required>`); | |
const Textarea = props => { | |
const [local, others] = splitProps(props, ['ref', 'onInput']); | |
return (() => { | |
const _el$ = _tmpl$$O(); | |
_el$.$$input = e => local.onInput(e.currentTarget.value); | |
const _ref$ = local.ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : local.ref = _el$; | |
_el$.style.setProperty("font-size", "16px"); | |
spread(_el$, mergeProps$2({ | |
get autofocus() { | |
return !isMobile(); | |
} | |
}, others), false, false); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["input"]); | |
const defaultAudioBubbleContent = { | |
isAutoplayEnabled: true | |
}; | |
const _tmpl$$N = /*#__PURE__*/template(`<div><div class="flex w-full items-center"><div class="flex relative z-10 items-start typebot-host-bubble max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing z-10 "></div><audio controls>`); | |
const showAnimationDuration$5 = 400; | |
const typingDuration = 100; | |
let typingTimeout$5; | |
const AudioBubble = props => { | |
let isPlayed = false; | |
let ref; | |
let audioElement; | |
const [isTyping, setIsTyping] = createSignal(props.onTransitionEnd ? true : false); | |
onMount(() => { | |
typingTimeout$5 = setTimeout(() => { | |
if (isPlayed) return; | |
isPlayed = true; | |
setIsTyping(false); | |
setTimeout(() => props.onTransitionEnd?.(ref), showAnimationDuration$5); | |
}, typingDuration); | |
}); | |
onCleanup(() => { | |
if (typingTimeout$5) clearTimeout(typingTimeout$5); | |
}); | |
return (() => { | |
const _el$ = _tmpl$$N(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.nextSibling; | |
const _ref$ = ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : ref = _el$; | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!isTyping()); | |
return () => _c$() && createComponent(TypingBubble, {}); | |
})()); | |
const _ref$2 = audioElement; | |
typeof _ref$2 === "function" ? use(_ref$2, _el$5) : audioElement = _el$5; | |
createRenderEffect(_p$ => { | |
const _v$ = clsx$1('flex flex-col', props.onTransitionEnd ? 'animate-fade-in' : undefined), | |
_v$2 = isTyping() ? '64px' : '100%', | |
_v$3 = isTyping() ? '32px' : '100%', | |
_v$4 = props.content?.url, | |
_v$5 = props.onTransitionEnd ? props.content?.isAutoplayEnabled ?? defaultAudioBubbleContent.isAutoplayEnabled : false, | |
_v$6 = 'z-10 text-fade-in ' + (isTyping() ? 'opacity-0' : 'opacity-100 m-2'), | |
_v$7 = isTyping() ? isMobile() ? '32px' : '36px' : 'revert'; | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$4.style.setProperty("width", _v$2) : _el$4.style.removeProperty("width")); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$4.style.setProperty("height", _v$3) : _el$4.style.removeProperty("height")); | |
_v$4 !== _p$._v$4 && setAttribute(_el$5, "src", _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && (_el$5.autoplay = _p$._v$5 = _v$5); | |
_v$6 !== _p$._v$6 && className(_el$5, _p$._v$6 = _v$6); | |
_v$7 !== _p$._v$7 && ((_p$._v$7 = _v$7) != null ? _el$5.style.setProperty("height", _v$7) : _el$5.style.removeProperty("height")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined, | |
_v$7: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
const defaultEmbedBubbleContent = { | |
height: 400 | |
}; | |
const _tmpl$$M = /*#__PURE__*/template(`<div><div class="flex w-full items-center"><div class="flex relative z-10 items-start typebot-host-bubble w-full max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing z-10 "></div><div><iframe id="embed-bubble-content" class="w-full h-full ">`); | |
let typingTimeout$4; | |
const showAnimationDuration$4 = 400; | |
const EmbedBubble = props => { | |
let ref; | |
const [isTyping, setIsTyping] = createSignal(props.onTransitionEnd ? true : false); | |
const handleMessage = event => { | |
if (props.content?.waitForEvent?.isEnabled && isNotEmpty(event.data.name) && event.data.name === props.content?.waitForEvent.name) { | |
props.onCompleted?.(props.content.waitForEvent.saveDataInVariableId && event.data.data ? event.data.data : undefined); | |
window.removeEventListener('message', handleMessage); | |
} | |
}; | |
onMount(() => { | |
typingTimeout$4 = setTimeout(() => { | |
setIsTyping(false); | |
if (props.content?.waitForEvent?.isEnabled) { | |
window.addEventListener('message', handleMessage); | |
} | |
setTimeout(() => { | |
props.onTransitionEnd?.(ref); | |
}, showAnimationDuration$4); | |
}, 2000); | |
}); | |
onCleanup(() => { | |
if (typingTimeout$4) clearTimeout(typingTimeout$4); | |
window.removeEventListener('message', handleMessage); | |
}); | |
return (() => { | |
const _el$ = _tmpl$$M(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.nextSibling, | |
_el$6 = _el$5.firstChild; | |
const _ref$ = ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : ref = _el$; | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!isTyping()); | |
return () => _c$() && createComponent(TypingBubble, {}); | |
})()); | |
createRenderEffect(_p$ => { | |
const _v$ = clsx$1('flex flex-col w-full', props.onTransitionEnd ? 'animate-fade-in' : undefined), | |
_v$2 = isTyping() ? '64px' : '100%', | |
_v$3 = isTyping() ? '32px' : '100%', | |
_v$4 = clsx$1('p-4 z-20 text-fade-in w-full', isTyping() ? 'opacity-0' : 'opacity-100 p-4'), | |
_v$5 = isTyping() ? isMobile() ? '32px' : '36px' : `${props.content?.height ?? defaultEmbedBubbleContent.height}px`, | |
_v$6 = props.content?.url; | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$4.style.setProperty("width", _v$2) : _el$4.style.removeProperty("width")); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$4.style.setProperty("height", _v$3) : _el$4.style.removeProperty("height")); | |
_v$4 !== _p$._v$4 && className(_el$5, _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && ((_p$._v$5 = _v$5) != null ? _el$5.style.setProperty("height", _v$5) : _el$5.style.removeProperty("height")); | |
_v$6 !== _p$._v$6 && setAttribute(_el$6, "src", _p$._v$6 = _v$6); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
const stringifyError = err => typeof err === 'string' ? err : err instanceof Error ? err.name + ': ' + err.message : JSON.stringify(err); | |
// eslint-disable-next-line @typescript-eslint/no-empty-function | |
const AsyncFunction$1 = Object.getPrototypeOf(async function () {}).constructor; | |
const executeScript = async ({ | |
content, | |
args | |
}) => { | |
try { | |
const func = AsyncFunction$1(...args.map(arg => arg.id), parseContent(content)); | |
await func(...args.map(arg => arg.value)); | |
} catch (err) { | |
return { | |
logs: [{ | |
status: 'error', | |
description: 'Script block failed to execute', | |
details: stringifyError(err) | |
}] | |
}; | |
} | |
}; | |
const parseContent = content => { | |
const contentWithoutScriptTags = content.replace(/<script>/g, '').replace(/<\/script>/g, ''); | |
return contentWithoutScriptTags; | |
}; | |
const executeCode = async ({ | |
args, | |
content | |
}) => { | |
try { | |
const func = AsyncFunction$1(...Object.keys(args), content); | |
await func(...Object.keys(args).map(key => args[key])); | |
} catch (err) { | |
console.warn('Script threw an error:', err); | |
} | |
}; | |
const [botContainerHeight, setBotContainerHeight] = createSignal('100%'); | |
const _tmpl$$L = /*#__PURE__*/template(`<div><div class="flex w-full items-center"><div class="flex relative z-10 items-start typebot-host-bubble w-full max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing z-10 "></div><div><div class="w-full overflow-y-auto">`); | |
let typingTimeout$3; | |
const showAnimationDuration$3 = 400; | |
const CustomEmbedBubble = props => { | |
let ref; | |
const [isTyping, setIsTyping] = createSignal(props.onTransitionEnd ? true : false); | |
let containerRef; | |
onMount(() => { | |
executeCode({ | |
args: { | |
...props.content.initFunction.args, | |
typebotElement: containerRef | |
}, | |
content: props.content.initFunction.content | |
}); | |
if (props.content.waitForEventFunction) executeCode({ | |
args: { | |
...props.content.waitForEventFunction.args, | |
continueFlow: props.onCompleted | |
}, | |
content: props.content.waitForEventFunction.content | |
}); | |
typingTimeout$3 = setTimeout(() => { | |
setIsTyping(false); | |
setTimeout(() => props.onTransitionEnd?.(ref), showAnimationDuration$3); | |
}, 2000); | |
}); | |
onCleanup(() => { | |
if (typingTimeout$3) clearTimeout(typingTimeout$3); | |
}); | |
return (() => { | |
const _el$ = _tmpl$$L(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.nextSibling, | |
_el$6 = _el$5.firstChild; | |
const _ref$ = ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : ref = _el$; | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!isTyping()); | |
return () => _c$() && createComponent(TypingBubble, {}); | |
})()); | |
const _ref$2 = containerRef; | |
typeof _ref$2 === "function" ? use(_ref$2, _el$6) : containerRef = _el$6; | |
createRenderEffect(_p$ => { | |
const _v$ = clsx$1('flex flex-col w-full', props.onTransitionEnd ? 'animate-fade-in' : undefined), | |
_v$2 = isTyping() ? '64px' : '100%', | |
_v$3 = isTyping() ? '32px' : '100%', | |
_v$4 = clsx$1('p-2 z-20 text-fade-in w-full', isTyping() ? 'opacity-0' : 'opacity-100'), | |
_v$5 = isTyping() ? isMobile() ? '32px' : '36px' : undefined, | |
_v$6 = `calc(${botContainerHeight()} - 100px)`; | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$4.style.setProperty("width", _v$2) : _el$4.style.removeProperty("width")); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$4.style.setProperty("height", _v$3) : _el$4.style.removeProperty("height")); | |
_v$4 !== _p$._v$4 && className(_el$5, _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && ((_p$._v$5 = _v$5) != null ? _el$5.style.setProperty("height", _v$5) : _el$5.style.removeProperty("height")); | |
_v$6 !== _p$._v$6 && ((_p$._v$6 = _v$6) != null ? _el$6.style.setProperty("max-height", _v$6) : _el$6.style.removeProperty("max-height")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
const defaultImageBubbleContent = { | |
clickLink: { | |
alt: 'Bubble image' | |
} | |
}; | |
const _tmpl$$K = /*#__PURE__*/template(`<img elementtiming="Bubble image" fetchpriority="high">`), | |
_tmpl$2$o = /*#__PURE__*/template(`<div><div class="flex w-full items-center"><div class="flex relative z-10 items-start typebot-host-bubble max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing z-10 ">`), | |
_tmpl$3$e = /*#__PURE__*/template(`<a target="_blank">`), | |
_tmpl$4$8 = /*#__PURE__*/template(`<figure>`); | |
const showAnimationDuration$2 = 400; | |
const mediaLoadingFallbackTimeout = 5000; | |
let typingTimeout$2; | |
const ImageBubble = props => { | |
let ref; | |
let image; | |
const [isTyping, setIsTyping] = createSignal(props.onTransitionEnd ? true : false); | |
const onTypingEnd = () => { | |
if (!isTyping()) return; | |
setIsTyping(false); | |
setTimeout(() => { | |
props.onTransitionEnd?.(ref); | |
}, showAnimationDuration$2); | |
}; | |
onMount(() => { | |
if (!image) return; | |
typingTimeout$2 = setTimeout(onTypingEnd, mediaLoadingFallbackTimeout); | |
image.onload = () => { | |
clearTimeout(typingTimeout$2); | |
onTypingEnd(); | |
}; | |
}); | |
onCleanup(() => { | |
if (typingTimeout$2) clearTimeout(typingTimeout$2); | |
}); | |
const Image = (() => { | |
const _el$ = _tmpl$$K(); | |
const _ref$ = image; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : image = _el$; | |
_el$.style.setProperty("max-height", "512px"); | |
createRenderEffect(_p$ => { | |
const _v$ = props.content?.url, | |
_v$2 = props.content?.clickLink?.alt ?? defaultImageBubbleContent.clickLink.alt, | |
_v$3 = clsx$1(isTyping() ? 'opacity-0' : 'opacity-100', props.onTransitionEnd ? 'text-fade-in' : undefined, props.content?.url?.endsWith('.svg') ? 'w-full' : undefined), | |
_v$4 = isTyping() ? '32px' : 'auto'; | |
_v$ !== _p$._v$ && setAttribute(_el$, "src", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$, "alt", _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && className(_el$, _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$.style.setProperty("height", _v$4) : _el$.style.removeProperty("height")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$; | |
})(); | |
return (() => { | |
const _el$2 = _tmpl$2$o(), | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.firstChild; | |
const _ref$2 = ref; | |
typeof _ref$2 === "function" ? use(_ref$2, _el$2) : ref = _el$2; | |
insert(_el$5, (() => { | |
const _c$ = createMemo(() => !!isTyping()); | |
return () => _c$() ? createComponent(TypingBubble, {}) : null; | |
})()); | |
insert(_el$4, (() => { | |
const _c$2 = createMemo(() => !!props.content?.clickLink); | |
return () => _c$2() ? (() => { | |
const _el$6 = _tmpl$3$e(); | |
insert(_el$6, Image); | |
createRenderEffect(_p$ => { | |
const _v$8 = props.content.clickLink.url, | |
_v$9 = clsx$1('z-10', isTyping() ? 'h-8' : 'p-4'); | |
_v$8 !== _p$._v$8 && setAttribute(_el$6, "href", _p$._v$8 = _v$8); | |
_v$9 !== _p$._v$9 && className(_el$6, _p$._v$9 = _v$9); | |
return _p$; | |
}, { | |
_v$8: undefined, | |
_v$9: undefined | |
}); | |
return _el$6; | |
})() : (() => { | |
const _el$7 = _tmpl$4$8(); | |
insert(_el$7, Image); | |
createRenderEffect(() => className(_el$7, clsx$1('z-10', !isTyping() && 'p-4', isTyping() ? isMobile() ? 'h-8' : 'h-9' : ''))); | |
return _el$7; | |
})(); | |
})(), null); | |
createRenderEffect(_p$ => { | |
const _v$5 = clsx$1('flex flex-col', props.onTransitionEnd ? 'animate-fade-in' : undefined), | |
_v$6 = isTyping() ? '64px' : '100%', | |
_v$7 = isTyping() ? '32px' : '100%'; | |
_v$5 !== _p$._v$5 && className(_el$2, _p$._v$5 = _v$5); | |
_v$6 !== _p$._v$6 && ((_p$._v$6 = _v$6) != null ? _el$5.style.setProperty("width", _v$6) : _el$5.style.removeProperty("width")); | |
_v$7 !== _p$._v$7 && ((_p$._v$7 = _v$7) != null ? _el$5.style.setProperty("height", _v$7) : _el$5.style.removeProperty("height")); | |
return _p$; | |
}, { | |
_v$5: undefined, | |
_v$6: undefined, | |
_v$7: undefined | |
}); | |
return _el$2; | |
})(); | |
}; | |
const _tmpl$$J = /*#__PURE__*/template(`<br>`), | |
_tmpl$2$n = /*#__PURE__*/template(`<span>`); | |
const computeClassNames = (bold, italic, underline) => { | |
let className = ''; | |
if (bold) className += 'slate-bold'; | |
if (italic) className += ' slate-italic'; | |
if (underline) className += ' slate-underline'; | |
return className; | |
}; | |
const PlateText = props => (() => { | |
const _el$ = _tmpl$2$n(); | |
insert(_el$, () => props.text, null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return createMemo(() => !!props.isUniqueChild)() && isEmpty$2(props.text); | |
}, | |
get children() { | |
return _tmpl$$J(); | |
} | |
}), null); | |
createRenderEffect(() => className(_el$, computeClassNames(props.bold, props.italic, props.underline))); | |
return _el$; | |
})(); | |
const _tmpl$$I = /*#__PURE__*/template(`<a target="_blank" rel="noopener noreferrer">`), | |
_tmpl$2$m = /*#__PURE__*/template(`<ol>`), | |
_tmpl$3$d = /*#__PURE__*/template(`<ul>`), | |
_tmpl$4$7 = /*#__PURE__*/template(`<li>`), | |
_tmpl$5$5 = /*#__PURE__*/template(`<span>`), | |
_tmpl$6$2 = /*#__PURE__*/template(`<div>`); | |
const PlateElement = props => createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return isDefined(props.element.text); | |
}, | |
get children() { | |
return createComponent(PlateText, mergeProps$2(() => props.element, { | |
get isUniqueChild() { | |
return props.isUniqueChild ?? false; | |
} | |
})); | |
} | |
}), createComponent(Match, { | |
when: true, | |
get children() { | |
return createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return props.element.type === 'a'; | |
}, | |
get children() { | |
const _el$ = _tmpl$$I(); | |
insert(_el$, createComponent(For, { | |
get each() { | |
return props.element.children; | |
}, | |
children: child => createComponent(PlateElement, { | |
element: child, | |
get isUniqueChild() { | |
return props.element.children?.length === 1; | |
} | |
}) | |
})); | |
createRenderEffect(() => setAttribute(_el$, "href", props.element.url)); | |
return _el$; | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.element.type === 'ol'; | |
}, | |
get children() { | |
const _el$2 = _tmpl$2$m(); | |
insert(_el$2, createComponent(For, { | |
get each() { | |
return props.element.children; | |
}, | |
children: child => createComponent(PlateElement, { | |
element: child, | |
get isUniqueChild() { | |
return props.element.children?.length === 1; | |
} | |
}) | |
})); | |
return _el$2; | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.element.type === 'ul'; | |
}, | |
get children() { | |
const _el$3 = _tmpl$3$d(); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return props.element.children; | |
}, | |
children: child => createComponent(PlateElement, { | |
element: child, | |
get isUniqueChild() { | |
return props.element.children?.length === 1; | |
} | |
}) | |
})); | |
return _el$3; | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.element.type === 'li'; | |
}, | |
get children() { | |
const _el$4 = _tmpl$4$7(); | |
insert(_el$4, createComponent(For, { | |
get each() { | |
return props.element.children; | |
}, | |
children: child => createComponent(PlateElement, { | |
element: child, | |
get isUniqueChild() { | |
return props.element.children?.length === 1; | |
} | |
}) | |
})); | |
return _el$4; | |
} | |
}), createComponent(Match, { | |
when: true, | |
get children() { | |
return createComponent(ElementRoot, { | |
get element() { | |
return props.element; | |
}, | |
get insideInlineVariable() { | |
return props.insideInlineVariable ?? false; | |
}, | |
get children() { | |
return createComponent(For, { | |
get each() { | |
return props.element.children; | |
}, | |
children: child => createComponent(PlateElement, { | |
element: child, | |
get isUniqueChild() { | |
return props.element.children?.length === 1; | |
}, | |
get insideInlineVariable() { | |
return props.element.type === 'inline-variable'; | |
} | |
}) | |
}); | |
} | |
}); | |
} | |
})]; | |
} | |
}); | |
} | |
})]; | |
} | |
}); | |
const ElementRoot = props => { | |
return createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return props.element.type === 'inline-variable' || props.insideInlineVariable; | |
}, | |
get children() { | |
const _el$5 = _tmpl$5$5(); | |
insert(_el$5, () => props.children); | |
createRenderEffect(() => setAttribute(_el$5, "data-element-type", props.element.type)); | |
return _el$5; | |
} | |
}), createComponent(Match, { | |
when: true, | |
get children() { | |
const _el$6 = _tmpl$6$2(); | |
insert(_el$6, () => props.children); | |
createRenderEffect(() => setAttribute(_el$6, "data-element-type", props.element.type)); | |
return _el$6; | |
} | |
})]; | |
} | |
}); | |
}; | |
const computePlainText = elements => elements.map(element => element.text ?? computePlainText(element.children)).join(''); | |
const defaultSettings = { | |
general: { | |
isInputPrefillEnabled: false, | |
isHideQueryParamsEnabled: true, | |
isNewResultOnRefreshEnabled: true, | |
rememberUser: { | |
isEnabled: false, | |
storage: 'session' | |
}, | |
isBrandingEnabled: false, | |
isTypingEmulationEnabled: true | |
}, | |
typingEmulation: { | |
enabled: true, | |
speed: 400, | |
maxDelay: 3, | |
delayBetweenBubbles: 0, | |
isDisabledOnFirstMessage: true | |
}, | |
metadata: { | |
description: 'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.', | |
favIconUrl: viewerBaseUrl => viewerBaseUrl + '/favicon.png', | |
imageUrl: viewerBaseUrl => viewerBaseUrl + '/site-preview.png' | |
} | |
}; | |
const computeTypingDuration = ({ | |
bubbleContent, | |
typingSettings | |
}) => { | |
let wordCount = bubbleContent.match(/(\w+)/g)?.length ?? 0; | |
if (wordCount === 0) wordCount = bubbleContent.length; | |
const { | |
enabled, | |
speed, | |
maxDelay | |
} = { | |
enabled: typingSettings?.enabled ?? defaultSettings.typingEmulation.enabled, | |
speed: typingSettings?.speed ?? defaultSettings.typingEmulation.speed, | |
maxDelay: typingSettings?.maxDelay ?? defaultSettings.typingEmulation.maxDelay | |
}; | |
const typedWordsPerMinute = speed; | |
let typingTimeout = enabled ? wordCount / typedWordsPerMinute * 60000 : 0; | |
if (typingTimeout > maxDelay * 1000) typingTimeout = maxDelay * 1000; | |
return typingTimeout; | |
}; | |
const _tmpl$$H = /*#__PURE__*/template(`<div><div class="flex w-full items-center"><div class="flex relative items-start typebot-host-bubble max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing " data-testid="host-bubble"></div><div>`); | |
const showAnimationDuration$1 = 400; | |
let typingTimeout$1; | |
const TextBubble = props => { | |
let ref; | |
const [isTyping, setIsTyping] = createSignal(props.onTransitionEnd ? true : false); | |
const onTypingEnd = () => { | |
if (!isTyping()) return; | |
setIsTyping(false); | |
setTimeout(() => { | |
props.onTransitionEnd?.(ref); | |
}, showAnimationDuration$1); | |
}; | |
onMount(() => { | |
if (!isTyping) return; | |
const plainText = props.content?.richText ? computePlainText(props.content.richText) : ''; | |
const typingDuration = props.typingEmulation?.enabled === false || props.isTypingSkipped ? 0 : computeTypingDuration({ | |
bubbleContent: plainText, | |
typingSettings: props.typingEmulation | |
}); | |
typingTimeout$1 = setTimeout(onTypingEnd, typingDuration); | |
}); | |
onCleanup(() => { | |
if (typingTimeout$1) clearTimeout(typingTimeout$1); | |
}); | |
return (() => { | |
const _el$ = _tmpl$$H(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.nextSibling; | |
const _ref$ = ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : ref = _el$; | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!isTyping()); | |
return () => _c$() && createComponent(TypingBubble, {}); | |
})()); | |
insert(_el$5, createComponent(For, { | |
get each() { | |
return props.content?.richText; | |
}, | |
children: element => createComponent(PlateElement, { | |
element: element | |
}) | |
})); | |
createRenderEffect(_p$ => { | |
const _v$ = clsx$1('flex flex-col', props.onTransitionEnd ? 'animate-fade-in' : undefined), | |
_v$2 = isTyping() ? '64px' : '100%', | |
_v$3 = isTyping() ? '32px' : '100%', | |
_v$4 = clsx$1('overflow-hidden text-fade-in mx-4 my-2 whitespace-pre-wrap slate-html-container relative text-ellipsis', isTyping() ? 'opacity-0' : 'opacity-100'), | |
_v$5 = isTyping() ? isMobile() ? '16px' : '20px' : '100%'; | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$4.style.setProperty("width", _v$2) : _el$4.style.removeProperty("width")); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$4.style.setProperty("height", _v$3) : _el$4.style.removeProperty("height")); | |
_v$4 !== _p$._v$4 && className(_el$5, _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && ((_p$._v$5 = _v$5) != null ? _el$5.style.setProperty("height", _v$5) : _el$5.style.removeProperty("height")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
let VideoBubbleContentType = /*#__PURE__*/function (VideoBubbleContentType) { | |
VideoBubbleContentType["URL"] = "url"; | |
VideoBubbleContentType["YOUTUBE"] = "youtube"; | |
VideoBubbleContentType["VIMEO"] = "vimeo"; | |
VideoBubbleContentType["TIKTOK"] = "tiktok"; | |
VideoBubbleContentType["GUMLET"] = "gumlet"; | |
return VideoBubbleContentType; | |
}({}); | |
const embeddableVideoTypes = [VideoBubbleContentType.YOUTUBE, VideoBubbleContentType.VIMEO, VideoBubbleContentType.TIKTOK, VideoBubbleContentType.GUMLET]; | |
const defaultVideoBubbleContent = { | |
height: 400, | |
aspectRatio: '16/9', | |
maxWidth: '100%' | |
}; | |
const youtubeBaseUrl = 'https://www.youtube.com/embed'; | |
const vimeoBaseUrl = 'https://player.vimeo.com/video'; | |
const tiktokBaseUrl = 'https://www.tiktok.com/embed/v2'; | |
const gumletBaseUrl = 'https://play.gumlet.io/embed'; | |
const embedBaseUrls = { | |
[VideoBubbleContentType.VIMEO]: vimeoBaseUrl, | |
[VideoBubbleContentType.YOUTUBE]: youtubeBaseUrl, | |
[VideoBubbleContentType.TIKTOK]: tiktokBaseUrl, | |
[VideoBubbleContentType.GUMLET]: gumletBaseUrl | |
}; | |
const _tmpl$$G = /*#__PURE__*/template(`<video controls>`), | |
_tmpl$2$l = /*#__PURE__*/template(`<div><iframe class="w-full h-full" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>`), | |
_tmpl$3$c = /*#__PURE__*/template(`<div><div class="flex w-full items-center"><div class="flex relative z-10 items-start typebot-host-bubble overflow-hidden w-full max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing z-10 ">`); | |
const showAnimationDuration = 400; | |
let typingTimeout; | |
const VideoBubble = props => { | |
let ref; | |
const [isTyping, setIsTyping] = createSignal(props.onTransitionEnd ? true : false); | |
onMount(() => { | |
const typingDuration = props.content?.type && embeddableVideoTypes.includes(props.content?.type) ? 2000 : 100; | |
typingTimeout = setTimeout(() => { | |
if (!isTyping()) return; | |
setIsTyping(false); | |
setTimeout(() => { | |
props.onTransitionEnd?.(ref); | |
}, showAnimationDuration); | |
}, typingDuration); | |
}); | |
onCleanup(() => { | |
if (typingTimeout) clearTimeout(typingTimeout); | |
}); | |
return (() => { | |
const _el$ = _tmpl$3$c(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild; | |
const _ref$ = ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : ref = _el$; | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!isTyping()); | |
return () => _c$() && createComponent(TypingBubble, {}); | |
})()); | |
insert(_el$3, createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return props.content?.type && props.content.type === VideoBubbleContentType.URL; | |
}, | |
get children() { | |
const _el$5 = _tmpl$$G(); | |
createRenderEffect(_p$ => { | |
const _v$ = props.onTransitionEnd ? false : true, | |
_v$2 = props.content?.url, | |
_v$3 = 'p-4 focus:outline-none w-full z-10 text-fade-in rounded-md ' + (isTyping() ? 'opacity-0' : 'opacity-100'), | |
_v$4 = isTyping() ? isMobile() ? '32px' : '36px' : 'auto', | |
_v$5 = props.content?.aspectRatio, | |
_v$6 = props.content?.maxWidth ?? defaultVideoBubbleContent.maxWidth; | |
_v$ !== _p$._v$ && (_el$5.autoplay = _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$5, "src", _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && className(_el$5, _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$5.style.setProperty("height", _v$4) : _el$5.style.removeProperty("height")); | |
_v$5 !== _p$._v$5 && ((_p$._v$5 = _v$5) != null ? _el$5.style.setProperty("aspect-ratio", _v$5) : _el$5.style.removeProperty("aspect-ratio")); | |
_v$6 !== _p$._v$6 && ((_p$._v$6 = _v$6) != null ? _el$5.style.setProperty("max-width", _v$6) : _el$5.style.removeProperty("max-width")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined | |
}); | |
return _el$5; | |
} | |
}), createComponent(Match, { | |
get when() { | |
return createMemo(() => !!props.content?.type)() && embeddableVideoTypes.includes(props.content.type); | |
}, | |
get children() { | |
const _el$6 = _tmpl$2$l(), | |
_el$7 = _el$6.firstChild; | |
createRenderEffect(_p$ => { | |
const _v$7 = clsx$1('p-4 z-10 text-fade-in w-full', isTyping() ? 'opacity-0' : 'opacity-100 p-4'), | |
_v$8 = isTyping() ? isMobile() ? '32px' : '36px' : !props.content?.aspectRatio ? `${props.content?.height ?? defaultVideoBubbleContent.height}px` : undefined, | |
_v$9 = props.content?.aspectRatio, | |
_v$10 = props.content?.maxWidth ?? defaultVideoBubbleContent.maxWidth, | |
_v$11 = `${embedBaseUrls[props.content?.type]}/${props.content?.id ?? ''}${props.content?.queryParamsStr ?? ''}`; | |
_v$7 !== _p$._v$7 && className(_el$6, _p$._v$7 = _v$7); | |
_v$8 !== _p$._v$8 && ((_p$._v$8 = _v$8) != null ? _el$6.style.setProperty("height", _v$8) : _el$6.style.removeProperty("height")); | |
_v$9 !== _p$._v$9 && ((_p$._v$9 = _v$9) != null ? _el$6.style.setProperty("aspect-ratio", _v$9) : _el$6.style.removeProperty("aspect-ratio")); | |
_v$10 !== _p$._v$10 && ((_p$._v$10 = _v$10) != null ? _el$6.style.setProperty("max-width", _v$10) : _el$6.style.removeProperty("max-width")); | |
_v$11 !== _p$._v$11 && setAttribute(_el$7, "src", _p$._v$11 = _v$11); | |
return _p$; | |
}, { | |
_v$7: undefined, | |
_v$8: undefined, | |
_v$9: undefined, | |
_v$10: undefined, | |
_v$11: undefined | |
}); | |
return _el$6; | |
} | |
})]; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$12 = clsx$1('flex flex-col w-full', props.onTransitionEnd ? 'animate-fade-in' : undefined), | |
_v$13 = isTyping() ? '64px' : '100%', | |
_v$14 = isTyping() ? '32px' : '100%', | |
_v$15 = props.content?.maxWidth ?? defaultVideoBubbleContent.maxWidth; | |
_v$12 !== _p$._v$12 && className(_el$, _p$._v$12 = _v$12); | |
_v$13 !== _p$._v$13 && ((_p$._v$13 = _v$13) != null ? _el$4.style.setProperty("width", _v$13) : _el$4.style.removeProperty("width")); | |
_v$14 !== _p$._v$14 && ((_p$._v$14 = _v$14) != null ? _el$4.style.setProperty("height", _v$14) : _el$4.style.removeProperty("height")); | |
_v$15 !== _p$._v$15 && ((_p$._v$15 = _v$15) != null ? _el$4.style.setProperty("max-width", _v$15) : _el$4.style.removeProperty("max-width")); | |
return _p$; | |
}, { | |
_v$12: undefined, | |
_v$13: undefined, | |
_v$14: undefined, | |
_v$15: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
let BubbleBlockType = /*#__PURE__*/function (BubbleBlockType) { | |
BubbleBlockType["TEXT"] = "text"; | |
BubbleBlockType["IMAGE"] = "image"; | |
BubbleBlockType["VIDEO"] = "video"; | |
BubbleBlockType["EMBED"] = "embed"; | |
BubbleBlockType["AUDIO"] = "audio"; | |
return BubbleBlockType; | |
}({}); | |
const HostBubble = props => createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return props.message.type === BubbleBlockType.TEXT; | |
}, | |
get children() { | |
return createComponent(TextBubble, { | |
get content() { | |
return props.message.content; | |
}, | |
get isTypingSkipped() { | |
return props.isTypingSkipped; | |
}, | |
get typingEmulation() { | |
return props.typingEmulation; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.message.type === BubbleBlockType.IMAGE; | |
}, | |
get children() { | |
return createComponent(ImageBubble, { | |
get content() { | |
return props.message.content; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.message.type === BubbleBlockType.VIDEO; | |
}, | |
get children() { | |
return createComponent(VideoBubble, { | |
get content() { | |
return props.message.content; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.message.type === BubbleBlockType.EMBED; | |
}, | |
get children() { | |
return createComponent(EmbedBubble, { | |
get content() { | |
return props.message.content; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
}, | |
get onCompleted() { | |
return props.onCompleted; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.message.type === 'custom-embed'; | |
}, | |
get children() { | |
return createComponent(CustomEmbedBubble, { | |
get content() { | |
return props.message.content; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
}, | |
get onCompleted() { | |
return props.onCompleted; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.message.type === BubbleBlockType.AUDIO; | |
}, | |
get children() { | |
return createComponent(AudioBubble, { | |
get content() { | |
return props.message.content; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
})]; | |
} | |
}); | |
const _tmpl$$F = /*#__PURE__*/template(`<figure data-testid="default-avatar"><svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="mask0" x="0" y="0" mask-type="alpha"><circle cx="37.5" cy="37.5" r="37.5" fill="#0042DA"></circle></mask><g mask="url(#mask0)"><rect x="-30" y="-43" width="131" height="154" fill="#0042DA"></rect><rect x="2.50413" y="120.333" width="81.5597" height="86.4577" rx="2.5" transform="rotate(-52.6423 2.50413 120.333)" stroke="#FED23D" stroke-width="5"></rect><circle cx="76.5" cy="-1.5" r="29" stroke="#FF8E20" stroke-width="5"></circle><path d="M-49.8224 22L-15.5 -40.7879L18.8224 22H-49.8224Z" stroke="#F7F8FF" stroke-width="5">`); | |
const DefaultAvatar = () => { | |
return (() => { | |
const _el$ = _tmpl$$F(), | |
_el$2 = _el$.firstChild; | |
createRenderEffect(_p$ => { | |
const _v$ = 'flex justify-center items-center rounded-full text-white relative flex-shrink-0 ' + (isMobile() ? 'w-6 h-6 text-sm' : 'w-10 h-10 text-xl'), | |
_v$2 = 'absolute top-0 left-0 ' + (isMobile() ? ' w-6 h-6 text-sm' : 'w-full h-full text-xl'); | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$2, "class", _p$._v$2 = _v$2); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
const _tmpl$$E = /*#__PURE__*/template(`<figure><img alt="Bot avatar" class="rounded-full object-cover w-full h-full" elementtiming="Bot avatar" fetchpriority="high">`); | |
const Avatar = props => { | |
const [avatarSrc, setAvatarSrc] = createSignal(props.initialAvatarSrc); | |
createEffect(() => { | |
if ((avatarSrc()?.startsWith('{{') || !avatarSrc()) && props.initialAvatarSrc?.startsWith('http')) setAvatarSrc(props.initialAvatarSrc); | |
}); | |
return createComponent(Show, { | |
get when() { | |
return isNotEmpty(avatarSrc()); | |
}, | |
keyed: true, | |
get fallback() { | |
return createComponent(DefaultAvatar, {}); | |
}, | |
get children() { | |
const _el$ = _tmpl$$E(), | |
_el$2 = _el$.firstChild; | |
createRenderEffect(_p$ => { | |
const _v$ = 'flex justify-center items-center rounded-full text-white relative animate-fade-in flex-shrink-0 ' + (isMobile() ? 'w-6 h-6 text-sm' : 'w-10 h-10 text-xl'), | |
_v$2 = avatarSrc(); | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$2, "src", _p$._v$2 = _v$2); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined | |
}); | |
return _el$; | |
} | |
}); | |
}; | |
/* eslint @typescript-eslint/no-explicit-any: off */ | |
// symbols | |
const GET_ORIGINAL_SYMBOL = Symbol(); | |
// get object prototype | |
const getProto = Object.getPrototypeOf; | |
const objectsToTrack = new WeakMap(); | |
// check if obj is a plain object or an array | |
const isObjectToTrack = (obj) => obj && | |
(objectsToTrack.has(obj) | |
? objectsToTrack.get(obj) | |
: getProto(obj) === Object.prototype || getProto(obj) === Array.prototype); | |
/** | |
* Unwrap proxy to get the original object. | |
* | |
* Used to retrieve the original object used to create the proxy instance with `createProxy`. | |
* | |
* @param {Proxy<object>} obj - The proxy wrapper of the originial object. | |
* @returns {object | null} - Return either the unwrapped object if exists. | |
* | |
* @example | |
* import { createProxy, getUntracked } from 'proxy-compare'; | |
* | |
* const original = { a: "1", c: "2", d: { e: "3" } }; | |
* const affected = new WeakMap(); | |
* | |
* const proxy = createProxy(original, affected); | |
* const originalFromProxy = getUntracked(proxy) | |
* | |
* Object.is(original, originalFromProxy) // true | |
* isChanged(original, originalFromProxy, affected) // false | |
*/ | |
const getUntracked = (obj) => { | |
if (isObjectToTrack(obj)) { | |
return obj[GET_ORIGINAL_SYMBOL] || null; | |
} | |
return null; | |
}; | |
/** | |
* Mark object to be tracked. | |
* | |
* This function marks an object that will be passed into `createProxy` | |
* as marked to track or not. By default only Array and Object are marked to track, | |
* so this is useful for example to mark a class instance to track or to mark a object | |
* to be untracked when creating your proxy. | |
* | |
* @param obj - Object to mark as tracked or not. | |
* @param mark - Boolean indicating whether you want to track this object or not. | |
* @returns - No return. | |
* | |
* @example | |
* import { createProxy, markToTrack, isChanged } from 'proxy-compare'; | |
* | |
* const nested = { e: "3" } | |
* | |
* markToTrack(nested, false) | |
* | |
* const original = { a: "1", c: "2", d: nested }; | |
* const affected = new WeakMap(); | |
* | |
* const proxy = createProxy(original, affected); | |
* | |
* proxy.d.e | |
* | |
* isChanged(original, { d: { e: "3" } }, affected) // true | |
*/ | |
const markToTrack = (obj, mark = true) => { | |
objectsToTrack.set(obj, mark); | |
}; | |
// src/global.ts | |
function getGlobal$2() { | |
if (typeof globalThis !== "undefined") return globalThis; | |
if (typeof self !== "undefined") return self; | |
if (typeof window !== "undefined") return window; | |
if (typeof global !== "undefined") return global; | |
} | |
function makeGlobal$1(key, value) { | |
const g = getGlobal$2(); | |
if (!g) return value(); | |
g[key] || (g[key] = value()); | |
return g[key]; | |
} | |
var isDev$1 = process.env.NODE_ENV !== "production"; | |
var isObject$2 = (x) => typeof x === "object" && x !== null; | |
var proxyStateMap = makeGlobal$1("__zag__proxyStateMap", () => /* @__PURE__ */ new WeakMap()); | |
var refSet = makeGlobal$1("__zag__refSet", () => /* @__PURE__ */ new WeakSet()); | |
var buildProxyFunction = (objectIs = Object.is, newProxy = (target, handler) => new Proxy(target, handler), canProxy = (x) => isObject$2(x) && !refSet.has(x) && (Array.isArray(x) || !(Symbol.iterator in x)) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer), defaultHandlePromise = (promise) => { | |
switch (promise.status) { | |
case "fulfilled": | |
return promise.value; | |
case "rejected": | |
throw promise.reason; | |
default: | |
throw promise; | |
} | |
}, snapCache = /* @__PURE__ */ new WeakMap(), createSnapshot = (target, version, handlePromise = defaultHandlePromise) => { | |
const cache = snapCache.get(target); | |
if (cache?.[0] === version) { | |
return cache[1]; | |
} | |
const snap = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target)); | |
markToTrack(snap, true); | |
snapCache.set(target, [version, snap]); | |
Reflect.ownKeys(target).forEach((key) => { | |
const value = Reflect.get(target, key); | |
if (refSet.has(value)) { | |
markToTrack(value, false); | |
snap[key] = value; | |
} else if (value instanceof Promise) { | |
Object.defineProperty(snap, key, { | |
get() { | |
return handlePromise(value); | |
} | |
}); | |
} else if (proxyStateMap.has(value)) { | |
snap[key] = snapshot(value, handlePromise); | |
} else { | |
snap[key] = value; | |
} | |
}); | |
return Object.freeze(snap); | |
}, proxyCache = /* @__PURE__ */ new WeakMap(), versionHolder = [1, 1], proxyFunction2 = (initialObject) => { | |
if (!isObject$2(initialObject)) { | |
throw new Error("object required"); | |
} | |
const found = proxyCache.get(initialObject); | |
if (found) { | |
return found; | |
} | |
let version = versionHolder[0]; | |
const listeners = /* @__PURE__ */ new Set(); | |
const notifyUpdate = (op, nextVersion = ++versionHolder[0]) => { | |
if (version !== nextVersion) { | |
version = nextVersion; | |
listeners.forEach((listener) => listener(op, nextVersion)); | |
} | |
}; | |
let checkVersion = versionHolder[1]; | |
const ensureVersion = (nextCheckVersion = ++versionHolder[1]) => { | |
if (checkVersion !== nextCheckVersion && !listeners.size) { | |
checkVersion = nextCheckVersion; | |
propProxyStates.forEach(([propProxyState]) => { | |
const propVersion = propProxyState[1](nextCheckVersion); | |
if (propVersion > version) { | |
version = propVersion; | |
} | |
}); | |
} | |
return version; | |
}; | |
const createPropListener = (prop) => (op, nextVersion) => { | |
const newOp = [...op]; | |
newOp[1] = [prop, ...newOp[1]]; | |
notifyUpdate(newOp, nextVersion); | |
}; | |
const propProxyStates = /* @__PURE__ */ new Map(); | |
const addPropListener = (prop, propProxyState) => { | |
if (isDev$1 && propProxyStates.has(prop)) { | |
throw new Error("prop listener already exists"); | |
} | |
if (listeners.size) { | |
const remove = propProxyState[3](createPropListener(prop)); | |
propProxyStates.set(prop, [propProxyState, remove]); | |
} else { | |
propProxyStates.set(prop, [propProxyState]); | |
} | |
}; | |
const removePropListener = (prop) => { | |
const entry = propProxyStates.get(prop); | |
if (entry) { | |
propProxyStates.delete(prop); | |
entry[1]?.(); | |
} | |
}; | |
const addListener = (listener) => { | |
listeners.add(listener); | |
if (listeners.size === 1) { | |
propProxyStates.forEach(([propProxyState, prevRemove], prop) => { | |
if (isDev$1 && prevRemove) { | |
throw new Error("remove already exists"); | |
} | |
const remove = propProxyState[3](createPropListener(prop)); | |
propProxyStates.set(prop, [propProxyState, remove]); | |
}); | |
} | |
const removeListener = () => { | |
listeners.delete(listener); | |
if (listeners.size === 0) { | |
propProxyStates.forEach(([propProxyState, remove], prop) => { | |
if (remove) { | |
remove(); | |
propProxyStates.set(prop, [propProxyState]); | |
} | |
}); | |
} | |
}; | |
return removeListener; | |
}; | |
const baseObject = Array.isArray(initialObject) ? [] : Object.create(Object.getPrototypeOf(initialObject)); | |
const handler = { | |
deleteProperty(target, prop) { | |
const prevValue = Reflect.get(target, prop); | |
removePropListener(prop); | |
const deleted = Reflect.deleteProperty(target, prop); | |
if (deleted) { | |
notifyUpdate(["delete", [prop], prevValue]); | |
} | |
return deleted; | |
}, | |
set(target, prop, value, receiver) { | |
const hasPrevValue = Reflect.has(target, prop); | |
const prevValue = Reflect.get(target, prop, receiver); | |
if (hasPrevValue && (objectIs(prevValue, value) || proxyCache.has(value) && objectIs(prevValue, proxyCache.get(value)))) { | |
return true; | |
} | |
removePropListener(prop); | |
if (isObject$2(value)) { | |
value = getUntracked(value) || value; | |
} | |
let nextValue = value; | |
if (Object.getOwnPropertyDescriptor(target, prop)?.set) ; else if (value instanceof Promise) { | |
value.then((v) => { | |
Object.assign(value, { status: "fulfilled", value: v }); | |
notifyUpdate(["resolve", [prop], v]); | |
}).catch((e) => { | |
Object.assign(value, { status: "rejected", reason: e }); | |
notifyUpdate(["reject", [prop], e]); | |
}); | |
} else { | |
if (!proxyStateMap.has(value) && canProxy(value)) { | |
nextValue = proxy(value); | |
} | |
const childProxyState = !refSet.has(nextValue) && proxyStateMap.get(nextValue); | |
if (childProxyState) { | |
addPropListener(prop, childProxyState); | |
} | |
} | |
Reflect.set(target, prop, nextValue, receiver); | |
notifyUpdate(["set", [prop], value, prevValue]); | |
return true; | |
} | |
}; | |
const proxyObject = newProxy(baseObject, handler); | |
proxyCache.set(initialObject, proxyObject); | |
const proxyState = [baseObject, ensureVersion, createSnapshot, addListener]; | |
proxyStateMap.set(proxyObject, proxyState); | |
Reflect.ownKeys(initialObject).forEach((key) => { | |
const desc = Object.getOwnPropertyDescriptor(initialObject, key); | |
if (desc.get || desc.set) { | |
Object.defineProperty(baseObject, key, desc); | |
} else { | |
proxyObject[key] = initialObject[key]; | |
} | |
}); | |
return proxyObject; | |
}) => [ | |
// public functions | |
proxyFunction2, | |
// shared state | |
proxyStateMap, | |
refSet, | |
// internal things | |
objectIs, | |
newProxy, | |
canProxy, | |
defaultHandlePromise, | |
snapCache, | |
createSnapshot, | |
proxyCache, | |
versionHolder | |
]; | |
var [proxyFunction] = buildProxyFunction(); | |
function proxy(initialObject = {}) { | |
return proxyFunction(initialObject); | |
} | |
function subscribe(proxyObject, callback, notifyInSync) { | |
const proxyState = proxyStateMap.get(proxyObject); | |
if (isDev$1 && !proxyState) { | |
console.warn("Please use proxy object"); | |
} | |
let promise; | |
const ops = []; | |
const addListener = proxyState[3]; | |
let isListenerActive = false; | |
const listener = (op) => { | |
ops.push(op); | |
if (notifyInSync) { | |
callback(ops.splice(0)); | |
return; | |
} | |
if (!promise) { | |
promise = Promise.resolve().then(() => { | |
promise = void 0; | |
if (isListenerActive) { | |
callback(ops.splice(0)); | |
} | |
}); | |
} | |
}; | |
const removeListener = addListener(listener); | |
isListenerActive = true; | |
return () => { | |
isListenerActive = false; | |
removeListener(); | |
}; | |
} | |
function snapshot(proxyObject, handlePromise) { | |
const proxyState = proxyStateMap.get(proxyObject); | |
if (isDev$1 && !proxyState) { | |
console.warn("Please use proxy object"); | |
} | |
const [target, ensureVersion, createSnapshot] = proxyState; | |
return createSnapshot(target, ensureVersion(), handlePromise); | |
} | |
function ref(obj) { | |
refSet.add(obj); | |
return obj; | |
} | |
// src/proxy-computed.ts | |
function proxyWithComputed(initialObject, computedFns) { | |
const keys = Object.keys(computedFns); | |
keys.forEach((key) => { | |
if (Object.getOwnPropertyDescriptor(initialObject, key)) { | |
throw new Error("object property already defined"); | |
} | |
const computedFn = computedFns[key]; | |
const { get, set } = typeof computedFn === "function" ? { get: computedFn } : computedFn; | |
const desc = {}; | |
desc.get = () => get(snapshot(proxyObject)); | |
if (set) { | |
desc.set = (newValue) => set(proxyObject, newValue); | |
} | |
Object.defineProperty(initialObject, key, desc); | |
}); | |
const proxyObject = proxy(initialObject); | |
return proxyObject; | |
} | |
function set$2(obj, key, val) { | |
if (typeof val.value === 'object') val.value = klona(val.value); | |
if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === '__proto__') { | |
Object.defineProperty(obj, key, val); | |
} else obj[key] = val.value; | |
} | |
function klona(x) { | |
if (typeof x !== 'object') return x; | |
var i=0, k, list, tmp, str=Object.prototype.toString.call(x); | |
if (str === '[object Object]') { | |
tmp = Object.create(x.__proto__ || null); | |
} else if (str === '[object Array]') { | |
tmp = Array(x.length); | |
} else if (str === '[object Set]') { | |
tmp = new Set; | |
x.forEach(function (val) { | |
tmp.add(klona(val)); | |
}); | |
} else if (str === '[object Map]') { | |
tmp = new Map; | |
x.forEach(function (val, key) { | |
tmp.set(klona(key), klona(val)); | |
}); | |
} else if (str === '[object Date]') { | |
tmp = new Date(+x); | |
} else if (str === '[object RegExp]') { | |
tmp = new RegExp(x.source, x.flags); | |
} else if (str === '[object DataView]') { | |
tmp = new x.constructor( klona(x.buffer) ); | |
} else if (str === '[object ArrayBuffer]') { | |
tmp = x.slice(0); | |
} else if (str.slice(-6) === 'Array]') { | |
// ArrayBuffer.isView(x) | |
// ~> `new` bcuz `Buffer.slice` => ref | |
tmp = new x.constructor(x); | |
} | |
if (tmp) { | |
for (list=Object.getOwnPropertySymbols(x); i < list.length; i++) { | |
set$2(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i])); | |
} | |
for (i=0, list=Object.getOwnPropertyNames(x); i < list.length; i++) { | |
if (Object.hasOwnProperty.call(tmp, k=list[i]) && tmp[k] === x[k]) continue; | |
set$2(tmp, k, Object.getOwnPropertyDescriptor(x, k)); | |
} | |
} | |
return tmp || x; | |
} | |
var __defProp = Object.defineProperty; | |
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | |
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | |
// ../utilities/core/src/array.ts | |
function clear(v) { | |
while (v.length > 0) v.pop(); | |
return v; | |
} | |
// ../utilities/core/src/functions.ts | |
var runIfFn$2 = (v, ...a) => { | |
const res = typeof v === "function" ? v(...a) : v; | |
return res ?? void 0; | |
}; | |
var cast$1 = (v) => v; | |
var noop$1 = () => { | |
}; | |
var callAll$1 = (...fns) => (...a) => { | |
fns.forEach(function(fn) { | |
fn?.(...a); | |
}); | |
}; | |
var uuid$1 = /* @__PURE__ */ (() => { | |
let id = 0; | |
return () => { | |
id++; | |
return id.toString(36); | |
}; | |
})(); | |
// ../utilities/core/src/guard.ts | |
var isDev = () => process.env.NODE_ENV !== "production"; | |
var isArray$1 = (v) => Array.isArray(v); | |
var isObject$1 = (v) => !(v == null || typeof v !== "object" || isArray$1(v)); | |
var isNumber$2 = (v) => typeof v === "number" && !Number.isNaN(v); | |
var isString$1 = (v) => typeof v === "string"; | |
var isFunction$2 = (v) => typeof v === "function"; | |
var hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); | |
// ../utilities/core/src/object.ts | |
function compact$1(obj) { | |
if (!isPlainObject$1(obj) || obj === void 0) { | |
return obj; | |
} | |
const keys = Reflect.ownKeys(obj).filter((key) => typeof key === "string"); | |
const filtered = {}; | |
for (const key of keys) { | |
const value = obj[key]; | |
if (value !== void 0) { | |
filtered[key] = compact$1(value); | |
} | |
} | |
return filtered; | |
} | |
var isPlainObject$1 = (value) => { | |
return value && typeof value === "object" && value.constructor === Object; | |
}; | |
// ../utilities/core/src/warning.ts | |
function warn$1(...a) { | |
const m = a.length === 1 ? a[0] : a[1]; | |
const c = a.length === 2 ? a[0] : true; | |
if (c && process.env.NODE_ENV !== "production") { | |
console.warn(m); | |
} | |
} | |
function invariant(...a) { | |
const m = a.length === 1 ? a[0] : a[1]; | |
const c = a.length === 2 ? a[0] : true; | |
if (c && process.env.NODE_ENV !== "production") { | |
throw new Error(m); | |
} | |
} | |
// src/deep-merge.ts | |
function deepMerge(source, ...objects) { | |
for (const obj of objects) { | |
const target = compact$1(obj); | |
for (const key in target) { | |
if (isObject$1(obj[key])) { | |
if (!source[key]) { | |
source[key] = {}; | |
} | |
deepMerge(source[key], obj[key]); | |
} else { | |
source[key] = obj[key]; | |
} | |
} | |
} | |
return source; | |
} | |
function structuredClone(v) { | |
return klona(v); | |
} | |
function toEvent(event) { | |
const obj = isString$1(event) ? { type: event } : event; | |
return obj; | |
} | |
function toArray(value) { | |
if (!value) return []; | |
return isArray$1(value) ? value.slice() : [value]; | |
} | |
function isGuardHelper(value) { | |
return isObject$1(value) && value.predicate != null; | |
} | |
// src/guard-utils.ts | |
var Truthy = () => true; | |
function exec(guardMap, ctx, event, meta) { | |
return (guard) => { | |
if (isString$1(guard)) { | |
return !!guardMap[guard]?.(ctx, event, meta); | |
} | |
if (isFunction$2(guard)) { | |
return guard(ctx, event, meta); | |
} | |
return guard.predicate(guardMap)(ctx, event, meta); | |
}; | |
} | |
function or$2(...conditions) { | |
return { | |
predicate: (guardMap) => (ctx, event, meta) => conditions.map(exec(guardMap, ctx, event, meta)).some(Boolean) | |
}; | |
} | |
function and$2(...conditions) { | |
return { | |
predicate: (guardMap) => (ctx, event, meta) => conditions.map(exec(guardMap, ctx, event, meta)).every(Boolean) | |
}; | |
} | |
function not$2(condition) { | |
return { | |
predicate: (guardMap) => (ctx, event, meta) => { | |
return !exec(guardMap, ctx, event, meta)(condition); | |
} | |
}; | |
} | |
function stateIn(...values) { | |
return (_ctx, _evt, meta) => meta.state.matches(...values); | |
} | |
var guards = { or: or$2, and: and$2, not: not$2, stateIn }; | |
function determineGuardFn(guard, guardMap) { | |
guard = guard ?? Truthy; | |
return (context, event, meta) => { | |
if (isString$1(guard)) { | |
const value = guardMap[guard]; | |
return isFunction$2(value) ? value(context, event, meta) : value; | |
} | |
if (isGuardHelper(guard)) { | |
return guard.predicate(guardMap)(context, event, meta); | |
} | |
return guard?.(context, event, meta); | |
}; | |
} | |
function determineActionsFn(values, guardMap) { | |
return (context, event, meta) => { | |
if (isGuardHelper(values)) { | |
return values.predicate(guardMap)(context, event, meta); | |
} | |
return values; | |
}; | |
} | |
function createProxy(config) { | |
const computedContext = config.computed ?? cast$1({}); | |
const initialContext = config.context ?? cast$1({}); | |
const initialTags = config.initial ? config.states?.[config.initial]?.tags : []; | |
const state = proxy({ | |
value: config.initial ?? "", | |
previousValue: "", | |
event: cast$1({}), | |
previousEvent: cast$1({}), | |
context: proxyWithComputed(initialContext, computedContext), | |
done: false, | |
tags: initialTags ?? [], | |
hasTag(tag) { | |
return this.tags.includes(tag); | |
}, | |
matches(...value) { | |
return value.includes(this.value); | |
}, | |
can(event) { | |
return cast$1(this).nextEvents.includes(event); | |
}, | |
get nextEvents() { | |
const stateEvents = config.states?.[this.value]?.["on"] ?? {}; | |
const globalEvents = config?.on ?? {}; | |
return Object.keys({ ...stateEvents, ...globalEvents }); | |
}, | |
get changed() { | |
if (this.event.value === "machine.init" /* Init */ || !this.previousValue) return false; | |
return this.value !== this.previousValue; | |
} | |
}); | |
return cast$1(state); | |
} | |
// src/delay-utils.ts | |
function determineDelayFn(delay, delaysMap) { | |
return (context, event) => { | |
if (isNumber$2(delay)) return delay; | |
if (isFunction$2(delay)) { | |
return delay(context, event); | |
} | |
if (isString$1(delay)) { | |
const value = Number.parseFloat(delay); | |
if (!Number.isNaN(value)) { | |
return value; | |
} | |
if (delaysMap) { | |
const valueOrFn = delaysMap?.[delay]; | |
invariant( | |
valueOrFn == null, | |
`[@zag-js/core > determine-delay] Cannot determine delay for \`${delay}\`. It doesn't exist in \`options.delays\`` | |
); | |
return isFunction$2(valueOrFn) ? valueOrFn(context, event) : valueOrFn; | |
} | |
} | |
}; | |
} | |
// src/transition-utils.ts | |
function toTarget(target) { | |
return isString$1(target) ? { target } : target; | |
} | |
function determineTransitionFn(transitions, guardMap) { | |
return (context, event, meta) => { | |
return toArray(transitions).map(toTarget).find((transition) => { | |
const determineGuard = determineGuardFn(transition.guard, guardMap); | |
const guard = determineGuard(context, event, meta); | |
return guard ?? transition.target ?? transition.actions; | |
}); | |
}; | |
} | |
// src/machine.ts | |
var Machine = class { | |
// Let's get started! | |
constructor(config, options) { | |
__publicField(this, "status", "Not Started" /* NotStarted */); | |
__publicField(this, "state"); | |
__publicField(this, "initialState"); | |
__publicField(this, "initialContext"); | |
__publicField(this, "id"); | |
__publicField(this, "type", "machine" /* Machine */); | |
// Cleanup function map (per state) | |
__publicField(this, "activityEvents", /* @__PURE__ */ new Map()); | |
__publicField(this, "delayedEvents", /* @__PURE__ */ new Map()); | |
// state update listeners the user can opt-in for | |
__publicField(this, "stateListeners", /* @__PURE__ */ new Set()); | |
__publicField(this, "doneListeners", /* @__PURE__ */ new Set()); | |
__publicField(this, "contextWatchers", /* @__PURE__ */ new Set()); | |
// Cleanup functions (for `subscribe`) | |
__publicField(this, "removeStateListener", noop$1); | |
// For Parent <==> Spawned Actor relationship | |
__publicField(this, "parent"); | |
__publicField(this, "children", /* @__PURE__ */ new Map()); | |
// A map of guard, action, delay implementations | |
__publicField(this, "guardMap"); | |
__publicField(this, "actionMap"); | |
__publicField(this, "delayMap"); | |
__publicField(this, "activityMap"); | |
__publicField(this, "sync"); | |
__publicField(this, "options"); | |
__publicField(this, "config"); | |
__publicField(this, "_created", () => { | |
const event = toEvent("machine.created" /* Created */); | |
this.executeActions(this.config?.created, event); | |
}); | |
// Starts the interpreted machine. | |
__publicField(this, "start", (init) => { | |
this.state.value = ""; | |
this.state.tags = []; | |
if (this.status === "Running" /* Running */) { | |
return this; | |
} | |
this.status = "Running" /* Running */; | |
this.removeStateListener = subscribe( | |
this.state, | |
() => { | |
this.stateListeners.forEach((listener) => { | |
listener(this.stateSnapshot); | |
}); | |
}, | |
this.sync | |
); | |
this.setupContextWatchers(); | |
this.executeActivities(toEvent("machine.start" /* Start */), toArray(this.config.activities), "machine.start" /* Start */); | |
this.executeActions(this.config.entry, toEvent("machine.start" /* Start */)); | |
const event = toEvent("machine.init" /* Init */); | |
const target = isObject$1(init) ? init.value : init; | |
const context = isObject$1(init) ? init.context : void 0; | |
if (context) { | |
this.setContext(context); | |
} | |
const transition = { | |
target: target ?? this.config.initial | |
}; | |
const next = this.getNextStateInfo(transition, event); | |
this.initialState = next; | |
this.performStateChangeEffects(this.state.value, next, event); | |
return this; | |
}); | |
__publicField(this, "setupContextWatchers", () => { | |
const { watch } = this.config; | |
if (!watch) return; | |
let prev = snapshot(this.state.context); | |
const cleanup = subscribe(this.state.context, () => { | |
const next = snapshot(this.state.context); | |
for (const [key, fn] of Object.entries(watch)) { | |
const isEqual = this.options.compareFns?.[key] ?? Object.is; | |
if (isEqual(prev[key], next[key])) continue; | |
this.executeActions(fn, this.state.event); | |
} | |
prev = next; | |
}); | |
this.contextWatchers.add(cleanup); | |
}); | |
// Stops the interpreted machine | |
__publicField(this, "stop", () => { | |
if (this.status === "Stopped" /* Stopped */) return; | |
this.performExitEffects(this.state.value, toEvent("machine.stop" /* Stop */)); | |
this.executeActions(this.config.exit, toEvent("machine.stop" /* Stop */)); | |
this.setState(""); | |
this.setEvent("machine.stop" /* Stop */); | |
this.stopStateListeners(); | |
this.stopChildren(); | |
this.stopActivities(); | |
this.stopDelayedEvents(); | |
this.stopContextWatchers(); | |
this.status = "Stopped" /* Stopped */; | |
return this; | |
}); | |
__publicField(this, "stopStateListeners", () => { | |
this.removeStateListener(); | |
this.stateListeners.clear(); | |
}); | |
__publicField(this, "stopContextWatchers", () => { | |
this.contextWatchers.forEach((fn) => fn()); | |
this.contextWatchers.clear(); | |
}); | |
__publicField(this, "stopDelayedEvents", () => { | |
this.delayedEvents.forEach((state) => { | |
state.forEach((stop) => stop()); | |
}); | |
this.delayedEvents.clear(); | |
}); | |
// Cleanup running activities (e.g `setInterval`, invoked callbacks, promises) | |
__publicField(this, "stopActivities", (state) => { | |
if (state) { | |
this.activityEvents.get(state)?.forEach((stop) => stop()); | |
this.activityEvents.get(state)?.clear(); | |
this.activityEvents.delete(state); | |
} else { | |
this.activityEvents.forEach((state2) => { | |
state2.forEach((stop) => stop()); | |
state2.clear(); | |
}); | |
this.activityEvents.clear(); | |
} | |
}); | |
/** | |
* Function to send event to spawned child machine or actor | |
*/ | |
__publicField(this, "sendChild", (evt, to) => { | |
const event = toEvent(evt); | |
const id = runIfFn$2(to, this.contextSnapshot); | |
const child = this.children.get(id); | |
if (!child) { | |
invariant(`[@zag-js/core] Cannot send '${event.type}' event to unknown child`); | |
} | |
child.send(event); | |
}); | |
/** | |
* Function to stop a running child machine or actor | |
*/ | |
__publicField(this, "stopChild", (id) => { | |
if (!this.children.has(id)) { | |
invariant(`[@zag-js/core > stop-child] Cannot stop unknown child ${id}`); | |
} | |
this.children.get(id).stop(); | |
this.children.delete(id); | |
}); | |
__publicField(this, "removeChild", (id) => { | |
this.children.delete(id); | |
}); | |
// Stop and delete spawned actors | |
__publicField(this, "stopChildren", () => { | |
this.children.forEach((child) => child.stop()); | |
this.children.clear(); | |
}); | |
__publicField(this, "setParent", (parent) => { | |
this.parent = parent; | |
}); | |
__publicField(this, "spawn", (src, id) => { | |
const actor = runIfFn$2(src); | |
if (id) actor.id = id; | |
actor.type = "machine.actor" /* Actor */; | |
actor.setParent(this); | |
this.children.set(actor.id, cast$1(actor)); | |
actor.onDone(() => { | |
this.removeChild(actor.id); | |
}).start(); | |
return cast$1(ref(actor)); | |
}); | |
__publicField(this, "stopActivity", (key) => { | |
if (!this.state.value) return; | |
const cleanups = this.activityEvents.get(this.state.value); | |
cleanups?.get(key)?.(); | |
cleanups?.delete(key); | |
}); | |
__publicField(this, "addActivityCleanup", (state, key, cleanup) => { | |
if (!state) return; | |
if (!this.activityEvents.has(state)) { | |
this.activityEvents.set(state, /* @__PURE__ */ new Map([[key, cleanup]])); | |
} else { | |
this.activityEvents.get(state)?.set(key, cleanup); | |
} | |
}); | |
__publicField(this, "setState", (target) => { | |
this.state.previousValue = this.state.value; | |
this.state.value = target; | |
const stateNode = this.getStateNode(target); | |
if (target == null) { | |
clear(this.state.tags); | |
} else { | |
this.state.tags = toArray(stateNode?.tags); | |
} | |
}); | |
/** | |
* To used within side effects for React or Vue to update context | |
*/ | |
__publicField(this, "setContext", (context) => { | |
if (!context) return; | |
deepMerge(this.state.context, compact$1(context)); | |
}); | |
__publicField(this, "setOptions", (options) => { | |
const opts = compact$1(options); | |
this.actionMap = { ...this.actionMap, ...opts.actions }; | |
this.delayMap = { ...this.delayMap, ...opts.delays }; | |
this.activityMap = { ...this.activityMap, ...opts.activities }; | |
this.guardMap = { ...this.guardMap, ...opts.guards }; | |
}); | |
__publicField(this, "getStateNode", (state) => { | |
if (!state) return; | |
return this.config.states?.[state]; | |
}); | |
__publicField(this, "getNextStateInfo", (transitions, event) => { | |
const transition = this.determineTransition(transitions, event); | |
const isTargetless = !transition?.target; | |
const target = transition?.target ?? this.state.value; | |
const changed = this.state.value !== target; | |
const stateNode = this.getStateNode(target); | |
const reenter = !isTargetless && !changed && !transition?.internal; | |
const info = { | |
reenter, | |
transition, | |
stateNode, | |
target, | |
changed | |
}; | |
this.log("NextState:", `[${event.type}]`, this.state.value, "---->", info.target); | |
return info; | |
}); | |
__publicField(this, "getAfterActions", (transition, delay) => { | |
let id; | |
return { | |
entry: () => { | |
id = globalThis.setTimeout(() => { | |
const next = this.getNextStateInfo(transition, this.state.event); | |
this.performStateChangeEffects(this.state.value, next, this.state.event); | |
}, delay); | |
}, | |
exit: () => { | |
globalThis.clearTimeout(id); | |
} | |
}; | |
}); | |
/** | |
* All `after` events leverage `setTimeout` and `clearTimeout`, | |
* we invoke the `clearTimeout` on exit and `setTimeout` on entry. | |
* | |
* To achieve this, we split the `after` defintion into `entry` and `exit` | |
* functions and append them to the state's `entry` and `exit` actions | |
*/ | |
__publicField(this, "getDelayedEventActions", (state) => { | |
const stateNode = this.getStateNode(state); | |
const event = this.state.event; | |
if (!stateNode || !stateNode.after) return; | |
const entries = []; | |
const exits = []; | |
if (isArray$1(stateNode.after)) { | |
const transition = this.determineTransition(stateNode.after, event); | |
if (!transition) return; | |
if (!hasProp(transition, "delay")) { | |
throw new Error(`[@zag-js/core > after] Delay is required for after transition: ${JSON.stringify(transition)}`); | |
} | |
const determineDelay = determineDelayFn(transition.delay, this.delayMap); | |
const __delay = determineDelay(this.contextSnapshot, event); | |
const actions = this.getAfterActions(transition, __delay); | |
entries.push(actions.entry); | |
exits.push(actions.exit); | |
return { entries, exits }; | |
} | |
if (isObject$1(stateNode.after)) { | |
for (const delay in stateNode.after) { | |
const transition = stateNode.after[delay]; | |
const determineDelay = determineDelayFn(delay, this.delayMap); | |
const __delay = determineDelay(this.contextSnapshot, event); | |
const actions = this.getAfterActions(transition, __delay); | |
entries.push(actions.entry); | |
exits.push(actions.exit); | |
} | |
} | |
return { entries, exits }; | |
}); | |
/** | |
* Function to executes defined actions. It can accept actions as string | |
* (referencing `options.actions`) or actual functions. | |
*/ | |
__publicField(this, "executeActions", (actions, event) => { | |
const pickedActions = determineActionsFn(actions, this.guardMap)(this.contextSnapshot, event, this.guardMeta); | |
for (const action of toArray(pickedActions)) { | |
const fn = isString$1(action) ? this.actionMap?.[action] : action; | |
warn$1( | |
isString$1(action) && !fn, | |
`[@zag-js/core > execute-actions] No implementation found for action: \`${action}\`` | |
); | |
fn?.(this.state.context, event, this.meta); | |
} | |
}); | |
/** | |
* Function to execute running activities and registers | |
* their cleanup function internally (to be called later on when we exit the state) | |
*/ | |
__publicField(this, "executeActivities", (event, activities, state) => { | |
for (const activity of activities) { | |
const fn = isString$1(activity) ? this.activityMap?.[activity] : activity; | |
if (!fn) { | |
warn$1(`[@zag-js/core > execute-activity] No implementation found for activity: \`${activity}\``); | |
continue; | |
} | |
const cleanup = fn(this.state.context, event, this.meta); | |
if (cleanup) { | |
const key = isString$1(activity) ? activity : activity.name || uuid$1(); | |
this.addActivityCleanup(state ?? this.state.value, key, cleanup); | |
} | |
} | |
}); | |
/** | |
* Normalizes the `every` definition to transition. `every` can be: | |
* - An array of possible actions to run (we need to pick the first match based on guard) | |
* - An object of intervals and actions | |
*/ | |
__publicField(this, "createEveryActivities", (every, callbackfn) => { | |
if (!every) return; | |
if (isArray$1(every)) { | |
const picked = toArray(every).find((transition) => { | |
const delayOrFn = transition.delay; | |
const determineDelay2 = determineDelayFn(delayOrFn, this.delayMap); | |
const delay2 = determineDelay2(this.contextSnapshot, this.state.event); | |
const determineGuard = determineGuardFn(transition.guard, this.guardMap); | |
const guard = determineGuard(this.contextSnapshot, this.state.event, this.guardMeta); | |
return guard ?? delay2 != null; | |
}); | |
if (!picked) return; | |
const determineDelay = determineDelayFn(picked.delay, this.delayMap); | |
const delay = determineDelay(this.contextSnapshot, this.state.event); | |
const activity = () => { | |
const id = globalThis.setInterval(() => { | |
this.executeActions(picked.actions, this.state.event); | |
}, delay); | |
return () => { | |
globalThis.clearInterval(id); | |
}; | |
}; | |
callbackfn(activity); | |
} else { | |
for (const interval in every) { | |
const actions = every?.[interval]; | |
const determineDelay = determineDelayFn(interval, this.delayMap); | |
const delay = determineDelay(this.contextSnapshot, this.state.event); | |
const activity = () => { | |
const id = globalThis.setInterval(() => { | |
this.executeActions(actions, this.state.event); | |
}, delay); | |
return () => { | |
globalThis.clearInterval(id); | |
}; | |
}; | |
callbackfn(activity); | |
} | |
} | |
}); | |
__publicField(this, "setEvent", (event) => { | |
this.state.previousEvent = this.state.event; | |
this.state.event = ref(toEvent(event)); | |
}); | |
__publicField(this, "performExitEffects", (current, event) => { | |
const currentState = this.state.value; | |
if (currentState === "") return; | |
const stateNode = current ? this.getStateNode(current) : void 0; | |
this.stopActivities(currentState); | |
const _exit = determineActionsFn(stateNode?.exit, this.guardMap)(this.contextSnapshot, event, this.guardMeta); | |
const exitActions = toArray(_exit); | |
const afterExitActions = this.delayedEvents.get(currentState); | |
if (afterExitActions) { | |
exitActions.push(...afterExitActions); | |
} | |
this.executeActions(exitActions, event); | |
}); | |
__publicField(this, "performEntryEffects", (next, event) => { | |
const stateNode = this.getStateNode(next); | |
const activities = toArray(stateNode?.activities); | |
this.createEveryActivities(stateNode?.every, (activity) => { | |
activities.unshift(activity); | |
}); | |
if (activities.length > 0) { | |
this.executeActivities(event, activities); | |
} | |
const pickedActions = determineActionsFn(stateNode?.entry, this.guardMap)( | |
this.contextSnapshot, | |
event, | |
this.guardMeta | |
); | |
const entryActions = toArray(pickedActions); | |
const afterActions = this.getDelayedEventActions(next); | |
if (stateNode?.after && afterActions) { | |
this.delayedEvents.set(next, afterActions?.exits); | |
entryActions.push(...afterActions.entries); | |
} | |
this.executeActions(entryActions, event); | |
if (stateNode?.type === "final") { | |
this.state.done = true; | |
this.doneListeners.forEach((listener) => { | |
listener(this.stateSnapshot); | |
}); | |
this.stop(); | |
} | |
}); | |
__publicField(this, "performTransitionEffects", (transitions, event) => { | |
const transition = this.determineTransition(transitions, event); | |
this.executeActions(transition?.actions, event); | |
}); | |
/** | |
* Performs all the requires side-effects or reactions when | |
* we move from state A => state B. | |
* | |
* The Effect order: | |
* Exit actions (current state) => Transition actions => Go to state => Entry actions (next state) | |
*/ | |
__publicField(this, "performStateChangeEffects", (current, next, event) => { | |
this.setEvent(event); | |
const changed = next.changed || next.reenter; | |
if (changed) { | |
this.performExitEffects(current, event); | |
} | |
this.performTransitionEffects(next.transition, event); | |
this.setState(next.target); | |
if (changed) { | |
this.performEntryEffects(next.target, event); | |
} | |
}); | |
__publicField(this, "determineTransition", (transition, event) => { | |
const fn = determineTransitionFn(transition, this.guardMap); | |
return fn?.(this.contextSnapshot, event, this.guardMeta); | |
}); | |
/** | |
* Function to send event to parent machine from spawned child | |
*/ | |
__publicField(this, "sendParent", (evt) => { | |
if (!this.parent) { | |
invariant("[@zag-js/core > send-parent] Cannot send event to an unknown parent"); | |
} | |
const event = toEvent(evt); | |
this.parent?.send(event); | |
}); | |
__publicField(this, "log", (...args) => { | |
if (isDev() && this.options.debug) { | |
console.log(...args); | |
} | |
}); | |
/** | |
* Function to send an event to current machine | |
*/ | |
__publicField(this, "send", (evt) => { | |
const event = toEvent(evt); | |
this.transition(this.state.value, event); | |
}); | |
__publicField(this, "transition", (state, evt) => { | |
const stateNode = isString$1(state) ? this.getStateNode(state) : state?.stateNode; | |
const event = toEvent(evt); | |
if (!stateNode && !this.config.on) { | |
const msg = this.status === "Stopped" /* Stopped */ ? "[@zag-js/core > transition] Cannot transition a stopped machine" : `[@zag-js/core > transition] State does not have a definition for \`state\`: ${state}, \`event\`: ${event.type}`; | |
warn$1(msg); | |
return; | |
} | |
const transitions = stateNode?.on?.[event.type] ?? this.config.on?.[event.type]; | |
const next = this.getNextStateInfo(transitions, event); | |
this.performStateChangeEffects(this.state.value, next, event); | |
return next.stateNode; | |
}); | |
__publicField(this, "subscribe", (listener) => { | |
this.stateListeners.add(listener); | |
if (this.status === "Running" /* Running */) { | |
listener(this.stateSnapshot); | |
} | |
return () => { | |
this.stateListeners.delete(listener); | |
}; | |
}); | |
__publicField(this, "onDone", (listener) => { | |
this.doneListeners.add(listener); | |
return this; | |
}); | |
__publicField(this, "onTransition", (listener) => { | |
this.stateListeners.add(listener); | |
if (this.status === "Running" /* Running */) { | |
listener(this.stateSnapshot); | |
} | |
return this; | |
}); | |
this.config = structuredClone(config); | |
this.options = structuredClone(options ?? {}); | |
this.id = this.config.id ?? `machine-${uuid$1()}`; | |
this.guardMap = this.options?.guards ?? {}; | |
this.actionMap = this.options?.actions ?? {}; | |
this.delayMap = this.options?.delays ?? {}; | |
this.activityMap = this.options?.activities ?? {}; | |
this.sync = this.options?.sync ?? false; | |
this.state = createProxy(this.config); | |
this.initialContext = snapshot(this.state.context); | |
} | |
// immutable state value | |
get stateSnapshot() { | |
return cast$1(snapshot(this.state)); | |
} | |
getState() { | |
return this.stateSnapshot; | |
} | |
// immutable context value | |
get contextSnapshot() { | |
return this.stateSnapshot.context; | |
} | |
/** | |
* A reference to the instance methods of the machine. | |
* Useful when spawning child machines and managing the communication between them. | |
*/ | |
get self() { | |
const self = this; | |
return { | |
id: this.id, | |
send: this.send.bind(this), | |
sendParent: this.sendParent.bind(this), | |
sendChild: this.sendChild.bind(this), | |
stop: this.stop.bind(this), | |
stopChild: this.stopChild.bind(this), | |
spawn: this.spawn.bind(this), | |
stopActivity: this.stopActivity.bind(this), | |
get state() { | |
return self.stateSnapshot; | |
}, | |
get initialContext() { | |
return self.initialContext; | |
}, | |
get initialState() { | |
return self.initialState?.target ?? ""; | |
} | |
}; | |
} | |
get meta() { | |
return { | |
state: this.stateSnapshot, | |
guards: this.guardMap, | |
send: this.send.bind(this), | |
self: this.self, | |
initialContext: this.initialContext, | |
initialState: this.initialState?.target ?? "", | |
getState: () => this.stateSnapshot, | |
getAction: (key) => this.actionMap[key], | |
getGuard: (key) => this.guardMap[key] | |
}; | |
} | |
get guardMeta() { | |
return { | |
state: this.stateSnapshot | |
}; | |
} | |
get [Symbol.toStringTag]() { | |
return "Machine"; | |
} | |
}; | |
var createMachine = (config, options) => new Machine(config, options); | |
var isMachine = (value) => { | |
return value instanceof Machine || value?.type === "machine" /* Machine */; | |
}; | |
// src/merge-props.ts | |
var clsx = (...args) => args.map((str) => str?.trim?.()).filter(Boolean).join(" "); | |
var CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g; | |
var serialize = (style) => { | |
const res = {}; | |
let match; | |
while (match = CSS_REGEX.exec(style)) { | |
res[match[1]] = match[2]; | |
} | |
return res; | |
}; | |
var css = (a, b) => { | |
if (isString$1(a)) { | |
if (isString$1(b)) return `${a};${b}`; | |
a = serialize(a); | |
} else if (isString$1(b)) { | |
b = serialize(b); | |
} | |
return Object.assign({}, a ?? {}, b ?? {}); | |
}; | |
function mergeProps$1(...args) { | |
let result = {}; | |
for (let props of args) { | |
for (let key in result) { | |
if (key.startsWith("on") && typeof result[key] === "function" && typeof props[key] === "function") { | |
result[key] = callAll$1(props[key], result[key]); | |
continue; | |
} | |
if (key === "className" || key === "class") { | |
result[key] = clsx(result[key], props[key]); | |
continue; | |
} | |
if (key === "style") { | |
result[key] = css(result[key], props[key]); | |
continue; | |
} | |
result[key] = props[key] !== void 0 ? props[key] : result[key]; | |
} | |
for (let key in props) { | |
if (result[key] === void 0) { | |
result[key] = props[key]; | |
} | |
} | |
} | |
return result; | |
} | |
// src/prop-types.ts | |
function createNormalizer(fn) { | |
return new Proxy({}, { | |
get() { | |
return fn; | |
} | |
}); | |
} | |
// src/create-props.ts | |
var createProps = () => (props) => Array.from(new Set(props)); | |
const $RAW = Symbol("store-raw"), | |
$NODE = Symbol("store-node"); | |
function wrap$1(value) { | |
let p = value[$PROXY]; | |
if (!p) { | |
Object.defineProperty(value, $PROXY, { | |
value: p = new Proxy(value, proxyTraps$1) | |
}); | |
if (!Array.isArray(value)) { | |
const keys = Object.keys(value), | |
desc = Object.getOwnPropertyDescriptors(value); | |
for (let i = 0, l = keys.length; i < l; i++) { | |
const prop = keys[i]; | |
if (desc[prop].get) { | |
Object.defineProperty(value, prop, { | |
enumerable: desc[prop].enumerable, | |
get: desc[prop].get.bind(p) | |
}); | |
} | |
} | |
} | |
} | |
return p; | |
} | |
function isWrappable(obj) { | |
let proto; | |
return obj != null && typeof obj === "object" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj)); | |
} | |
function unwrap(item, set = new Set()) { | |
let result, unwrapped, v, prop; | |
if (result = item != null && item[$RAW]) return result; | |
if (!isWrappable(item) || set.has(item)) return item; | |
if (Array.isArray(item)) { | |
if (Object.isFrozen(item)) item = item.slice(0);else set.add(item); | |
for (let i = 0, l = item.length; i < l; i++) { | |
v = item[i]; | |
if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped; | |
} | |
} else { | |
if (Object.isFrozen(item)) item = Object.assign({}, item);else set.add(item); | |
const keys = Object.keys(item), | |
desc = Object.getOwnPropertyDescriptors(item); | |
for (let i = 0, l = keys.length; i < l; i++) { | |
prop = keys[i]; | |
if (desc[prop].get) continue; | |
v = item[prop]; | |
if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped; | |
} | |
} | |
return item; | |
} | |
function getDataNodes(target) { | |
let nodes = target[$NODE]; | |
if (!nodes) Object.defineProperty(target, $NODE, { | |
value: nodes = Object.create(null) | |
}); | |
return nodes; | |
} | |
function getDataNode(nodes, property, value) { | |
return nodes[property] || (nodes[property] = createDataNode(value)); | |
} | |
function proxyDescriptor$1(target, property) { | |
const desc = Reflect.getOwnPropertyDescriptor(target, property); | |
if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc; | |
delete desc.value; | |
delete desc.writable; | |
desc.get = () => target[$PROXY][property]; | |
return desc; | |
} | |
function trackSelf(target) { | |
if (getListener()) { | |
const nodes = getDataNodes(target); | |
(nodes._ || (nodes._ = createDataNode()))(); | |
} | |
} | |
function ownKeys$1(target) { | |
trackSelf(target); | |
return Reflect.ownKeys(target); | |
} | |
function createDataNode(value) { | |
const [s, set] = createSignal(value, { | |
equals: false, | |
internal: true | |
}); | |
s.$ = set; | |
return s; | |
} | |
const proxyTraps$1 = { | |
get(target, property, receiver) { | |
if (property === $RAW) return target; | |
if (property === $PROXY) return receiver; | |
if (property === $TRACK) { | |
trackSelf(target); | |
return receiver; | |
} | |
const nodes = getDataNodes(target); | |
const tracked = nodes[property]; | |
let value = tracked ? tracked() : target[property]; | |
if (property === $NODE || property === "__proto__") return value; | |
if (!tracked) { | |
const desc = Object.getOwnPropertyDescriptor(target, property); | |
if (getListener() && (typeof value !== "function" || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getDataNode(nodes, property, value)(); | |
} | |
return isWrappable(value) ? wrap$1(value) : value; | |
}, | |
has(target, property) { | |
if (property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === "__proto__") return true; | |
this.get(target, property, target); | |
return property in target; | |
}, | |
set() { | |
return true; | |
}, | |
deleteProperty() { | |
return true; | |
}, | |
ownKeys: ownKeys$1, | |
getOwnPropertyDescriptor: proxyDescriptor$1 | |
}; | |
function setProperty(state, property, value, deleting = false) { | |
if (!deleting && state[property] === value) return; | |
const prev = state[property], | |
len = state.length; | |
if (value === undefined) delete state[property];else state[property] = value; | |
let nodes = getDataNodes(state), | |
node; | |
if (node = getDataNode(nodes, property, prev)) node.$(() => value); | |
if (Array.isArray(state) && state.length !== len) { | |
for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$(); | |
(node = getDataNode(nodes, "length", len)) && node.$(state.length); | |
} | |
(node = nodes._) && node.$(); | |
} | |
function mergeStoreNode(state, value) { | |
const keys = Object.keys(value); | |
for (let i = 0; i < keys.length; i += 1) { | |
const key = keys[i]; | |
setProperty(state, key, value[key]); | |
} | |
} | |
function updateArray(current, next) { | |
if (typeof next === "function") next = next(current); | |
next = unwrap(next); | |
if (Array.isArray(next)) { | |
if (current === next) return; | |
let i = 0, | |
len = next.length; | |
for (; i < len; i++) { | |
const value = next[i]; | |
if (current[i] !== value) setProperty(current, i, value); | |
} | |
setProperty(current, "length", len); | |
} else mergeStoreNode(current, next); | |
} | |
function updatePath(current, path, traversed = []) { | |
let part, | |
prev = current; | |
if (path.length > 1) { | |
part = path.shift(); | |
const partType = typeof part, | |
isArray = Array.isArray(current); | |
if (Array.isArray(part)) { | |
for (let i = 0; i < part.length; i++) { | |
updatePath(current, [part[i]].concat(path), traversed); | |
} | |
return; | |
} else if (isArray && partType === "function") { | |
for (let i = 0; i < current.length; i++) { | |
if (part(current[i], i)) updatePath(current, [i].concat(path), traversed); | |
} | |
return; | |
} else if (isArray && partType === "object") { | |
const { | |
from = 0, | |
to = current.length - 1, | |
by = 1 | |
} = part; | |
for (let i = from; i <= to; i += by) { | |
updatePath(current, [i].concat(path), traversed); | |
} | |
return; | |
} else if (path.length > 1) { | |
updatePath(current[part], path, [part].concat(traversed)); | |
return; | |
} | |
prev = current[part]; | |
traversed = [part].concat(traversed); | |
} | |
let value = path[0]; | |
if (typeof value === "function") { | |
value = value(prev, traversed); | |
if (value === prev) return; | |
} | |
if (part === undefined && value == undefined) return; | |
value = unwrap(value); | |
if (part === undefined || isWrappable(prev) && isWrappable(value) && !Array.isArray(value)) { | |
mergeStoreNode(prev, value); | |
} else setProperty(current, part, value); | |
} | |
function createStore(...[store, options]) { | |
const unwrappedStore = unwrap(store || {}); | |
const isArray = Array.isArray(unwrappedStore); | |
const wrappedStore = wrap$1(unwrappedStore); | |
function setStore(...args) { | |
batch(() => { | |
isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args); | |
}); | |
} | |
return [wrappedStore, setStore]; | |
} | |
const $ROOT = Symbol("store-root"); | |
function applyState(target, parent, property, merge, key) { | |
const previous = parent[property]; | |
if (target === previous) return; | |
if (property !== $ROOT && (!isWrappable(target) || !isWrappable(previous) || key && target[key] !== previous[key])) { | |
setProperty(parent, property, target); | |
return; | |
} | |
if (Array.isArray(target)) { | |
if (target.length && previous.length && (!merge || key && target[0] && target[0][key] != null)) { | |
let i, j, start, end, newEnd, item, newIndicesNext, keyVal; | |
for (start = 0, end = Math.min(previous.length, target.length); start < end && (previous[start] === target[start] || key && previous[start] && target[start] && previous[start][key] === target[start][key]); start++) { | |
applyState(target[start], previous, start, merge, key); | |
} | |
const temp = new Array(target.length), | |
newIndices = new Map(); | |
for (end = previous.length - 1, newEnd = target.length - 1; end >= start && newEnd >= start && (previous[end] === target[newEnd] || key && previous[start] && target[start] && previous[end][key] === target[newEnd][key]); end--, newEnd--) { | |
temp[newEnd] = previous[end]; | |
} | |
if (start > newEnd || start > end) { | |
for (j = start; j <= newEnd; j++) setProperty(previous, j, target[j]); | |
for (; j < target.length; j++) { | |
setProperty(previous, j, temp[j]); | |
applyState(target[j], previous, j, merge, key); | |
} | |
if (previous.length > target.length) setProperty(previous, "length", target.length); | |
return; | |
} | |
newIndicesNext = new Array(newEnd + 1); | |
for (j = newEnd; j >= start; j--) { | |
item = target[j]; | |
keyVal = key && item ? item[key] : item; | |
i = newIndices.get(keyVal); | |
newIndicesNext[j] = i === undefined ? -1 : i; | |
newIndices.set(keyVal, j); | |
} | |
for (i = start; i <= end; i++) { | |
item = previous[i]; | |
keyVal = key && item ? item[key] : item; | |
j = newIndices.get(keyVal); | |
if (j !== undefined && j !== -1) { | |
temp[j] = previous[i]; | |
j = newIndicesNext[j]; | |
newIndices.set(keyVal, j); | |
} | |
} | |
for (j = start; j < target.length; j++) { | |
if (j in temp) { | |
setProperty(previous, j, temp[j]); | |
applyState(target[j], previous, j, merge, key); | |
} else setProperty(previous, j, target[j]); | |
} | |
} else { | |
for (let i = 0, len = target.length; i < len; i++) { | |
applyState(target[i], previous, i, merge, key); | |
} | |
} | |
if (previous.length > target.length) setProperty(previous, "length", target.length); | |
return; | |
} | |
const targetKeys = Object.keys(target); | |
for (let i = 0, len = targetKeys.length; i < len; i++) { | |
applyState(target[targetKeys[i]], previous, targetKeys[i], merge, key); | |
} | |
const previousKeys = Object.keys(previous); | |
for (let i = 0, len = previousKeys.length; i < len; i++) { | |
if (target[previousKeys[i]] === undefined) setProperty(previous, previousKeys[i], undefined); | |
} | |
} | |
function reconcile(value, options = {}) { | |
const { | |
merge, | |
key = "id" | |
} = options, | |
v = unwrap(value); | |
return state => { | |
if (!isWrappable(state) || !isWrappable(v)) return v; | |
const res = applyState(v, { | |
[$ROOT]: state | |
}, $ROOT, merge, key); | |
return res === undefined ? state : res; | |
}; | |
} | |
// src/merge-props.ts | |
function mergeProps(...sources) { | |
const target = {}; | |
for (let i = 0; i < sources.length; i++) { | |
let source = sources[i]; | |
if (typeof source === "function") source = source(); | |
if (source) { | |
const descriptors = Object.getOwnPropertyDescriptors(source); | |
for (const key in descriptors) { | |
if (key in target) continue; | |
Object.defineProperty(target, key, { | |
enumerable: true, | |
get() { | |
let e = {}; | |
if (key === "style" || key === "class" || key === "className" || key.startsWith("on")) { | |
for (let i2 = 0; i2 < sources.length; i2++) { | |
let s = sources[i2]; | |
if (typeof s === "function") s = s(); | |
e = mergeProps$1(e, { [key]: (s || {})[key] }); | |
} | |
return e[key]; | |
} | |
for (let i2 = sources.length - 1; i2 >= 0; i2--) { | |
let v, s = sources[i2]; | |
if (typeof s === "function") s = s(); | |
v = (s || {})[key]; | |
if (v !== void 0) return v; | |
} | |
} | |
}); | |
} | |
} | |
} | |
return target; | |
} | |
// ../../utilities/core/src/guard.ts | |
var isArray = (v) => Array.isArray(v); | |
var isObject = (v) => !(v == null || typeof v !== "object" || isArray(v)); | |
var isNumber$1 = (v) => typeof v === "number" && !Number.isNaN(v); | |
var isString = (v) => typeof v === "string"; | |
// src/normalize-props.ts | |
var eventMap = { | |
onFocus: "onFocusIn", | |
onBlur: "onFocusOut", | |
onDoubleClick: "onDblClick", | |
onChange: "onInput", | |
defaultChecked: "checked", | |
defaultValue: "value", | |
htmlFor: "for", | |
className: "class" | |
}; | |
var format = (v) => v.startsWith("--") ? v : hyphenateStyleName(v); | |
function toSolidProp(prop) { | |
return prop in eventMap ? eventMap[prop] : prop; | |
} | |
var normalizeProps = createNormalizer((props) => { | |
const normalized = {}; | |
for (const key in props) { | |
const value = props[key]; | |
if (key === "readOnly" && value === false) { | |
continue; | |
} | |
if (key === "style" && isObject(value)) { | |
normalized["style"] = cssify(value); | |
continue; | |
} | |
if (key === "children") { | |
if (isString(value)) { | |
normalized["textContent"] = value; | |
} | |
continue; | |
} | |
normalized[toSolidProp(key)] = value; | |
} | |
return normalized; | |
}); | |
function cssify(style) { | |
let css = {}; | |
for (const property in style) { | |
const value = style[property]; | |
if (!isString(value) && !isNumber$1(value)) continue; | |
css[format(property)] = value; | |
} | |
return css; | |
} | |
var uppercasePattern = /[A-Z]/g; | |
var msPattern = /^ms-/; | |
var cache = {}; | |
function toHyphenLower(match) { | |
return "-" + match.toLowerCase(); | |
} | |
function hyphenateStyleName(name) { | |
if (cache.hasOwnProperty(name)) return cache[name]; | |
var hName = name.replace(uppercasePattern, toHyphenLower); | |
return cache[name] = msPattern.test(hName) ? "-" + hName : hName; | |
} | |
function useSnapshot(service, options) { | |
const { actions, context } = options ?? {}; | |
const [state, setState] = createStore(service.getState()); | |
onMount(() => { | |
const unsubscribe = service.subscribe((nextState) => { | |
setState(reconcile(nextState)); | |
}); | |
onCleanup(() => { | |
unsubscribe(); | |
}); | |
}); | |
createEffect(() => { | |
const contextValue = typeof context === "function" ? context() : context; | |
service.setContext(contextValue); | |
}); | |
createEffect(() => { | |
service.setOptions({ actions }); | |
}); | |
return state; | |
} | |
// src/use-actor.ts | |
function useActor(service) { | |
const state = useSnapshot(service); | |
return [state, service.send]; | |
} | |
function useService(machine, options) { | |
const { state: hydratedState, context } = options ?? {}; | |
const service = (() => { | |
const instance = typeof machine === "function" ? machine() : machine; | |
const ctx = typeof context === "function" ? context() : context; | |
if (ctx) instance.setContext(ctx); | |
instance._created(); | |
return instance; | |
})(); | |
onMount(() => { | |
service.start(hydratedState); | |
onCleanup(() => { | |
service.stop(); | |
}); | |
}); | |
return service; | |
} | |
// src/use-machine.ts | |
function useMachine(machine, options) { | |
const service = useService(machine, options); | |
const state = useSnapshot(service, options); | |
return [state, service.send, service]; | |
} | |
// src/create-anatomy.ts | |
var createAnatomy$1 = (name, parts = []) => ({ | |
parts: (...values) => { | |
if (isEmpty$1(parts)) { | |
return createAnatomy$1(name, values); | |
} | |
throw new Error("createAnatomy().parts(...) should only be called once. Did you mean to use .extendWith(...) ?"); | |
}, | |
extendWith: (...values) => createAnatomy$1(name, [...parts, ...values]), | |
rename: (newName) => createAnatomy$1(newName, parts), | |
keys: () => parts, | |
build: () => [...new Set(parts)].reduce( | |
(prev, part) => Object.assign(prev, { | |
[part]: { | |
selector: [ | |
`&[data-scope="${toKebabCase$1(name)}"][data-part="${toKebabCase$1(part)}"]`, | |
`& [data-scope="${toKebabCase$1(name)}"][data-part="${toKebabCase$1(part)}"]` | |
].join(", "), | |
attrs: { "data-scope": toKebabCase$1(name), "data-part": toKebabCase$1(part) } | |
} | |
}), | |
{} | |
) | |
}); | |
var toKebabCase$1 = (value) => value.replace(/([A-Z])([A-Z])/g, "$1-$2").replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase(); | |
var isEmpty$1 = (v) => v.length === 0; | |
// src/attrs.ts | |
var dataAttr = (guard) => guard ? "" : void 0; | |
// src/constants.ts | |
var MAX_Z_INDEX = 2147483647; | |
// src/is.ts | |
var isHTMLElement$1 = (v) => typeof v === "object" && v?.nodeType === Node.ELEMENT_NODE && typeof v?.nodeName === "string"; | |
var isDocument = (el) => el.nodeType === Node.DOCUMENT_NODE; | |
var isWindow = (el) => el != null && el === el.window; | |
var isNode$1 = (el) => el.nodeType !== void 0; | |
var isShadowRoot$1 = (el) => el && isNode$1(el) && el.nodeType === Node.DOCUMENT_FRAGMENT_NODE && "host" in el; | |
// src/contains.ts | |
function contains(parent, child) { | |
if (!parent || !child) return false; | |
if (!isHTMLElement$1(parent) || !isHTMLElement$1(child)) return false; | |
return parent === child || parent.contains(child); | |
} | |
// src/env.ts | |
function getDocument(el) { | |
if (isDocument(el)) return el; | |
if (isWindow(el)) return el.document; | |
return el?.ownerDocument ?? document; | |
} | |
function getWindow$1(el) { | |
if (isShadowRoot$1(el)) return getWindow$1(el.host); | |
if (isDocument(el)) return el.defaultView ?? window; | |
if (isHTMLElement$1(el)) return el.ownerDocument?.defaultView ?? window; | |
return window; | |
} | |
// src/data-url.ts | |
function getDataUrl(svg, opts) { | |
const { type, quality = 0.92 } = opts; | |
if (!svg) throw new Error("[get-data-url]: could not find the svg element"); | |
const win = getWindow$1(svg); | |
const doc = win.document; | |
const serializer = new win.XMLSerializer(); | |
const source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svg); | |
const svgString = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source); | |
if (type === "image/svg+xml") { | |
return Promise.resolve(svgString); | |
} | |
const svgBounds = svg.getBoundingClientRect(); | |
const dpr = win.devicePixelRatio || 1; | |
const canvas = doc.createElement("canvas"); | |
const image = new win.Image(); | |
image.src = svgString; | |
canvas.width = svgBounds.width * dpr; | |
canvas.height = svgBounds.height * dpr; | |
const context = canvas.getContext("2d"); | |
context.scale(dpr, dpr); | |
return new Promise((resolve) => { | |
image.onload = () => { | |
context.drawImage(image, 0, 0); | |
resolve(canvas.toDataURL(type, quality)); | |
}; | |
}); | |
} | |
// src/platform.ts | |
var isDom = () => typeof document !== "undefined"; | |
function getPlatform() { | |
const agent = navigator.userAgentData; | |
return agent?.platform ?? navigator.platform; | |
} | |
var pt = (v) => isDom() && v.test(getPlatform()); | |
var ua = (v) => isDom() && v.test(navigator.userAgent); | |
var isMac = () => pt(/^Mac/); | |
var isFirefox = () => ua(/firefox\//i); | |
var isApple = () => pt(/mac|iphone|ipad|ipod/i); | |
var isIos = () => pt(/iP(hone|ad|od)|iOS/); | |
function getComposedPath(event) { | |
return event.composedPath?.() ?? event.nativeEvent?.composedPath?.(); | |
} | |
function getEventTarget(event) { | |
const composedPath = getComposedPath(event); | |
return composedPath?.[0] ?? event.target; | |
} | |
var isSelfTarget = (event) => { | |
return contains(event.currentTarget, getEventTarget(event)); | |
}; | |
function isOpeningInNewTab(event) { | |
const element = event.currentTarget; | |
if (!element) return false; | |
const isAppleDevice = isApple(); | |
if (isAppleDevice && !event.metaKey) return false; | |
if (!isAppleDevice && !event.ctrlKey) return false; | |
const localName = element.localName; | |
if (localName === "a") return true; | |
if (localName === "button" && element.type === "submit") return true; | |
if (localName === "input" && element.type === "submit") return true; | |
return false; | |
} | |
function isDownloadingEvent(event) { | |
const element = event.currentTarget; | |
if (!element) return false; | |
const localName = element.localName; | |
if (!event.altKey) return false; | |
if (localName === "a") return true; | |
if (localName === "button" && element.type === "submit") return true; | |
if (localName === "input" && element.type === "submit") return true; | |
return false; | |
} | |
// src/get-by-id.ts | |
var defaultItemToId = (v) => v.id; | |
function itemById(v, id, itemToId = defaultItemToId) { | |
return v.find((item) => itemToId(item) === id); | |
} | |
function indexOfId(v, id, itemToId = defaultItemToId) { | |
const item = itemById(v, id, itemToId); | |
return item ? v.indexOf(item) : -1; | |
} | |
function nextById(v, id, loop = true) { | |
let idx = indexOfId(v, id); | |
idx = loop ? (idx + 1) % v.length : Math.min(idx + 1, v.length - 1); | |
return v[idx]; | |
} | |
function prevById(v, id, loop = true) { | |
let idx = indexOfId(v, id); | |
if (idx === -1) return loop ? v[v.length - 1] : null; | |
idx = loop ? (idx - 1 + v.length) % v.length : Math.max(0, idx - 1); | |
return v[idx]; | |
} | |
// src/sanitize.ts | |
var sanitize = (str) => str.split("").map((char) => { | |
const code = char.charCodeAt(0); | |
if (code > 0 && code < 128) return char; | |
if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\"); | |
return ""; | |
}).join("").trim(); | |
// src/get-by-text.ts | |
var getValueText = (item) => sanitize(item.dataset.valuetext ?? item.textContent ?? ""); | |
var match = (valueText, query2) => valueText.trim().toLowerCase().startsWith(query2.toLowerCase()); | |
var wrap = (v, idx) => { | |
return v.map((_, index) => v[(Math.max(idx, 0) + index) % v.length]); | |
}; | |
function getByText(v, text, currentId, itemToId = defaultItemToId) { | |
const index = currentId ? indexOfId(v, currentId, itemToId) : -1; | |
let items = currentId ? wrap(v, index) : v; | |
const isSingleKey = text.length === 1; | |
if (isSingleKey) { | |
items = items.filter((item) => itemToId(item) !== currentId); | |
} | |
return items.find((item) => match(getValueText(item), text)); | |
} | |
// src/get-by-typeahead.ts | |
function getByTypeaheadImpl(_items, options) { | |
const { state, activeId, key, timeout = 350, itemToId } = options; | |
const search = state.keysSoFar + key; | |
const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]); | |
const query2 = isRepeated ? search[0] : search; | |
let items = _items.slice(); | |
const next = getByText(items, query2, activeId, itemToId); | |
function cleanup() { | |
clearTimeout(state.timer); | |
state.timer = -1; | |
} | |
function update(value) { | |
state.keysSoFar = value; | |
cleanup(); | |
if (value !== "") { | |
state.timer = +setTimeout(() => { | |
update(""); | |
cleanup(); | |
}, timeout); | |
} | |
} | |
update(search); | |
return next; | |
} | |
var getByTypeahead = /* @__PURE__ */ Object.assign(getByTypeaheadImpl, { | |
defaultOptions: { keysSoFar: "", timer: -1 }, | |
isValidEvent: isValidTypeaheadEvent | |
}); | |
function isValidTypeaheadEvent(event) { | |
return event.key.length === 1 && !event.ctrlKey && !event.metaKey; | |
} | |
// src/tabbable.ts | |
var isHTMLElement2 = (element) => typeof element === "object" && element !== null && element.nodeType === 1; | |
var isFrame = (element) => isHTMLElement2(element) && element.tagName === "IFRAME"; | |
function isVisible(el) { | |
if (!isHTMLElement2(el)) return false; | |
return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0; | |
} | |
function hasNegativeTabIndex(element) { | |
const tabIndex = parseInt(element.getAttribute("tabindex") || "0", 10); | |
return tabIndex < 0; | |
} | |
var focusableSelector = "input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], button:not([disabled]), [tabindex], iframe, object, embed, area[href], audio[controls], video[controls], [contenteditable]:not([contenteditable='false']), details > summary:first-of-type"; | |
function isFocusable$1(element) { | |
if (!element || element.closest("[inert]")) return false; | |
return element.matches(focusableSelector) && isVisible(element); | |
} | |
function getTabbables(container, includeContainer) { | |
if (!container) return []; | |
const elements = Array.from(container.querySelectorAll(focusableSelector)); | |
const tabbableElements = elements.filter(isTabbable$1); | |
if (includeContainer && isTabbable$1(container)) { | |
tabbableElements.unshift(container); | |
} | |
tabbableElements.forEach((element, i) => { | |
if (isFrame(element) && element.contentDocument) { | |
const frameBody = element.contentDocument.body; | |
const allFrameTabbable = getTabbables(frameBody); | |
tabbableElements.splice(i, 1, ...allFrameTabbable); | |
} | |
}); | |
if (!tabbableElements.length && includeContainer) { | |
return elements; | |
} | |
return tabbableElements; | |
} | |
function isTabbable$1(el) { | |
if (el != null && el.tabIndex > 0) return true; | |
return isFocusable$1(el) && !hasNegativeTabIndex(el); | |
} | |
function getTabbableEdges(container, includeContainer) { | |
const elements = getTabbables(container, includeContainer); | |
const first = elements[0] || null; | |
const last = elements[elements.length - 1] || null; | |
return [first, last]; | |
} | |
// src/initial-focus.ts | |
function getInitialFocus(options) { | |
const { root, getInitialEl, filter, enabled = true } = options; | |
if (!enabled) return; | |
let node = null; | |
node || (node = typeof getInitialEl === "function" ? getInitialEl() : getInitialEl); | |
node || (node = root?.querySelector("[data-autofocus],[autofocus]")); | |
if (!node) { | |
const tabbables = getTabbables(root); | |
node = filter ? tabbables.filter(filter)[0] : tabbables[0]; | |
} | |
return node || root || void 0; | |
} | |
function isValidTabEvent(event) { | |
const container = event.currentTarget; | |
if (!container) return false; | |
const [firstTabbable, lastTabbable] = getTabbableEdges(container); | |
const doc = container.ownerDocument || document; | |
if (doc.activeElement === firstTabbable && event.shiftKey) return false; | |
if (doc.activeElement === lastTabbable && !event.shiftKey) return false; | |
if (!firstTabbable && !lastTabbable) return false; | |
return true; | |
} | |
// src/is-editable-element.ts | |
function isEditableElement(el) { | |
if (el == null || !isHTMLElement$1(el)) { | |
return false; | |
} | |
try { | |
const win = getWindow$1(el); | |
return el instanceof win.HTMLInputElement && el.selectionStart != null || /(textarea|select)/.test(el.localName) || el.isContentEditable; | |
} catch { | |
return false; | |
} | |
} | |
// src/is-overflow-element.ts | |
var OVERFLOW_RE = /auto|scroll|overlay|hidden|clip/; | |
function isOverflowElement$1(el) { | |
const win = getWindow$1(el); | |
const { overflow, overflowX, overflowY, display } = win.getComputedStyle(el); | |
return OVERFLOW_RE.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display); | |
} | |
// src/raf.ts | |
function nextTick(fn) { | |
const set2 = /* @__PURE__ */ new Set(); | |
function raf2(fn2) { | |
const id = globalThis.requestAnimationFrame(fn2); | |
set2.add(() => globalThis.cancelAnimationFrame(id)); | |
} | |
raf2(() => raf2(fn)); | |
return function cleanup() { | |
set2.forEach((fn2) => fn2()); | |
}; | |
} | |
function raf(fn) { | |
const id = globalThis.requestAnimationFrame(fn); | |
return () => { | |
globalThis.cancelAnimationFrame(id); | |
}; | |
} | |
// src/observe-attributes.ts | |
function observeAttributesImpl(node, options) { | |
if (!node) return; | |
const { attributes, callback: fn } = options; | |
const win = node.ownerDocument.defaultView || window; | |
const obs = new win.MutationObserver((changes) => { | |
for (const change of changes) { | |
if (change.type === "attributes" && change.attributeName && attributes.includes(change.attributeName)) { | |
fn(change); | |
} | |
} | |
}); | |
obs.observe(node, { attributes: true, attributeFilter: attributes }); | |
return () => obs.disconnect(); | |
} | |
function observeAttributes(nodeOrFn, options) { | |
const { defer } = options; | |
const func = defer ? raf : (v) => v(); | |
const cleanups2 = []; | |
cleanups2.push( | |
func(() => { | |
const node = typeof nodeOrFn === "function" ? nodeOrFn() : nodeOrFn; | |
cleanups2.push(observeAttributesImpl(node, options)); | |
}) | |
); | |
return () => { | |
cleanups2.forEach((fn) => fn?.()); | |
}; | |
} | |
// src/query.ts | |
function queryAll(root, selector) { | |
return Array.from(root?.querySelectorAll(selector) ?? []); | |
} | |
function query(root, selector) { | |
return root?.querySelector(selector) ?? null; | |
} | |
// src/scope.ts | |
function createScope(methods) { | |
const screen = { | |
getRootNode: (ctx) => ctx.getRootNode?.() ?? document, | |
getDoc: (ctx) => getDocument(screen.getRootNode(ctx)), | |
getWin: (ctx) => screen.getDoc(ctx).defaultView ?? window, | |
getActiveElement: (ctx) => screen.getDoc(ctx).activeElement, | |
isActiveElement: (ctx, elem) => elem === screen.getActiveElement(ctx), | |
getById: (ctx, id) => screen.getRootNode(ctx).getElementById(id), | |
setValue: (elem, value) => { | |
if (elem == null || value == null) return; | |
const valueAsString = value.toString(); | |
if (elem.value === valueAsString) return; | |
elem.value = value.toString(); | |
} | |
}; | |
return { ...screen, ...methods }; | |
} | |
// src/scroll-into-view.ts | |
function isScrollable(el) { | |
return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth; | |
} | |
function scrollIntoView(el, options) { | |
const { rootEl, ...scrollOptions } = options || {}; | |
if (!el || !rootEl) { | |
return; | |
} | |
if (!isOverflowElement$1(rootEl) || !isScrollable(rootEl)) { | |
return; | |
} | |
el.scrollIntoView(scrollOptions); | |
} | |
// src/set.ts | |
var cleanups = /* @__PURE__ */ new WeakMap(); | |
function set$1(element, key, setup) { | |
if (!cleanups.has(element)) { | |
cleanups.set(element, /* @__PURE__ */ new Map()); | |
} | |
const elementCleanups = cleanups.get(element); | |
const prevCleanup = elementCleanups.get(key); | |
if (!prevCleanup) { | |
elementCleanups.set(key, setup()); | |
return () => { | |
elementCleanups.get(key)?.(); | |
elementCleanups.delete(key); | |
}; | |
} | |
const cleanup = setup(); | |
const nextCleanup = () => { | |
cleanup(); | |
prevCleanup(); | |
elementCleanups.delete(key); | |
}; | |
elementCleanups.set(key, nextCleanup); | |
return () => { | |
const isCurrent = elementCleanups.get(key) === nextCleanup; | |
if (!isCurrent) return; | |
cleanup(); | |
elementCleanups.set(key, prevCleanup); | |
}; | |
} | |
function setStyle(element, style) { | |
if (!element) return () => { | |
}; | |
const setup = () => { | |
const prevStyle = element.style.cssText; | |
Object.assign(element.style, style); | |
return () => { | |
element.style.cssText = prevStyle; | |
}; | |
}; | |
return set$1(element, "style", setup); | |
} | |
// src/wait-for.ts | |
var fps = 1e3 / 60; | |
function waitForElement(query2, cb) { | |
const el = query2(); | |
if (isHTMLElement$1(el) && el.isConnected) { | |
cb(el); | |
return () => void 0; | |
} else { | |
const timerId = setInterval(() => { | |
const el2 = query2(); | |
if (isHTMLElement$1(el2) && el2.isConnected) { | |
cb(el2); | |
clearInterval(timerId); | |
} | |
}, fps); | |
return () => clearInterval(timerId); | |
} | |
} | |
function waitForElements(queries, cb) { | |
const cleanups2 = []; | |
queries?.forEach((query2) => { | |
const clean = waitForElement(query2, cb); | |
cleanups2.push(clean); | |
}); | |
return () => { | |
cleanups2.forEach((fn) => fn()); | |
}; | |
} | |
// src/array.ts | |
var first = (v) => v[0]; | |
var last = (v) => v[v.length - 1]; | |
// src/equal.ts | |
var isArrayLike = (value) => value?.constructor.name === "Array"; | |
var isEqual = (a, b) => { | |
if (Object.is(a, b)) return true; | |
if (a == null && b != null || a != null && b == null) return false; | |
if (typeof a?.isEqual === "function" && typeof b?.isEqual === "function") { | |
return a.isEqual(b); | |
} | |
if (typeof a === "function" && typeof b === "function") { | |
return a.toString() === b.toString(); | |
} | |
if (isArrayLike(a) && isArrayLike(b)) { | |
return Array.from(a).toString() === Array.from(b).toString(); | |
} | |
if (!(typeof a === "object") || !(typeof b === "object")) return false; | |
const keys = Object.keys(b ?? /* @__PURE__ */ Object.create(null)); | |
const length = keys.length; | |
for (let i = 0; i < length; i++) { | |
const hasKey = Reflect.has(a, keys[i]); | |
if (!hasKey) return false; | |
} | |
for (let i = 0; i < length; i++) { | |
const key = keys[i]; | |
if (!isEqual(a[key], b[key])) return false; | |
} | |
return true; | |
}; | |
// src/functions.ts | |
var runIfFn = (v, ...a) => { | |
const res = typeof v === "function" ? v(...a) : v; | |
return res ?? void 0; | |
}; | |
var cast = (v) => v; | |
var noop = () => { | |
}; | |
var callAll = (...fns) => (...a) => { | |
fns.forEach(function(fn) { | |
fn?.(...a); | |
}); | |
}; | |
var uuid = /* @__PURE__ */ (() => { | |
let id = 0; | |
return () => { | |
id++; | |
return id.toString(36); | |
}; | |
})(); | |
var isNumber = (v) => typeof v === "number" && !Number.isNaN(v); | |
var isFunction = (v) => typeof v === "function"; | |
var isNull = (v) => v == null; | |
// src/object.ts | |
function compact(obj) { | |
if (!isPlainObject(obj) || obj === void 0) { | |
return obj; | |
} | |
const keys = Reflect.ownKeys(obj).filter((key) => typeof key === "string"); | |
const filtered = {}; | |
for (const key of keys) { | |
const value = obj[key]; | |
if (value !== void 0) { | |
filtered[key] = compact(value); | |
} | |
} | |
return filtered; | |
} | |
var isPlainObject = (value) => { | |
return value && typeof value === "object" && value.constructor === Object; | |
}; | |
// src/warning.ts | |
function warn(...a) { | |
const m = a.length === 1 ? a[0] : a[1]; | |
const c = a.length === 2 ? a[0] : true; | |
if (c && process.env.NODE_ENV !== "production") { | |
console.warn(m); | |
} | |
} | |
// src/add-dom-event.ts | |
var addDomEvent = (target, eventName, handler, options) => { | |
const node = typeof target === "function" ? target() : target; | |
node?.addEventListener(eventName, handler, options); | |
return () => { | |
node?.removeEventListener(eventName, handler, options); | |
}; | |
}; | |
function isPrintableKey(e) { | |
return e.key.length === 1 && !e.ctrlKey && !e.metaKey; | |
} | |
var isLeftClick = (e) => e.button === 0; | |
var isContextMenuEvent = (e) => { | |
return e.button === 2 || isMac() && e.ctrlKey && e.button === 0; | |
}; | |
var isModifierKey = (e) => e.ctrlKey || e.altKey || e.metaKey; | |
// src/queue-before-event.ts | |
function queueBeforeEvent(element, type, cb) { | |
const createTimer = (callback) => { | |
const timerId = requestAnimationFrame(callback); | |
return () => cancelAnimationFrame(timerId); | |
}; | |
const cancelTimer = createTimer(() => { | |
element.removeEventListener(type, callSync, true); | |
cb(); | |
}); | |
const callSync = () => { | |
cancelTimer(); | |
cb(); | |
}; | |
element.addEventListener(type, callSync, { once: true, capture: true }); | |
return cancelTimer; | |
} | |
// src/click-link.ts | |
function isLinkElement(element) { | |
return element?.matches("a[href]") ?? false; | |
} | |
function clickIfLink(element) { | |
if (!isLinkElement(element)) return; | |
const click = () => element.click(); | |
if (isFirefox()) { | |
queueBeforeEvent(element, "keyup", click); | |
} else { | |
queueMicrotask(click); | |
} | |
} | |
// src/fire-event.ts | |
function fireCustomEvent(el, type, init) { | |
if (!el) return; | |
const win = el.ownerDocument.defaultView || window; | |
const event = new win.CustomEvent(type, init); | |
return el.dispatchEvent(event); | |
} | |
// src/get-event-key.ts | |
var keyMap = { | |
Up: "ArrowUp", | |
Down: "ArrowDown", | |
Esc: "Escape", | |
" ": "Space", | |
",": "Comma", | |
Left: "ArrowLeft", | |
Right: "ArrowRight" | |
}; | |
var rtlKeyMap = { | |
ArrowLeft: "ArrowRight", | |
ArrowRight: "ArrowLeft" | |
}; | |
function getEventKey(event, options = {}) { | |
const { dir = "ltr", orientation = "horizontal" } = options; | |
let { key } = event; | |
key = keyMap[key] ?? key; | |
const isRtl = dir === "rtl" && orientation === "horizontal"; | |
if (isRtl && key in rtlKeyMap) { | |
key = rtlKeyMap[key]; | |
} | |
return key; | |
} | |
// src/get-event-point.ts | |
function pointFromTouch(e, type = "client") { | |
const point = e.touches[0] || e.changedTouches[0]; | |
return { x: point[`${type}X`], y: point[`${type}Y`] }; | |
} | |
function pointFromMouse(point, type = "client") { | |
return { x: point[`${type}X`], y: point[`${type}Y`] }; | |
} | |
var isTouchEvent = (event) => "touches" in event && event.touches.length > 0; | |
function getEventPoint(event, type = "client") { | |
return isTouchEvent(event) ? pointFromTouch(event, type) : pointFromMouse(event, type); | |
} | |
/** | |
* Custom positioning reference element. | |
* @see https://floating-ui.com/docs/virtual-elements | |
*/ | |
const min = Math.min; | |
const max = Math.max; | |
const round = Math.round; | |
const floor = Math.floor; | |
const createCoords = v => ({ | |
x: v, | |
y: v | |
}); | |
const oppositeSideMap = { | |
left: 'right', | |
right: 'left', | |
bottom: 'top', | |
top: 'bottom' | |
}; | |
const oppositeAlignmentMap = { | |
start: 'end', | |
end: 'start' | |
}; | |
function clamp(start, value, end) { | |
return max(start, min(value, end)); | |
} | |
function evaluate(value, param) { | |
return typeof value === 'function' ? value(param) : value; | |
} | |
function getSide(placement) { | |
return placement.split('-')[0]; | |
} | |
function getAlignment(placement) { | |
return placement.split('-')[1]; | |
} | |
function getOppositeAxis(axis) { | |
return axis === 'x' ? 'y' : 'x'; | |
} | |
function getAxisLength(axis) { | |
return axis === 'y' ? 'height' : 'width'; | |
} | |
function getSideAxis(placement) { | |
return ['top', 'bottom'].includes(getSide(placement)) ? 'y' : 'x'; | |
} | |
function getAlignmentAxis(placement) { | |
return getOppositeAxis(getSideAxis(placement)); | |
} | |
function getAlignmentSides(placement, rects, rtl) { | |
if (rtl === void 0) { | |
rtl = false; | |
} | |
const alignment = getAlignment(placement); | |
const alignmentAxis = getAlignmentAxis(placement); | |
const length = getAxisLength(alignmentAxis); | |
let mainAlignmentSide = alignmentAxis === 'x' ? alignment === (rtl ? 'end' : 'start') ? 'right' : 'left' : alignment === 'start' ? 'bottom' : 'top'; | |
if (rects.reference[length] > rects.floating[length]) { | |
mainAlignmentSide = getOppositePlacement(mainAlignmentSide); | |
} | |
return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; | |
} | |
function getExpandedPlacements(placement) { | |
const oppositePlacement = getOppositePlacement(placement); | |
return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; | |
} | |
function getOppositeAlignmentPlacement(placement) { | |
return placement.replace(/start|end/g, alignment => oppositeAlignmentMap[alignment]); | |
} | |
function getSideList(side, isStart, rtl) { | |
const lr = ['left', 'right']; | |
const rl = ['right', 'left']; | |
const tb = ['top', 'bottom']; | |
const bt = ['bottom', 'top']; | |
switch (side) { | |
case 'top': | |
case 'bottom': | |
if (rtl) return isStart ? rl : lr; | |
return isStart ? lr : rl; | |
case 'left': | |
case 'right': | |
return isStart ? tb : bt; | |
default: | |
return []; | |
} | |
} | |
function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { | |
const alignment = getAlignment(placement); | |
let list = getSideList(getSide(placement), direction === 'start', rtl); | |
if (alignment) { | |
list = list.map(side => side + "-" + alignment); | |
if (flipAlignment) { | |
list = list.concat(list.map(getOppositeAlignmentPlacement)); | |
} | |
} | |
return list; | |
} | |
function getOppositePlacement(placement) { | |
return placement.replace(/left|right|bottom|top/g, side => oppositeSideMap[side]); | |
} | |
function expandPaddingObject(padding) { | |
return { | |
top: 0, | |
right: 0, | |
bottom: 0, | |
left: 0, | |
...padding | |
}; | |
} | |
function getPaddingObject(padding) { | |
return typeof padding !== 'number' ? expandPaddingObject(padding) : { | |
top: padding, | |
right: padding, | |
bottom: padding, | |
left: padding | |
}; | |
} | |
function rectToClientRect(rect) { | |
return { | |
...rect, | |
top: rect.y, | |
left: rect.x, | |
right: rect.x + rect.width, | |
bottom: rect.y + rect.height | |
}; | |
} | |
function computeCoordsFromPlacement(_ref, placement, rtl) { | |
let { | |
reference, | |
floating | |
} = _ref; | |
const sideAxis = getSideAxis(placement); | |
const alignmentAxis = getAlignmentAxis(placement); | |
const alignLength = getAxisLength(alignmentAxis); | |
const side = getSide(placement); | |
const isVertical = sideAxis === 'y'; | |
const commonX = reference.x + reference.width / 2 - floating.width / 2; | |
const commonY = reference.y + reference.height / 2 - floating.height / 2; | |
const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; | |
let coords; | |
switch (side) { | |
case 'top': | |
coords = { | |
x: commonX, | |
y: reference.y - floating.height | |
}; | |
break; | |
case 'bottom': | |
coords = { | |
x: commonX, | |
y: reference.y + reference.height | |
}; | |
break; | |
case 'right': | |
coords = { | |
x: reference.x + reference.width, | |
y: commonY | |
}; | |
break; | |
case 'left': | |
coords = { | |
x: reference.x - floating.width, | |
y: commonY | |
}; | |
break; | |
default: | |
coords = { | |
x: reference.x, | |
y: reference.y | |
}; | |
} | |
switch (getAlignment(placement)) { | |
case 'start': | |
coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); | |
break; | |
case 'end': | |
coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1); | |
break; | |
} | |
return coords; | |
} | |
/** | |
* Computes the `x` and `y` coordinates that will place the floating element | |
* next to a given reference element. | |
* | |
* This export does not have any `platform` interface logic. You will need to | |
* write one for the platform you are using Floating UI with. | |
*/ | |
const computePosition$1 = async (reference, floating, config) => { | |
const { | |
placement = 'bottom', | |
strategy = 'absolute', | |
middleware = [], | |
platform | |
} = config; | |
const validMiddleware = middleware.filter(Boolean); | |
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating)); | |
let rects = await platform.getElementRects({ | |
reference, | |
floating, | |
strategy | |
}); | |
let { | |
x, | |
y | |
} = computeCoordsFromPlacement(rects, placement, rtl); | |
let statefulPlacement = placement; | |
let middlewareData = {}; | |
let resetCount = 0; | |
for (let i = 0; i < validMiddleware.length; i++) { | |
const { | |
name, | |
fn | |
} = validMiddleware[i]; | |
const { | |
x: nextX, | |
y: nextY, | |
data, | |
reset | |
} = await fn({ | |
x, | |
y, | |
initialPlacement: placement, | |
placement: statefulPlacement, | |
strategy, | |
middlewareData, | |
rects, | |
platform, | |
elements: { | |
reference, | |
floating | |
} | |
}); | |
x = nextX != null ? nextX : x; | |
y = nextY != null ? nextY : y; | |
middlewareData = { | |
...middlewareData, | |
[name]: { | |
...middlewareData[name], | |
...data | |
} | |
}; | |
if (reset && resetCount <= 50) { | |
resetCount++; | |
if (typeof reset === 'object') { | |
if (reset.placement) { | |
statefulPlacement = reset.placement; | |
} | |
if (reset.rects) { | |
rects = reset.rects === true ? await platform.getElementRects({ | |
reference, | |
floating, | |
strategy | |
}) : reset.rects; | |
} | |
({ | |
x, | |
y | |
} = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); | |
} | |
i = -1; | |
} | |
} | |
return { | |
x, | |
y, | |
placement: statefulPlacement, | |
strategy, | |
middlewareData | |
}; | |
}; | |
/** | |
* Resolves with an object of overflow side offsets that determine how much the | |
* element is overflowing a given clipping boundary on each side. | |
* - positive = overflowing the boundary by that number of pixels | |
* - negative = how many pixels left before it will overflow | |
* - 0 = lies flush with the boundary | |
* @see https://floating-ui.com/docs/detectOverflow | |
*/ | |
async function detectOverflow(state, options) { | |
var _await$platform$isEle; | |
if (options === void 0) { | |
options = {}; | |
} | |
const { | |
x, | |
y, | |
platform, | |
rects, | |
elements, | |
strategy | |
} = state; | |
const { | |
boundary = 'clippingAncestors', | |
rootBoundary = 'viewport', | |
elementContext = 'floating', | |
altBoundary = false, | |
padding = 0 | |
} = evaluate(options, state); | |
const paddingObject = getPaddingObject(padding); | |
const altContext = elementContext === 'floating' ? 'reference' : 'floating'; | |
const element = elements[altBoundary ? altContext : elementContext]; | |
const clippingClientRect = rectToClientRect(await platform.getClippingRect({ | |
element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))), | |
boundary, | |
rootBoundary, | |
strategy | |
})); | |
const rect = elementContext === 'floating' ? { | |
...rects.floating, | |
x, | |
y | |
} : rects.reference; | |
const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating)); | |
const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || { | |
x: 1, | |
y: 1 | |
} : { | |
x: 1, | |
y: 1 | |
}; | |
const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({ | |
elements, | |
rect, | |
offsetParent, | |
strategy | |
}) : rect); | |
return { | |
top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y, | |
bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y, | |
left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x, | |
right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x | |
}; | |
} | |
/** | |
* Provides data to position an inner element of the floating element so that it | |
* appears centered to the reference element. | |
* @see https://floating-ui.com/docs/arrow | |
*/ | |
const arrow$1 = options => ({ | |
name: 'arrow', | |
options, | |
async fn(state) { | |
const { | |
x, | |
y, | |
placement, | |
rects, | |
platform, | |
elements, | |
middlewareData | |
} = state; | |
// Since `element` is required, we don't Partial<> the type. | |
const { | |
element, | |
padding = 0 | |
} = evaluate(options, state) || {}; | |
if (element == null) { | |
return {}; | |
} | |
const paddingObject = getPaddingObject(padding); | |
const coords = { | |
x, | |
y | |
}; | |
const axis = getAlignmentAxis(placement); | |
const length = getAxisLength(axis); | |
const arrowDimensions = await platform.getDimensions(element); | |
const isYAxis = axis === 'y'; | |
const minProp = isYAxis ? 'top' : 'left'; | |
const maxProp = isYAxis ? 'bottom' : 'right'; | |
const clientProp = isYAxis ? 'clientHeight' : 'clientWidth'; | |
const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length]; | |
const startDiff = coords[axis] - rects.reference[axis]; | |
const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element)); | |
let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0; | |
// DOM platform can return `window` as the `offsetParent`. | |
if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) { | |
clientSize = elements.floating[clientProp] || rects.floating[length]; | |
} | |
const centerToReference = endDiff / 2 - startDiff / 2; | |
// If the padding is large enough that it causes the arrow to no longer be | |
// centered, modify the padding so that it is centered. | |
const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1; | |
const minPadding = min(paddingObject[minProp], largestPossiblePadding); | |
const maxPadding = min(paddingObject[maxProp], largestPossiblePadding); | |
// Make sure the arrow doesn't overflow the floating element if the center | |
// point is outside the floating element's bounds. | |
const min$1 = minPadding; | |
const max = clientSize - arrowDimensions[length] - maxPadding; | |
const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference; | |
const offset = clamp(min$1, center, max); | |
// If the reference is small enough that the arrow's padding causes it to | |
// to point to nothing for an aligned placement, adjust the offset of the | |
// floating element itself. To ensure `shift()` continues to take action, | |
// a single reset is performed when this is true. | |
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0; | |
const alignmentOffset = shouldAddOffset ? center < min$1 ? center - min$1 : center - max : 0; | |
return { | |
[axis]: coords[axis] + alignmentOffset, | |
data: { | |
[axis]: offset, | |
centerOffset: center - offset - alignmentOffset, | |
...(shouldAddOffset && { | |
alignmentOffset | |
}) | |
}, | |
reset: shouldAddOffset | |
}; | |
} | |
}); | |
/** | |
* Optimizes the visibility of the floating element by flipping the `placement` | |
* in order to keep it in view when the preferred placement(s) will overflow the | |
* clipping boundary. Alternative to `autoPlacement`. | |
* @see https://floating-ui.com/docs/flip | |
*/ | |
const flip$1 = function (options) { | |
if (options === void 0) { | |
options = {}; | |
} | |
return { | |
name: 'flip', | |
options, | |
async fn(state) { | |
var _middlewareData$arrow, _middlewareData$flip; | |
const { | |
placement, | |
middlewareData, | |
rects, | |
initialPlacement, | |
platform, | |
elements | |
} = state; | |
const { | |
mainAxis: checkMainAxis = true, | |
crossAxis: checkCrossAxis = true, | |
fallbackPlacements: specifiedFallbackPlacements, | |
fallbackStrategy = 'bestFit', | |
fallbackAxisSideDirection = 'none', | |
flipAlignment = true, | |
...detectOverflowOptions | |
} = evaluate(options, state); | |
// If a reset by the arrow was caused due to an alignment offset being | |
// added, we should skip any logic now since `flip()` has already done its | |
// work. | |
// https://github.com/floating-ui/floating-ui/issues/2549#issuecomment-1719601643 | |
if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { | |
return {}; | |
} | |
const side = getSide(placement); | |
const isBasePlacement = getSide(initialPlacement) === initialPlacement; | |
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)); | |
const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); | |
if (!specifiedFallbackPlacements && fallbackAxisSideDirection !== 'none') { | |
fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); | |
} | |
const placements = [initialPlacement, ...fallbackPlacements]; | |
const overflow = await detectOverflow(state, detectOverflowOptions); | |
const overflows = []; | |
let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; | |
if (checkMainAxis) { | |
overflows.push(overflow[side]); | |
} | |
if (checkCrossAxis) { | |
const sides = getAlignmentSides(placement, rects, rtl); | |
overflows.push(overflow[sides[0]], overflow[sides[1]]); | |
} | |
overflowsData = [...overflowsData, { | |
placement, | |
overflows | |
}]; | |
// One or more sides is overflowing. | |
if (!overflows.every(side => side <= 0)) { | |
var _middlewareData$flip2, _overflowsData$filter; | |
const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1; | |
const nextPlacement = placements[nextIndex]; | |
if (nextPlacement) { | |
// Try next placement and re-run the lifecycle. | |
return { | |
data: { | |
index: nextIndex, | |
overflows: overflowsData | |
}, | |
reset: { | |
placement: nextPlacement | |
} | |
}; | |
} | |
// First, find the candidates that fit on the mainAxis side of overflow, | |
// then find the placement that fits the best on the main crossAxis side. | |
let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement; | |
// Otherwise fallback. | |
if (!resetPlacement) { | |
switch (fallbackStrategy) { | |
case 'bestFit': | |
{ | |
var _overflowsData$map$so; | |
const placement = (_overflowsData$map$so = overflowsData.map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$map$so[0]; | |
if (placement) { | |
resetPlacement = placement; | |
} | |
break; | |
} | |
case 'initialPlacement': | |
resetPlacement = initialPlacement; | |
break; | |
} | |
} | |
if (placement !== resetPlacement) { | |
return { | |
reset: { | |
placement: resetPlacement | |
} | |
}; | |
} | |
} | |
return {}; | |
} | |
}; | |
}; | |
// For type backwards-compatibility, the `OffsetOptions` type was also | |
// Derivable. | |
async function convertValueToCoords(state, options) { | |
const { | |
placement, | |
platform, | |
elements | |
} = state; | |
const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)); | |
const side = getSide(placement); | |
const alignment = getAlignment(placement); | |
const isVertical = getSideAxis(placement) === 'y'; | |
const mainAxisMulti = ['left', 'top'].includes(side) ? -1 : 1; | |
const crossAxisMulti = rtl && isVertical ? -1 : 1; | |
const rawValue = evaluate(options, state); | |
let { | |
mainAxis, | |
crossAxis, | |
alignmentAxis | |
} = typeof rawValue === 'number' ? { | |
mainAxis: rawValue, | |
crossAxis: 0, | |
alignmentAxis: null | |
} : { | |
mainAxis: 0, | |
crossAxis: 0, | |
alignmentAxis: null, | |
...rawValue | |
}; | |
if (alignment && typeof alignmentAxis === 'number') { | |
crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis; | |
} | |
return isVertical ? { | |
x: crossAxis * crossAxisMulti, | |
y: mainAxis * mainAxisMulti | |
} : { | |
x: mainAxis * mainAxisMulti, | |
y: crossAxis * crossAxisMulti | |
}; | |
} | |
/** | |
* Modifies the placement by translating the floating element along the | |
* specified axes. | |
* A number (shorthand for `mainAxis` or distance), or an axes configuration | |
* object may be passed. | |
* @see https://floating-ui.com/docs/offset | |
*/ | |
const offset$1 = function (options) { | |
if (options === void 0) { | |
options = 0; | |
} | |
return { | |
name: 'offset', | |
options, | |
async fn(state) { | |
var _middlewareData$offse, _middlewareData$arrow; | |
const { | |
x, | |
y, | |
placement, | |
middlewareData | |
} = state; | |
const diffCoords = await convertValueToCoords(state, options); | |
// If the placement is the same and the arrow caused an alignment offset | |
// then we don't need to change the positioning coordinates. | |
if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { | |
return {}; | |
} | |
return { | |
x: x + diffCoords.x, | |
y: y + diffCoords.y, | |
data: { | |
...diffCoords, | |
placement | |
} | |
}; | |
} | |
}; | |
}; | |
/** | |
* Optimizes the visibility of the floating element by shifting it in order to | |
* keep it in view when it will overflow the clipping boundary. | |
* @see https://floating-ui.com/docs/shift | |
*/ | |
const shift$1 = function (options) { | |
if (options === void 0) { | |
options = {}; | |
} | |
return { | |
name: 'shift', | |
options, | |
async fn(state) { | |
const { | |
x, | |
y, | |
placement | |
} = state; | |
const { | |
mainAxis: checkMainAxis = true, | |
crossAxis: checkCrossAxis = false, | |
limiter = { | |
fn: _ref => { | |
let { | |
x, | |
y | |
} = _ref; | |
return { | |
x, | |
y | |
}; | |
} | |
}, | |
...detectOverflowOptions | |
} = evaluate(options, state); | |
const coords = { | |
x, | |
y | |
}; | |
const overflow = await detectOverflow(state, detectOverflowOptions); | |
const crossAxis = getSideAxis(getSide(placement)); | |
const mainAxis = getOppositeAxis(crossAxis); | |
let mainAxisCoord = coords[mainAxis]; | |
let crossAxisCoord = coords[crossAxis]; | |
if (checkMainAxis) { | |
const minSide = mainAxis === 'y' ? 'top' : 'left'; | |
const maxSide = mainAxis === 'y' ? 'bottom' : 'right'; | |
const min = mainAxisCoord + overflow[minSide]; | |
const max = mainAxisCoord - overflow[maxSide]; | |
mainAxisCoord = clamp(min, mainAxisCoord, max); | |
} | |
if (checkCrossAxis) { | |
const minSide = crossAxis === 'y' ? 'top' : 'left'; | |
const maxSide = crossAxis === 'y' ? 'bottom' : 'right'; | |
const min = crossAxisCoord + overflow[minSide]; | |
const max = crossAxisCoord - overflow[maxSide]; | |
crossAxisCoord = clamp(min, crossAxisCoord, max); | |
} | |
const limitedCoords = limiter.fn({ | |
...state, | |
[mainAxis]: mainAxisCoord, | |
[crossAxis]: crossAxisCoord | |
}); | |
return { | |
...limitedCoords, | |
data: { | |
x: limitedCoords.x - x, | |
y: limitedCoords.y - y | |
} | |
}; | |
} | |
}; | |
}; | |
/** | |
* Built-in `limiter` that will stop `shift()` at a certain point. | |
*/ | |
const limitShift$1 = function (options) { | |
if (options === void 0) { | |
options = {}; | |
} | |
return { | |
options, | |
fn(state) { | |
const { | |
x, | |
y, | |
placement, | |
rects, | |
middlewareData | |
} = state; | |
const { | |
offset = 0, | |
mainAxis: checkMainAxis = true, | |
crossAxis: checkCrossAxis = true | |
} = evaluate(options, state); | |
const coords = { | |
x, | |
y | |
}; | |
const crossAxis = getSideAxis(placement); | |
const mainAxis = getOppositeAxis(crossAxis); | |
let mainAxisCoord = coords[mainAxis]; | |
let crossAxisCoord = coords[crossAxis]; | |
const rawOffset = evaluate(offset, state); | |
const computedOffset = typeof rawOffset === 'number' ? { | |
mainAxis: rawOffset, | |
crossAxis: 0 | |
} : { | |
mainAxis: 0, | |
crossAxis: 0, | |
...rawOffset | |
}; | |
if (checkMainAxis) { | |
const len = mainAxis === 'y' ? 'height' : 'width'; | |
const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis; | |
const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis; | |
if (mainAxisCoord < limitMin) { | |
mainAxisCoord = limitMin; | |
} else if (mainAxisCoord > limitMax) { | |
mainAxisCoord = limitMax; | |
} | |
} | |
if (checkCrossAxis) { | |
var _middlewareData$offse, _middlewareData$offse2; | |
const len = mainAxis === 'y' ? 'width' : 'height'; | |
const isOriginSide = ['top', 'left'].includes(getSide(placement)); | |
const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis); | |
const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0); | |
if (crossAxisCoord < limitMin) { | |
crossAxisCoord = limitMin; | |
} else if (crossAxisCoord > limitMax) { | |
crossAxisCoord = limitMax; | |
} | |
} | |
return { | |
[mainAxis]: mainAxisCoord, | |
[crossAxis]: crossAxisCoord | |
}; | |
} | |
}; | |
}; | |
/** | |
* Provides data that allows you to change the size of the floating element — | |
* for instance, prevent it from overflowing the clipping boundary or match the | |
* width of the reference element. | |
* @see https://floating-ui.com/docs/size | |
*/ | |
const size$1 = function (options) { | |
if (options === void 0) { | |
options = {}; | |
} | |
return { | |
name: 'size', | |
options, | |
async fn(state) { | |
const { | |
placement, | |
rects, | |
platform, | |
elements | |
} = state; | |
const { | |
apply = () => {}, | |
...detectOverflowOptions | |
} = evaluate(options, state); | |
const overflow = await detectOverflow(state, detectOverflowOptions); | |
const side = getSide(placement); | |
const alignment = getAlignment(placement); | |
const isYAxis = getSideAxis(placement) === 'y'; | |
const { | |
width, | |
height | |
} = rects.floating; | |
let heightSide; | |
let widthSide; | |
if (side === 'top' || side === 'bottom') { | |
heightSide = side; | |
widthSide = alignment === ((await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating))) ? 'start' : 'end') ? 'left' : 'right'; | |
} else { | |
widthSide = side; | |
heightSide = alignment === 'end' ? 'top' : 'bottom'; | |
} | |
const overflowAvailableHeight = height - overflow[heightSide]; | |
const overflowAvailableWidth = width - overflow[widthSide]; | |
const noShift = !state.middlewareData.shift; | |
let availableHeight = overflowAvailableHeight; | |
let availableWidth = overflowAvailableWidth; | |
if (isYAxis) { | |
const maximumClippingWidth = width - overflow.left - overflow.right; | |
availableWidth = alignment || noShift ? min(overflowAvailableWidth, maximumClippingWidth) : maximumClippingWidth; | |
} else { | |
const maximumClippingHeight = height - overflow.top - overflow.bottom; | |
availableHeight = alignment || noShift ? min(overflowAvailableHeight, maximumClippingHeight) : maximumClippingHeight; | |
} | |
if (noShift && !alignment) { | |
const xMin = max(overflow.left, 0); | |
const xMax = max(overflow.right, 0); | |
const yMin = max(overflow.top, 0); | |
const yMax = max(overflow.bottom, 0); | |
if (isYAxis) { | |
availableWidth = width - 2 * (xMin !== 0 || xMax !== 0 ? xMin + xMax : max(overflow.left, overflow.right)); | |
} else { | |
availableHeight = height - 2 * (yMin !== 0 || yMax !== 0 ? yMin + yMax : max(overflow.top, overflow.bottom)); | |
} | |
} | |
await apply({ | |
...state, | |
availableWidth, | |
availableHeight | |
}); | |
const nextDimensions = await platform.getDimensions(elements.floating); | |
if (width !== nextDimensions.width || height !== nextDimensions.height) { | |
return { | |
reset: { | |
rects: true | |
} | |
}; | |
} | |
return {}; | |
} | |
}; | |
}; | |
function getNodeName(node) { | |
if (isNode(node)) { | |
return (node.nodeName || '').toLowerCase(); | |
} | |
// Mocked nodes in testing environments may not be instances of Node. By | |
// returning `#document` an infinite loop won't occur. | |
// https://github.com/floating-ui/floating-ui/issues/2317 | |
return '#document'; | |
} | |
function getWindow(node) { | |
var _node$ownerDocument; | |
return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; | |
} | |
function getDocumentElement(node) { | |
var _ref; | |
return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; | |
} | |
function isNode(value) { | |
return value instanceof Node || value instanceof getWindow(value).Node; | |
} | |
function isElement(value) { | |
return value instanceof Element || value instanceof getWindow(value).Element; | |
} | |
function isHTMLElement(value) { | |
return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement; | |
} | |
function isShadowRoot(value) { | |
// Browsers without `ShadowRoot` support. | |
if (typeof ShadowRoot === 'undefined') { | |
return false; | |
} | |
return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot; | |
} | |
function isOverflowElement(element) { | |
const { | |
overflow, | |
overflowX, | |
overflowY, | |
display | |
} = getComputedStyle$1(element); | |
return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); | |
} | |
function isTableElement(element) { | |
return ['table', 'td', 'th'].includes(getNodeName(element)); | |
} | |
function isContainingBlock(element) { | |
const webkit = isWebKit(); | |
const css = getComputedStyle$1(element); | |
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block | |
return css.transform !== 'none' || css.perspective !== 'none' || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); | |
} | |
function getContainingBlock(element) { | |
let currentNode = getParentNode(element); | |
while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { | |
if (isContainingBlock(currentNode)) { | |
return currentNode; | |
} else { | |
currentNode = getParentNode(currentNode); | |
} | |
} | |
return null; | |
} | |
function isWebKit() { | |
if (typeof CSS === 'undefined' || !CSS.supports) return false; | |
return CSS.supports('-webkit-backdrop-filter', 'none'); | |
} | |
function isLastTraversableNode(node) { | |
return ['html', 'body', '#document'].includes(getNodeName(node)); | |
} | |
function getComputedStyle$1(element) { | |
return getWindow(element).getComputedStyle(element); | |
} | |
function getNodeScroll(element) { | |
if (isElement(element)) { | |
return { | |
scrollLeft: element.scrollLeft, | |
scrollTop: element.scrollTop | |
}; | |
} | |
return { | |
scrollLeft: element.pageXOffset, | |
scrollTop: element.pageYOffset | |
}; | |
} | |
function getParentNode(node) { | |
if (getNodeName(node) === 'html') { | |
return node; | |
} | |
const result = | |
// Step into the shadow DOM of the parent of a slotted node. | |
node.assignedSlot || | |
// DOM Element detected. | |
node.parentNode || | |
// ShadowRoot detected. | |
isShadowRoot(node) && node.host || | |
// Fallback. | |
getDocumentElement(node); | |
return isShadowRoot(result) ? result.host : result; | |
} | |
function getNearestOverflowAncestor(node) { | |
const parentNode = getParentNode(node); | |
if (isLastTraversableNode(parentNode)) { | |
return node.ownerDocument ? node.ownerDocument.body : node.body; | |
} | |
if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) { | |
return parentNode; | |
} | |
return getNearestOverflowAncestor(parentNode); | |
} | |
function getOverflowAncestors(node, list, traverseIframes) { | |
var _node$ownerDocument2; | |
if (list === void 0) { | |
list = []; | |
} | |
if (traverseIframes === void 0) { | |
traverseIframes = true; | |
} | |
const scrollableAncestor = getNearestOverflowAncestor(node); | |
const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); | |
const win = getWindow(scrollableAncestor); | |
if (isBody) { | |
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], win.frameElement && traverseIframes ? getOverflowAncestors(win.frameElement) : []); | |
} | |
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes)); | |
} | |
function getCssDimensions(element) { | |
const css = getComputedStyle$1(element); | |
// In testing environments, the `width` and `height` properties are empty | |
// strings for SVG elements, returning NaN. Fallback to `0` in this case. | |
let width = parseFloat(css.width) || 0; | |
let height = parseFloat(css.height) || 0; | |
const hasOffset = isHTMLElement(element); | |
const offsetWidth = hasOffset ? element.offsetWidth : width; | |
const offsetHeight = hasOffset ? element.offsetHeight : height; | |
const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight; | |
if (shouldFallback) { | |
width = offsetWidth; | |
height = offsetHeight; | |
} | |
return { | |
width, | |
height, | |
$: shouldFallback | |
}; | |
} | |
function unwrapElement(element) { | |
return !isElement(element) ? element.contextElement : element; | |
} | |
function getScale(element) { | |
const domElement = unwrapElement(element); | |
if (!isHTMLElement(domElement)) { | |
return createCoords(1); | |
} | |
const rect = domElement.getBoundingClientRect(); | |
const { | |
width, | |
height, | |
$ | |
} = getCssDimensions(domElement); | |
let x = ($ ? round(rect.width) : rect.width) / width; | |
let y = ($ ? round(rect.height) : rect.height) / height; | |
// 0, NaN, or Infinity should always fallback to 1. | |
if (!x || !Number.isFinite(x)) { | |
x = 1; | |
} | |
if (!y || !Number.isFinite(y)) { | |
y = 1; | |
} | |
return { | |
x, | |
y | |
}; | |
} | |
const noOffsets = /*#__PURE__*/createCoords(0); | |
function getVisualOffsets(element) { | |
const win = getWindow(element); | |
if (!isWebKit() || !win.visualViewport) { | |
return noOffsets; | |
} | |
return { | |
x: win.visualViewport.offsetLeft, | |
y: win.visualViewport.offsetTop | |
}; | |
} | |
function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { | |
if (isFixed === void 0) { | |
isFixed = false; | |
} | |
if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) { | |
return false; | |
} | |
return isFixed; | |
} | |
function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { | |
if (includeScale === void 0) { | |
includeScale = false; | |
} | |
if (isFixedStrategy === void 0) { | |
isFixedStrategy = false; | |
} | |
const clientRect = element.getBoundingClientRect(); | |
const domElement = unwrapElement(element); | |
let scale = createCoords(1); | |
if (includeScale) { | |
if (offsetParent) { | |
if (isElement(offsetParent)) { | |
scale = getScale(offsetParent); | |
} | |
} else { | |
scale = getScale(element); | |
} | |
} | |
const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); | |
let x = (clientRect.left + visualOffsets.x) / scale.x; | |
let y = (clientRect.top + visualOffsets.y) / scale.y; | |
let width = clientRect.width / scale.x; | |
let height = clientRect.height / scale.y; | |
if (domElement) { | |
const win = getWindow(domElement); | |
const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent; | |
let currentWin = win; | |
let currentIFrame = currentWin.frameElement; | |
while (currentIFrame && offsetParent && offsetWin !== currentWin) { | |
const iframeScale = getScale(currentIFrame); | |
const iframeRect = currentIFrame.getBoundingClientRect(); | |
const css = getComputedStyle$1(currentIFrame); | |
const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; | |
const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; | |
x *= iframeScale.x; | |
y *= iframeScale.y; | |
width *= iframeScale.x; | |
height *= iframeScale.y; | |
x += left; | |
y += top; | |
currentWin = getWindow(currentIFrame); | |
currentIFrame = currentWin.frameElement; | |
} | |
} | |
return rectToClientRect({ | |
width, | |
height, | |
x, | |
y | |
}); | |
} | |
const topLayerSelectors = [':popover-open', ':modal']; | |
function isTopLayer(element) { | |
return topLayerSelectors.some(selector => { | |
try { | |
return element.matches(selector); | |
} catch (e) { | |
return false; | |
} | |
}); | |
} | |
function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { | |
let { | |
elements, | |
rect, | |
offsetParent, | |
strategy | |
} = _ref; | |
const isFixed = strategy === 'fixed'; | |
const documentElement = getDocumentElement(offsetParent); | |
const topLayer = elements ? isTopLayer(elements.floating) : false; | |
if (offsetParent === documentElement || topLayer && isFixed) { | |
return rect; | |
} | |
let scroll = { | |
scrollLeft: 0, | |
scrollTop: 0 | |
}; | |
let scale = createCoords(1); | |
const offsets = createCoords(0); | |
const isOffsetParentAnElement = isHTMLElement(offsetParent); | |
if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { | |
if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) { | |
scroll = getNodeScroll(offsetParent); | |
} | |
if (isHTMLElement(offsetParent)) { | |
const offsetRect = getBoundingClientRect(offsetParent); | |
scale = getScale(offsetParent); | |
offsets.x = offsetRect.x + offsetParent.clientLeft; | |
offsets.y = offsetRect.y + offsetParent.clientTop; | |
} | |
} | |
return { | |
width: rect.width * scale.x, | |
height: rect.height * scale.y, | |
x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x, | |
y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y | |
}; | |
} | |
function getClientRects(element) { | |
return Array.from(element.getClientRects()); | |
} | |
function getWindowScrollBarX(element) { | |
// If <html> has a CSS width greater than the viewport, then this will be | |
// incorrect for RTL. | |
return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft; | |
} | |
// Gets the entire size of the scrollable document area, even extending outside | |
// of the `<html>` and `<body>` rect bounds if horizontally scrollable. | |
function getDocumentRect(element) { | |
const html = getDocumentElement(element); | |
const scroll = getNodeScroll(element); | |
const body = element.ownerDocument.body; | |
const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth); | |
const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); | |
let x = -scroll.scrollLeft + getWindowScrollBarX(element); | |
const y = -scroll.scrollTop; | |
if (getComputedStyle$1(body).direction === 'rtl') { | |
x += max(html.clientWidth, body.clientWidth) - width; | |
} | |
return { | |
width, | |
height, | |
x, | |
y | |
}; | |
} | |
function getViewportRect(element, strategy) { | |
const win = getWindow(element); | |
const html = getDocumentElement(element); | |
const visualViewport = win.visualViewport; | |
let width = html.clientWidth; | |
let height = html.clientHeight; | |
let x = 0; | |
let y = 0; | |
if (visualViewport) { | |
width = visualViewport.width; | |
height = visualViewport.height; | |
const visualViewportBased = isWebKit(); | |
if (!visualViewportBased || visualViewportBased && strategy === 'fixed') { | |
x = visualViewport.offsetLeft; | |
y = visualViewport.offsetTop; | |
} | |
} | |
return { | |
width, | |
height, | |
x, | |
y | |
}; | |
} | |
// Returns the inner client rect, subtracting scrollbars if present. | |
function getInnerBoundingClientRect(element, strategy) { | |
const clientRect = getBoundingClientRect(element, true, strategy === 'fixed'); | |
const top = clientRect.top + element.clientTop; | |
const left = clientRect.left + element.clientLeft; | |
const scale = isHTMLElement(element) ? getScale(element) : createCoords(1); | |
const width = element.clientWidth * scale.x; | |
const height = element.clientHeight * scale.y; | |
const x = left * scale.x; | |
const y = top * scale.y; | |
return { | |
width, | |
height, | |
x, | |
y | |
}; | |
} | |
function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) { | |
let rect; | |
if (clippingAncestor === 'viewport') { | |
rect = getViewportRect(element, strategy); | |
} else if (clippingAncestor === 'document') { | |
rect = getDocumentRect(getDocumentElement(element)); | |
} else if (isElement(clippingAncestor)) { | |
rect = getInnerBoundingClientRect(clippingAncestor, strategy); | |
} else { | |
const visualOffsets = getVisualOffsets(element); | |
rect = { | |
...clippingAncestor, | |
x: clippingAncestor.x - visualOffsets.x, | |
y: clippingAncestor.y - visualOffsets.y | |
}; | |
} | |
return rectToClientRect(rect); | |
} | |
function hasFixedPositionAncestor(element, stopNode) { | |
const parentNode = getParentNode(element); | |
if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { | |
return false; | |
} | |
return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); | |
} | |
// A "clipping ancestor" is an `overflow` element with the characteristic of | |
// clipping (or hiding) child elements. This returns all clipping ancestors | |
// of the given element up the tree. | |
function getClippingElementAncestors(element, cache) { | |
const cachedResult = cache.get(element); | |
if (cachedResult) { | |
return cachedResult; | |
} | |
let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); | |
let currentContainingBlockComputedStyle = null; | |
const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; | |
let currentNode = elementIsFixed ? getParentNode(element) : element; | |
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block | |
while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { | |
const computedStyle = getComputedStyle$1(currentNode); | |
const currentNodeIsContaining = isContainingBlock(currentNode); | |
if (!currentNodeIsContaining && computedStyle.position === 'fixed') { | |
currentContainingBlockComputedStyle = null; | |
} | |
const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode); | |
if (shouldDropCurrentNode) { | |
// Drop non-containing blocks. | |
result = result.filter(ancestor => ancestor !== currentNode); | |
} else { | |
// Record last containing block for next iteration. | |
currentContainingBlockComputedStyle = computedStyle; | |
} | |
currentNode = getParentNode(currentNode); | |
} | |
cache.set(element, result); | |
return result; | |
} | |
// Gets the maximum area that the element is visible in due to any number of | |
// clipping ancestors. | |
function getClippingRect(_ref) { | |
let { | |
element, | |
boundary, | |
rootBoundary, | |
strategy | |
} = _ref; | |
const elementClippingAncestors = boundary === 'clippingAncestors' ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary); | |
const clippingAncestors = [...elementClippingAncestors, rootBoundary]; | |
const firstClippingAncestor = clippingAncestors[0]; | |
const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => { | |
const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy); | |
accRect.top = max(rect.top, accRect.top); | |
accRect.right = min(rect.right, accRect.right); | |
accRect.bottom = min(rect.bottom, accRect.bottom); | |
accRect.left = max(rect.left, accRect.left); | |
return accRect; | |
}, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy)); | |
return { | |
width: clippingRect.right - clippingRect.left, | |
height: clippingRect.bottom - clippingRect.top, | |
x: clippingRect.left, | |
y: clippingRect.top | |
}; | |
} | |
function getDimensions(element) { | |
const { | |
width, | |
height | |
} = getCssDimensions(element); | |
return { | |
width, | |
height | |
}; | |
} | |
function getRectRelativeToOffsetParent(element, offsetParent, strategy) { | |
const isOffsetParentAnElement = isHTMLElement(offsetParent); | |
const documentElement = getDocumentElement(offsetParent); | |
const isFixed = strategy === 'fixed'; | |
const rect = getBoundingClientRect(element, true, isFixed, offsetParent); | |
let scroll = { | |
scrollLeft: 0, | |
scrollTop: 0 | |
}; | |
const offsets = createCoords(0); | |
if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { | |
if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) { | |
scroll = getNodeScroll(offsetParent); | |
} | |
if (isOffsetParentAnElement) { | |
const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent); | |
offsets.x = offsetRect.x + offsetParent.clientLeft; | |
offsets.y = offsetRect.y + offsetParent.clientTop; | |
} else if (documentElement) { | |
offsets.x = getWindowScrollBarX(documentElement); | |
} | |
} | |
const x = rect.left + scroll.scrollLeft - offsets.x; | |
const y = rect.top + scroll.scrollTop - offsets.y; | |
return { | |
x, | |
y, | |
width: rect.width, | |
height: rect.height | |
}; | |
} | |
function isStaticPositioned(element) { | |
return getComputedStyle$1(element).position === 'static'; | |
} | |
function getTrueOffsetParent(element, polyfill) { | |
if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { | |
return null; | |
} | |
if (polyfill) { | |
return polyfill(element); | |
} | |
return element.offsetParent; | |
} | |
// Gets the closest ancestor positioned element. Handles some edge cases, | |
// such as table ancestors and cross browser bugs. | |
function getOffsetParent(element, polyfill) { | |
const win = getWindow(element); | |
if (isTopLayer(element)) { | |
return win; | |
} | |
if (!isHTMLElement(element)) { | |
let svgOffsetParent = getParentNode(element); | |
while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) { | |
if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) { | |
return svgOffsetParent; | |
} | |
svgOffsetParent = getParentNode(svgOffsetParent); | |
} | |
return win; | |
} | |
let offsetParent = getTrueOffsetParent(element, polyfill); | |
while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) { | |
offsetParent = getTrueOffsetParent(offsetParent, polyfill); | |
} | |
if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) { | |
return win; | |
} | |
return offsetParent || getContainingBlock(element) || win; | |
} | |
const getElementRects = async function (data) { | |
const getOffsetParentFn = this.getOffsetParent || getOffsetParent; | |
const getDimensionsFn = this.getDimensions; | |
const floatingDimensions = await getDimensionsFn(data.floating); | |
return { | |
reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy), | |
floating: { | |
x: 0, | |
y: 0, | |
width: floatingDimensions.width, | |
height: floatingDimensions.height | |
} | |
}; | |
}; | |
function isRTL(element) { | |
return getComputedStyle$1(element).direction === 'rtl'; | |
} | |
const platform = { | |
convertOffsetParentRelativeRectToViewportRelativeRect, | |
getDocumentElement, | |
getClippingRect, | |
getOffsetParent, | |
getElementRects, | |
getClientRects, | |
getDimensions, | |
getScale, | |
isElement, | |
isRTL | |
}; | |
// https://samthor.au/2021/observing-dom/ | |
function observeMove(element, onMove) { | |
let io = null; | |
let timeoutId; | |
const root = getDocumentElement(element); | |
function cleanup() { | |
var _io; | |
clearTimeout(timeoutId); | |
(_io = io) == null || _io.disconnect(); | |
io = null; | |
} | |
function refresh(skip, threshold) { | |
if (skip === void 0) { | |
skip = false; | |
} | |
if (threshold === void 0) { | |
threshold = 1; | |
} | |
cleanup(); | |
const { | |
left, | |
top, | |
width, | |
height | |
} = element.getBoundingClientRect(); | |
if (!skip) { | |
onMove(); | |
} | |
if (!width || !height) { | |
return; | |
} | |
const insetTop = floor(top); | |
const insetRight = floor(root.clientWidth - (left + width)); | |
const insetBottom = floor(root.clientHeight - (top + height)); | |
const insetLeft = floor(left); | |
const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; | |
const options = { | |
rootMargin, | |
threshold: max(0, min(1, threshold)) || 1 | |
}; | |
let isFirstUpdate = true; | |
function handleObserve(entries) { | |
const ratio = entries[0].intersectionRatio; | |
if (ratio !== threshold) { | |
if (!isFirstUpdate) { | |
return refresh(); | |
} | |
if (!ratio) { | |
// If the reference is clipped, the ratio is 0. Throttle the refresh | |
// to prevent an infinite loop of updates. | |
timeoutId = setTimeout(() => { | |
refresh(false, 1e-7); | |
}, 1000); | |
} else { | |
refresh(false, ratio); | |
} | |
} | |
isFirstUpdate = false; | |
} | |
// Older browsers don't support a `document` as the root and will throw an | |
// error. | |
try { | |
io = new IntersectionObserver(handleObserve, { | |
...options, | |
// Handle <iframe>s | |
root: root.ownerDocument | |
}); | |
} catch (e) { | |
io = new IntersectionObserver(handleObserve, options); | |
} | |
io.observe(element); | |
} | |
refresh(true); | |
return cleanup; | |
} | |
/** | |
* Automatically updates the position of the floating element when necessary. | |
* Should only be called when the floating element is mounted on the DOM or | |
* visible on the screen. | |
* @returns cleanup function that should be invoked when the floating element is | |
* removed from the DOM or hidden from the screen. | |
* @see https://floating-ui.com/docs/autoUpdate | |
*/ | |
function autoUpdate(reference, floating, update, options) { | |
if (options === void 0) { | |
options = {}; | |
} | |
const { | |
ancestorScroll = true, | |
ancestorResize = true, | |
elementResize = typeof ResizeObserver === 'function', | |
layoutShift = typeof IntersectionObserver === 'function', | |
animationFrame = false | |
} = options; | |
const referenceEl = unwrapElement(reference); | |
const ancestors = ancestorScroll || ancestorResize ? [...(referenceEl ? getOverflowAncestors(referenceEl) : []), ...getOverflowAncestors(floating)] : []; | |
ancestors.forEach(ancestor => { | |
ancestorScroll && ancestor.addEventListener('scroll', update, { | |
passive: true | |
}); | |
ancestorResize && ancestor.addEventListener('resize', update); | |
}); | |
const cleanupIo = referenceEl && layoutShift ? observeMove(referenceEl, update) : null; | |
let reobserveFrame = -1; | |
let resizeObserver = null; | |
if (elementResize) { | |
resizeObserver = new ResizeObserver(_ref => { | |
let [firstEntry] = _ref; | |
if (firstEntry && firstEntry.target === referenceEl && resizeObserver) { | |
// Prevent update loops when using the `size` middleware. | |
// https://github.com/floating-ui/floating-ui/issues/1740 | |
resizeObserver.unobserve(floating); | |
cancelAnimationFrame(reobserveFrame); | |
reobserveFrame = requestAnimationFrame(() => { | |
var _resizeObserver; | |
(_resizeObserver = resizeObserver) == null || _resizeObserver.observe(floating); | |
}); | |
} | |
update(); | |
}); | |
if (referenceEl && !animationFrame) { | |
resizeObserver.observe(referenceEl); | |
} | |
resizeObserver.observe(floating); | |
} | |
let frameId; | |
let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null; | |
if (animationFrame) { | |
frameLoop(); | |
} | |
function frameLoop() { | |
const nextRefRect = getBoundingClientRect(reference); | |
if (prevRefRect && (nextRefRect.x !== prevRefRect.x || nextRefRect.y !== prevRefRect.y || nextRefRect.width !== prevRefRect.width || nextRefRect.height !== prevRefRect.height)) { | |
update(); | |
} | |
prevRefRect = nextRefRect; | |
frameId = requestAnimationFrame(frameLoop); | |
} | |
update(); | |
return () => { | |
var _resizeObserver2; | |
ancestors.forEach(ancestor => { | |
ancestorScroll && ancestor.removeEventListener('scroll', update); | |
ancestorResize && ancestor.removeEventListener('resize', update); | |
}); | |
cleanupIo == null || cleanupIo(); | |
(_resizeObserver2 = resizeObserver) == null || _resizeObserver2.disconnect(); | |
resizeObserver = null; | |
if (animationFrame) { | |
cancelAnimationFrame(frameId); | |
} | |
}; | |
} | |
/** | |
* Modifies the placement by translating the floating element along the | |
* specified axes. | |
* A number (shorthand for `mainAxis` or distance), or an axes configuration | |
* object may be passed. | |
* @see https://floating-ui.com/docs/offset | |
*/ | |
const offset = offset$1; | |
/** | |
* Optimizes the visibility of the floating element by shifting it in order to | |
* keep it in view when it will overflow the clipping boundary. | |
* @see https://floating-ui.com/docs/shift | |
*/ | |
const shift = shift$1; | |
/** | |
* Optimizes the visibility of the floating element by flipping the `placement` | |
* in order to keep it in view when the preferred placement(s) will overflow the | |
* clipping boundary. Alternative to `autoPlacement`. | |
* @see https://floating-ui.com/docs/flip | |
*/ | |
const flip = flip$1; | |
/** | |
* Provides data that allows you to change the size of the floating element — | |
* for instance, prevent it from overflowing the clipping boundary or match the | |
* width of the reference element. | |
* @see https://floating-ui.com/docs/size | |
*/ | |
const size = size$1; | |
/** | |
* Provides data to position an inner element of the floating element so that it | |
* appears centered to the reference element. | |
* @see https://floating-ui.com/docs/arrow | |
*/ | |
const arrow = arrow$1; | |
/** | |
* Built-in `limiter` that will stop `shift()` at a certain point. | |
*/ | |
const limitShift = limitShift$1; | |
/** | |
* Computes the `x` and `y` coordinates that will place the floating element | |
* next to a given reference element. | |
*/ | |
const computePosition = (reference, floating, options) => { | |
// This caches the expensive `getClippingElementAncestors` function so that | |
// multiple lifecycle resets re-use the same result. It only lives for a | |
// single call. If other functions become expensive, we can add them as well. | |
const cache = new Map(); | |
const mergedOptions = { | |
platform, | |
...options | |
}; | |
const platformWithCache = { | |
...mergedOptions.platform, | |
_c: cache | |
}; | |
return computePosition$1(reference, floating, { | |
...mergedOptions, | |
platform: platformWithCache | |
}); | |
}; | |
// src/get-placement.ts | |
function createDOMRect(x = 0, y = 0, width = 0, height = 0) { | |
if (typeof DOMRect === "function") { | |
return new DOMRect(x, y, width, height); | |
} | |
const rect = { | |
x, | |
y, | |
width, | |
height, | |
top: y, | |
right: x + width, | |
bottom: y + height, | |
left: x | |
}; | |
return { ...rect, toJSON: () => rect }; | |
} | |
function getDOMRect(anchorRect) { | |
if (!anchorRect) return createDOMRect(); | |
const { x, y, width, height } = anchorRect; | |
return createDOMRect(x, y, width, height); | |
} | |
function getAnchorElement(anchorElement, getAnchorRect) { | |
return { | |
contextElement: isHTMLElement$1(anchorElement) ? anchorElement : void 0, | |
getBoundingClientRect: () => { | |
const anchor = anchorElement; | |
const anchorRect = getAnchorRect?.(anchor); | |
if (anchorRect || !anchor) { | |
return getDOMRect(anchorRect); | |
} | |
return anchor.getBoundingClientRect(); | |
} | |
}; | |
} | |
// src/middleware.ts | |
var toVar = (value) => ({ variable: value, reference: `var(${value})` }); | |
var cssVars = { | |
arrowSize: toVar("--arrow-size"), | |
arrowSizeHalf: toVar("--arrow-size-half"), | |
arrowBg: toVar("--arrow-background"), | |
transformOrigin: toVar("--transform-origin"), | |
arrowOffset: toVar("--arrow-offset") | |
}; | |
var getTransformOrigin = (arrow2) => ({ | |
top: "bottom center", | |
"top-start": arrow2 ? `${arrow2.x}px bottom` : "left bottom", | |
"top-end": arrow2 ? `${arrow2.x}px bottom` : "right bottom", | |
bottom: "top center", | |
"bottom-start": arrow2 ? `${arrow2.x}px top` : "top left", | |
"bottom-end": arrow2 ? `${arrow2.x}px top` : "top right", | |
left: "right center", | |
"left-start": arrow2 ? `right ${arrow2.y}px` : "right top", | |
"left-end": arrow2 ? `right ${arrow2.y}px` : "right bottom", | |
right: "left center", | |
"right-start": arrow2 ? `left ${arrow2.y}px` : "left top", | |
"right-end": arrow2 ? `left ${arrow2.y}px` : "left bottom" | |
}); | |
var transformOriginMiddleware = { | |
name: "transformOrigin", | |
fn({ placement, elements, middlewareData }) { | |
const { arrow: arrow2 } = middlewareData; | |
const transformOrigin = getTransformOrigin(arrow2)[placement]; | |
const { floating } = elements; | |
floating.style.setProperty(cssVars.transformOrigin.variable, transformOrigin); | |
return { | |
data: { transformOrigin } | |
}; | |
} | |
}; | |
var rectMiddleware = { | |
name: "rects", | |
fn({ rects }) { | |
return { | |
data: rects | |
}; | |
} | |
}; | |
var shiftArrowMiddleware = (arrowEl) => { | |
if (!arrowEl) return; | |
return { | |
name: "shiftArrow", | |
fn({ placement, middlewareData }) { | |
if (!middlewareData.arrow) return {}; | |
const { x, y } = middlewareData.arrow; | |
const dir = placement.split("-")[0]; | |
Object.assign(arrowEl.style, { | |
left: x != null ? `${x}px` : "", | |
top: y != null ? `${y}px` : "", | |
[dir]: `calc(100% + ${cssVars.arrowOffset.reference})` | |
}); | |
return {}; | |
} | |
}; | |
}; | |
function getPlacementDetails(placement) { | |
const [side, align] = placement.split("-"); | |
return { side, align, hasAlign: align != null }; | |
} | |
function getPlacementSide(placement) { | |
return placement.split("-")[0]; | |
} | |
// src/get-placement.ts | |
var defaultOptions = { | |
strategy: "absolute", | |
placement: "bottom", | |
listeners: true, | |
gutter: 8, | |
flip: true, | |
slide: true, | |
overlap: false, | |
sameWidth: false, | |
fitViewport: false, | |
overflowPadding: 8, | |
arrowPadding: 4 | |
}; | |
function roundByDpr(win, value) { | |
const dpr = win.devicePixelRatio || 1; | |
return Math.round(value * dpr) / dpr; | |
} | |
function getBoundaryMiddleware(opts) { | |
return runIfFn(opts.boundary); | |
} | |
function getArrowMiddleware(arrowElement, opts) { | |
if (!arrowElement) return; | |
return arrow({ | |
element: arrowElement, | |
padding: opts.arrowPadding | |
}); | |
} | |
function getOffsetMiddleware(arrowElement, opts) { | |
if (isNull(opts.offset ?? opts.gutter)) return; | |
return offset(({ placement }) => { | |
const arrowOffset = (arrowElement?.clientHeight || 0) / 2; | |
const gutter = opts.offset?.mainAxis ?? opts.gutter; | |
const mainAxis = typeof gutter === "number" ? gutter + arrowOffset : gutter ?? arrowOffset; | |
const { hasAlign } = getPlacementDetails(placement); | |
const shift2 = !hasAlign ? opts.shift : void 0; | |
const crossAxis = opts.offset?.crossAxis ?? shift2; | |
return compact({ | |
crossAxis, | |
mainAxis, | |
alignmentAxis: opts.shift | |
}); | |
}); | |
} | |
function getFlipMiddleware(opts) { | |
if (!opts.flip) return; | |
return flip({ | |
boundary: getBoundaryMiddleware(opts), | |
padding: opts.overflowPadding, | |
fallbackPlacements: opts.flip === true ? void 0 : opts.flip | |
}); | |
} | |
function getShiftMiddleware(opts) { | |
if (!opts.slide && !opts.overlap) return; | |
return shift({ | |
boundary: getBoundaryMiddleware(opts), | |
mainAxis: opts.slide, | |
crossAxis: opts.overlap, | |
padding: opts.overflowPadding, | |
limiter: limitShift() | |
}); | |
} | |
function getSizeMiddleware(opts) { | |
return size({ | |
padding: opts.overflowPadding, | |
apply({ elements, rects, availableHeight, availableWidth }) { | |
const floating = elements.floating; | |
const referenceWidth = Math.round(rects.reference.width); | |
availableWidth = Math.floor(availableWidth); | |
availableHeight = Math.floor(availableHeight); | |
floating.style.setProperty("--reference-width", `${referenceWidth}px`); | |
floating.style.setProperty("--available-width", `${availableWidth}px`); | |
floating.style.setProperty("--available-height", `${availableHeight}px`); | |
} | |
}); | |
} | |
function getAutoUpdateOptions(opts) { | |
if (!opts) return {}; | |
if (opts === true) { | |
return { ancestorResize: true, ancestorScroll: true, elementResize: true, layoutShift: true }; | |
} | |
return opts; | |
} | |
function getPlacementImpl(referenceOrVirtual, floating, opts = {}) { | |
const reference = getAnchorElement(referenceOrVirtual, opts.getAnchorRect); | |
if (!floating || !reference) return; | |
const options = Object.assign({}, defaultOptions, opts); | |
const arrowEl = floating.querySelector("[data-part=arrow]"); | |
const middleware = [ | |
getOffsetMiddleware(arrowEl, options), | |
getFlipMiddleware(options), | |
getShiftMiddleware(options), | |
getArrowMiddleware(arrowEl, options), | |
shiftArrowMiddleware(arrowEl), | |
transformOriginMiddleware, | |
getSizeMiddleware(options), | |
rectMiddleware | |
]; | |
const { placement, strategy, onComplete, onPositioned } = options; | |
const updatePosition = async () => { | |
if (!reference || !floating) return; | |
const pos = await computePosition(reference, floating, { | |
placement, | |
middleware, | |
strategy | |
}); | |
onComplete?.(pos); | |
onPositioned?.({ placed: true }); | |
const win = getWindow$1(floating); | |
const x = roundByDpr(win, pos.x); | |
const y = roundByDpr(win, pos.y); | |
floating.style.setProperty("--x", `${x}px`); | |
floating.style.setProperty("--y", `${y}px`); | |
const contentEl = floating.firstElementChild; | |
if (contentEl) { | |
const zIndex = win.getComputedStyle(contentEl).zIndex; | |
floating.style.setProperty("--z-index", zIndex); | |
} | |
}; | |
const update = async () => { | |
if (opts.updatePosition) { | |
await opts.updatePosition({ updatePosition }); | |
onPositioned?.({ placed: true }); | |
} else { | |
await updatePosition(); | |
} | |
}; | |
const autoUpdateOptions = getAutoUpdateOptions(options.listeners); | |
const cancelAutoUpdate = options.listeners ? autoUpdate(reference, floating, update, autoUpdateOptions) : noop; | |
update(); | |
return () => { | |
cancelAutoUpdate?.(); | |
onPositioned?.({ placed: false }); | |
}; | |
} | |
function getPlacement(referenceOrFn, floatingOrFn, opts = {}) { | |
const { defer, ...options } = opts; | |
const func = defer ? raf : (v) => v(); | |
const cleanups = []; | |
cleanups.push( | |
func(() => { | |
const reference = typeof referenceOrFn === "function" ? referenceOrFn() : referenceOrFn; | |
const floating = typeof floatingOrFn === "function" ? floatingOrFn() : floatingOrFn; | |
cleanups.push(getPlacementImpl(reference, floating, options)); | |
}) | |
); | |
return () => { | |
cleanups.forEach((fn) => fn?.()); | |
}; | |
} | |
// src/get-styles.ts | |
var ARROW_FLOATING_STYLE = { | |
bottom: "rotate(45deg)", | |
left: "rotate(135deg)", | |
top: "rotate(225deg)", | |
right: "rotate(315deg)" | |
}; | |
function getPlacementStyles(options = {}) { | |
const { placement, sameWidth, fitViewport, strategy = "absolute" } = options; | |
return { | |
arrow: { | |
position: "absolute", | |
width: cssVars.arrowSize.reference, | |
height: cssVars.arrowSize.reference, | |
[cssVars.arrowSizeHalf.variable]: `calc(${cssVars.arrowSize.reference} / 2)`, | |
[cssVars.arrowOffset.variable]: `calc(${cssVars.arrowSizeHalf.reference} * -1)` | |
}, | |
arrowTip: { | |
transform: placement ? ARROW_FLOATING_STYLE[placement.split("-")[0]] : void 0, | |
background: cssVars.arrowBg.reference, | |
top: "0", | |
left: "0", | |
width: "100%", | |
height: "100%", | |
position: "absolute", | |
zIndex: "inherit" | |
}, | |
floating: { | |
position: strategy, | |
isolation: "isolate", | |
minWidth: sameWidth ? void 0 : "max-content", | |
width: sameWidth ? "var(--reference-width)" : void 0, | |
maxWidth: fitViewport ? "var(--available-width)" : void 0, | |
maxHeight: fitViewport ? "var(--available-height)" : void 0, | |
top: "0px", | |
left: "0px", | |
// move off-screen if placement is not defined | |
transform: placement ? "translate3d(var(--x), var(--y), 0)" : "translate3d(0, -100vh, 0)", | |
zIndex: "var(--z-index)" | |
} | |
}; | |
} | |
// src/index.ts | |
// src/get-window-frames.ts | |
function getWindowFrames(win) { | |
const frames = { | |
each(cb) { | |
for (let i = 0; i < win.frames?.length; i += 1) { | |
const frame = win.frames[i]; | |
if (frame) cb(frame); | |
} | |
}, | |
addEventListener(event, listener, options) { | |
frames.each((frame) => { | |
try { | |
frame.document.addEventListener(event, listener, options); | |
} catch { | |
} | |
}); | |
return () => { | |
try { | |
frames.removeEventListener(event, listener, options); | |
} catch { | |
} | |
}; | |
}, | |
removeEventListener(event, listener, options) { | |
frames.each((frame) => { | |
try { | |
frame.document.removeEventListener(event, listener, options); | |
} catch { | |
} | |
}); | |
} | |
}; | |
return frames; | |
} | |
// src/index.ts | |
var POINTER_OUTSIDE_EVENT = "pointerdown.outside"; | |
var FOCUS_OUTSIDE_EVENT = "focus.outside"; | |
function isComposedPathFocusable(composedPath) { | |
for (const node of composedPath) { | |
if (isHTMLElement$1(node) && isFocusable$1(node)) return true; | |
} | |
return false; | |
} | |
var isPointerEvent = (event) => "clientY" in event; | |
function isEventPointWithin(node, event) { | |
if (!isPointerEvent(event) || !node) return false; | |
const rect = node.getBoundingClientRect(); | |
if (rect.width === 0 || rect.height === 0) return false; | |
return rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; | |
} | |
function isEventWithinScrollbar(event) { | |
const target = getEventTarget(event); | |
if (!target || !isPointerEvent(event)) return false; | |
const isScrollableY = target.scrollHeight > target.clientHeight; | |
const onScrollbarY = isScrollableY && event.clientX > target.clientWidth; | |
const isScrollableX = target.scrollWidth > target.clientWidth; | |
const onScrollbarX = isScrollableX && event.clientY > target.clientHeight; | |
return onScrollbarY || onScrollbarX; | |
} | |
function trackInteractOutsideImpl(node, options) { | |
const { exclude, onFocusOutside, onPointerDownOutside, onInteractOutside, defer } = options; | |
if (!node) return; | |
const doc = getDocument(node); | |
const win = getWindow$1(node); | |
const frames = getWindowFrames(win); | |
function isEventOutside(event) { | |
const target = getEventTarget(event); | |
if (!isHTMLElement$1(target)) return false; | |
if (contains(node, target)) return false; | |
if (isEventPointWithin(node, event)) return false; | |
if (isEventWithinScrollbar(event)) return false; | |
return !exclude?.(target); | |
} | |
let clickHandler; | |
function onPointerDown(event) { | |
function handler() { | |
const func = defer ? raf : (v) => v(); | |
const composedPath = event.composedPath?.() ?? [event.target]; | |
func(() => { | |
if (!node || !isEventOutside(event)) return; | |
if (onPointerDownOutside || onInteractOutside) { | |
const handler2 = callAll(onPointerDownOutside, onInteractOutside); | |
node.addEventListener(POINTER_OUTSIDE_EVENT, handler2, { once: true }); | |
} | |
fireCustomEvent(node, POINTER_OUTSIDE_EVENT, { | |
bubbles: false, | |
cancelable: true, | |
detail: { | |
originalEvent: event, | |
contextmenu: isContextMenuEvent(event), | |
focusable: isComposedPathFocusable(composedPath) | |
} | |
}); | |
}); | |
} | |
if (event.pointerType === "touch") { | |
frames.removeEventListener("click", handler); | |
doc.removeEventListener("click", handler); | |
clickHandler = handler; | |
doc.addEventListener("click", handler, { once: true }); | |
frames.addEventListener("click", handler, { once: true }); | |
} else { | |
handler(); | |
} | |
} | |
const cleanups = /* @__PURE__ */ new Set(); | |
const timer = setTimeout(() => { | |
cleanups.add(frames.addEventListener("pointerdown", onPointerDown, true)); | |
cleanups.add(addDomEvent(doc, "pointerdown", onPointerDown, true)); | |
}, 0); | |
function onFocusin(event) { | |
const func = defer ? raf : (v) => v(); | |
func(() => { | |
if (!node || !isEventOutside(event)) return; | |
if (onFocusOutside || onInteractOutside) { | |
const handler = callAll(onFocusOutside, onInteractOutside); | |
node.addEventListener(FOCUS_OUTSIDE_EVENT, handler, { once: true }); | |
} | |
fireCustomEvent(node, FOCUS_OUTSIDE_EVENT, { | |
bubbles: false, | |
cancelable: true, | |
detail: { | |
originalEvent: event, | |
contextmenu: false, | |
focusable: isFocusable$1(getEventTarget(event)) | |
} | |
}); | |
}); | |
} | |
cleanups.add(addDomEvent(doc, "focusin", onFocusin, true)); | |
cleanups.add(frames.addEventListener("focusin", onFocusin, true)); | |
return () => { | |
clearTimeout(timer); | |
if (clickHandler) { | |
frames.removeEventListener("click", clickHandler); | |
doc.removeEventListener("click", clickHandler); | |
} | |
cleanups.forEach((fn) => fn()); | |
}; | |
} | |
function trackInteractOutside(nodeOrFn, options) { | |
const { defer } = options; | |
const func = defer ? raf : (v) => v(); | |
const cleanups = []; | |
cleanups.push( | |
func(() => { | |
const node = typeof nodeOrFn === "function" ? nodeOrFn() : nodeOrFn; | |
cleanups.push(trackInteractOutsideImpl(node, options)); | |
}) | |
); | |
return () => { | |
cleanups.forEach((fn) => fn?.()); | |
}; | |
} | |
// src/dismissable-layer.ts | |
function trackEscapeKeydown(node, fn) { | |
const handleKeyDown = (event) => { | |
if (event.key !== "Escape") return; | |
if (event.isComposing) return; | |
fn?.(event); | |
}; | |
return addDomEvent(getDocument(node), "keydown", handleKeyDown, { capture: true }); | |
} | |
var layerStack = { | |
layers: [], | |
branches: [], | |
count() { | |
return this.layers.length; | |
}, | |
pointerBlockingLayers() { | |
return this.layers.filter((layer) => layer.pointerBlocking); | |
}, | |
topMostPointerBlockingLayer() { | |
return [...this.pointerBlockingLayers()].slice(-1)[0]; | |
}, | |
hasPointerBlockingLayer() { | |
return this.pointerBlockingLayers().length > 0; | |
}, | |
isBelowPointerBlockingLayer(node) { | |
const index = this.indexOf(node); | |
const highestBlockingIndex = this.topMostPointerBlockingLayer() ? this.indexOf(this.topMostPointerBlockingLayer()?.node) : -1; | |
return index < highestBlockingIndex; | |
}, | |
isTopMost(node) { | |
const layer = this.layers[this.count() - 1]; | |
return layer?.node === node; | |
}, | |
getNestedLayers(node) { | |
return Array.from(this.layers).slice(this.indexOf(node) + 1); | |
}, | |
isInNestedLayer(node, target) { | |
return this.getNestedLayers(node).some((layer) => contains(layer.node, target)); | |
}, | |
isInBranch(target) { | |
return Array.from(this.branches).some((branch) => contains(branch, target)); | |
}, | |
add(layer) { | |
const num = this.layers.push(layer); | |
layer.node.style.setProperty("--layer-index", `${num}`); | |
}, | |
addBranch(node) { | |
this.branches.push(node); | |
}, | |
remove(node) { | |
const index = this.indexOf(node); | |
if (index < 0) return; | |
if (index < this.count() - 1) { | |
const _layers = this.getNestedLayers(node); | |
_layers.forEach((layer) => layer.dismiss()); | |
} | |
this.layers.splice(index, 1); | |
node.style.removeProperty("--layer-index"); | |
}, | |
removeBranch(node) { | |
const index = this.branches.indexOf(node); | |
if (index >= 0) this.branches.splice(index, 1); | |
}, | |
indexOf(node) { | |
return this.layers.findIndex((layer) => layer.node === node); | |
}, | |
dismiss(node) { | |
this.layers[this.indexOf(node)]?.dismiss(); | |
}, | |
clear() { | |
this.remove(this.layers[0].node); | |
} | |
}; | |
var originalBodyPointerEvents; | |
function assignPointerEventToLayers() { | |
layerStack.layers.forEach(({ node }) => { | |
node.style.pointerEvents = layerStack.isBelowPointerBlockingLayer(node) ? "none" : "auto"; | |
}); | |
} | |
function clearPointerEvent(node) { | |
node.style.pointerEvents = ""; | |
} | |
function disablePointerEventsOutside(node, peristentElements) { | |
const doc = getDocument(node); | |
const cleanups = []; | |
if (layerStack.hasPointerBlockingLayer() && !doc.body.hasAttribute("data-inert")) { | |
originalBodyPointerEvents = document.body.style.pointerEvents; | |
queueMicrotask(() => { | |
doc.body.style.pointerEvents = "none"; | |
doc.body.setAttribute("data-inert", ""); | |
}); | |
} | |
if (peristentElements) { | |
const persistedCleanup = waitForElements(peristentElements, (el) => { | |
cleanups.push(setStyle(el, { pointerEvents: "auto" })); | |
}); | |
cleanups.push(persistedCleanup); | |
} | |
return () => { | |
if (layerStack.hasPointerBlockingLayer()) return; | |
queueMicrotask(() => { | |
doc.body.style.pointerEvents = originalBodyPointerEvents; | |
doc.body.removeAttribute("data-inert"); | |
if (doc.body.style.length === 0) doc.body.removeAttribute("style"); | |
}); | |
cleanups.forEach((fn) => fn()); | |
}; | |
} | |
// src/dismissable-layer.ts | |
function trackDismissableElementImpl(node, options) { | |
if (!node) { | |
warn("[@zag-js/dismissable] node is `null` or `undefined`"); | |
return; | |
} | |
const { onDismiss, pointerBlocking, exclude: excludeContainers, debug } = options; | |
const layer = { dismiss: onDismiss, node, pointerBlocking }; | |
layerStack.add(layer); | |
assignPointerEventToLayers(); | |
function onPointerDownOutside(event) { | |
const target = getEventTarget(event.detail.originalEvent); | |
if (layerStack.isBelowPointerBlockingLayer(node) || layerStack.isInBranch(target)) return; | |
options.onPointerDownOutside?.(event); | |
options.onInteractOutside?.(event); | |
if (event.defaultPrevented) return; | |
if (debug) { | |
console.log("onPointerDownOutside:", event.detail.originalEvent); | |
} | |
onDismiss?.(); | |
} | |
function onFocusOutside(event) { | |
const target = getEventTarget(event.detail.originalEvent); | |
if (layerStack.isInBranch(target)) return; | |
options.onFocusOutside?.(event); | |
options.onInteractOutside?.(event); | |
if (event.defaultPrevented) return; | |
if (debug) { | |
console.log("onFocusOutside:", event.detail.originalEvent); | |
} | |
onDismiss?.(); | |
} | |
function onEscapeKeyDown(event) { | |
if (!layerStack.isTopMost(node)) return; | |
options.onEscapeKeyDown?.(event); | |
if (!event.defaultPrevented && onDismiss) { | |
event.preventDefault(); | |
onDismiss(); | |
} | |
} | |
function exclude(target) { | |
if (!node) return false; | |
const containers = typeof excludeContainers === "function" ? excludeContainers() : excludeContainers; | |
const _containers = Array.isArray(containers) ? containers : [containers]; | |
const persistentElements = options.persistentElements?.map((fn) => fn()).filter(isHTMLElement$1); | |
if (persistentElements) _containers.push(...persistentElements); | |
return _containers.some((node2) => contains(node2, target)) || layerStack.isInNestedLayer(node, target); | |
} | |
const cleanups = [ | |
pointerBlocking ? disablePointerEventsOutside(node, options.persistentElements) : void 0, | |
trackEscapeKeydown(node, onEscapeKeyDown), | |
trackInteractOutside(node, { exclude, onFocusOutside, onPointerDownOutside, defer: options.defer }) | |
]; | |
return () => { | |
layerStack.remove(node); | |
assignPointerEventToLayers(); | |
clearPointerEvent(node); | |
cleanups.forEach((fn) => fn?.()); | |
}; | |
} | |
function trackDismissableElement(nodeOrFn, options) { | |
const { defer } = options; | |
const func = defer ? raf : (v) => v(); | |
const cleanups = []; | |
cleanups.push( | |
func(() => { | |
const node = isFunction(nodeOrFn) ? nodeOrFn() : nodeOrFn; | |
cleanups.push(trackDismissableElementImpl(node, options)); | |
}) | |
); | |
return () => { | |
cleanups.forEach((fn) => fn?.()); | |
}; | |
} | |
function trackDismissableBranch(nodeOrFn, options = {}) { | |
const { defer } = options; | |
const func = defer ? raf : (v) => v(); | |
const cleanups = []; | |
cleanups.push( | |
func(() => { | |
const node = isFunction(nodeOrFn) ? nodeOrFn() : nodeOrFn; | |
if (!node) { | |
warn("[@zag-js/dismissable] branch node is `null` or `undefined`"); | |
return; | |
} | |
layerStack.addBranch(node); | |
cleanups.push(() => { | |
layerStack.removeBranch(node); | |
}); | |
}) | |
); | |
return () => { | |
cleanups.forEach((fn) => fn?.()); | |
}; | |
} | |
// src/index.ts | |
var refCountMap = /* @__PURE__ */ new WeakMap(); | |
var observerStack = []; | |
function ariaHiddenImpl(targets, options = {}) { | |
const { rootEl } = options; | |
const exclude = targets.filter(Boolean); | |
if (exclude.length === 0) return; | |
const doc = exclude[0].ownerDocument || document; | |
const win = doc.defaultView ?? window; | |
const visibleNodes = new Set(exclude); | |
const hiddenNodes = /* @__PURE__ */ new Set(); | |
const root = rootEl ?? doc.body; | |
let walk = (root2) => { | |
for (let element of root2.querySelectorAll("[data-live-announcer], [data-zag-top-layer]")) { | |
visibleNodes.add(element); | |
} | |
let acceptNode = (node) => { | |
if (visibleNodes.has(node) || hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute("role") !== "row") { | |
return NodeFilter.FILTER_REJECT; | |
} | |
for (let target of visibleNodes) { | |
if (node.contains(target)) { | |
return NodeFilter.FILTER_SKIP; | |
} | |
} | |
return NodeFilter.FILTER_ACCEPT; | |
}; | |
let walker = doc.createTreeWalker(root2, NodeFilter.SHOW_ELEMENT, { acceptNode }); | |
let acceptRoot = acceptNode(root2); | |
if (acceptRoot === NodeFilter.FILTER_ACCEPT) { | |
hide(root2); | |
} | |
if (acceptRoot !== NodeFilter.FILTER_REJECT) { | |
let node = walker.nextNode(); | |
while (node != null) { | |
hide(node); | |
node = walker.nextNode(); | |
} | |
} | |
}; | |
let hide = (node) => { | |
let refCount = refCountMap.get(node) ?? 0; | |
if (node.getAttribute("aria-hidden") === "true" && refCount === 0) { | |
return; | |
} | |
if (refCount === 0) { | |
node.setAttribute("aria-hidden", "true"); | |
} | |
hiddenNodes.add(node); | |
refCountMap.set(node, refCount + 1); | |
}; | |
if (observerStack.length) { | |
observerStack[observerStack.length - 1].disconnect(); | |
} | |
walk(root); | |
const observer = new win.MutationObserver((changes) => { | |
for (let change of changes) { | |
if (change.type !== "childList" || change.addedNodes.length === 0) { | |
continue; | |
} | |
if (![...visibleNodes, ...hiddenNodes].some((node) => node.contains(change.target))) { | |
for (let node of change.removedNodes) { | |
if (node instanceof win.Element) { | |
visibleNodes.delete(node); | |
hiddenNodes.delete(node); | |
} | |
} | |
for (let node of change.addedNodes) { | |
if ((node instanceof win.HTMLElement || node instanceof win.SVGElement) && (node.dataset.liveAnnouncer === "true" || node.dataset.zagTopLayer === "true")) { | |
visibleNodes.add(node); | |
} else if (node instanceof win.Element) { | |
walk(node); | |
} | |
} | |
} | |
} | |
}); | |
observer.observe(root, { childList: true, subtree: true }); | |
let observerWrapper = { | |
observe() { | |
observer.observe(root, { childList: true, subtree: true }); | |
}, | |
disconnect() { | |
observer.disconnect(); | |
} | |
}; | |
observerStack.push(observerWrapper); | |
return () => { | |
observer.disconnect(); | |
for (let node of hiddenNodes) { | |
let count = refCountMap.get(node); | |
if (count === 1) { | |
node.removeAttribute("aria-hidden"); | |
refCountMap.delete(node); | |
} else { | |
refCountMap.set(node, count - 1); | |
} | |
} | |
if (observerWrapper === observerStack[observerStack.length - 1]) { | |
observerStack.pop(); | |
if (observerStack.length) { | |
observerStack[observerStack.length - 1].observe(); | |
} | |
} else { | |
observerStack.splice(observerStack.indexOf(observerWrapper), 1); | |
} | |
}; | |
} | |
function ariaHidden(targetsOrFn, options = {}) { | |
const { defer } = options; | |
const func = defer ? raf : (v) => v(); | |
const cleanups = []; | |
cleanups.push( | |
func(() => { | |
const targets = typeof targetsOrFn === "function" ? targetsOrFn() : targetsOrFn; | |
cleanups.push(ariaHiddenImpl(targets, options)); | |
}) | |
); | |
return () => { | |
cleanups.forEach((fn) => fn?.()); | |
}; | |
} | |
// src/index.ts | |
var LOCK_CLASSNAME = "data-zag-scroll-lock"; | |
function assignStyle(el, style) { | |
if (!el) return; | |
const previousStyle = el.style.cssText; | |
Object.assign(el.style, style); | |
return () => { | |
el.style.cssText = previousStyle; | |
}; | |
} | |
function setCSSProperty(el, property, value) { | |
if (!el) return; | |
const previousValue = el.style.getPropertyValue(property); | |
el.style.setProperty(property, value); | |
return () => { | |
if (previousValue) { | |
el.style.setProperty(property, previousValue); | |
} else { | |
el.style.removeProperty(property); | |
} | |
}; | |
} | |
function getPaddingProperty(documentElement) { | |
const documentLeft = documentElement.getBoundingClientRect().left; | |
const scrollbarX = Math.round(documentLeft) + documentElement.scrollLeft; | |
return scrollbarX ? "paddingLeft" : "paddingRight"; | |
} | |
function preventBodyScroll(_document) { | |
const doc = _document ?? document; | |
const win = doc.defaultView ?? window; | |
const { documentElement, body } = doc; | |
const locked = body.hasAttribute(LOCK_CLASSNAME); | |
if (locked) return; | |
body.setAttribute(LOCK_CLASSNAME, ""); | |
const scrollbarWidth = win.innerWidth - documentElement.clientWidth; | |
const setScrollbarWidthProperty = () => setCSSProperty(documentElement, "--scrollbar-width", `${scrollbarWidth}px`); | |
const paddingProperty = getPaddingProperty(documentElement); | |
const setStyle = () => assignStyle(body, { | |
overflow: "hidden", | |
[paddingProperty]: `${scrollbarWidth}px` | |
}); | |
const setIOSStyle = () => { | |
const { scrollX, scrollY, visualViewport } = win; | |
const offsetLeft = visualViewport?.offsetLeft ?? 0; | |
const offsetTop = visualViewport?.offsetTop ?? 0; | |
const restoreStyle = assignStyle(body, { | |
position: "fixed", | |
overflow: "hidden", | |
top: `${-(scrollY - Math.floor(offsetTop))}px`, | |
left: `${-(scrollX - Math.floor(offsetLeft))}px`, | |
right: "0", | |
[paddingProperty]: `${scrollbarWidth}px` | |
}); | |
return () => { | |
restoreStyle?.(); | |
win.scrollTo({ left: scrollX, top: scrollY, behavior: "instant" }); | |
}; | |
}; | |
const cleanups = [setScrollbarWidthProperty(), isIos() ? setIOSStyle() : setStyle()]; | |
return () => { | |
cleanups.forEach((fn) => fn?.()); | |
body.removeAttribute(LOCK_CLASSNAME); | |
}; | |
} | |
/*! | |
* tabbable 6.2.0 | |
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | |
*/ | |
// NOTE: separate `:not()` selectors has broader browser support than the newer | |
// `:not([inert], [inert] *)` (Feb 2023) | |
// CAREFUL: JSDom does not support `:not([inert] *)` as a selector; using it causes | |
// the entire query to fail, resulting in no nodes found, which will break a lot | |
// of things... so we have to rely on JS to identify nodes inside an inert container | |
var candidateSelectors = ['input:not([inert])', 'select:not([inert])', 'textarea:not([inert])', 'a[href]:not([inert])', 'button:not([inert])', '[tabindex]:not(slot):not([inert])', 'audio[controls]:not([inert])', 'video[controls]:not([inert])', '[contenteditable]:not([contenteditable="false"]):not([inert])', 'details>summary:first-of-type:not([inert])', 'details:not([inert])']; | |
var candidateSelector = /* #__PURE__ */candidateSelectors.join(','); | |
var NoElement = typeof Element === 'undefined'; | |
var matches = NoElement ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | |
var getRootNode = !NoElement && Element.prototype.getRootNode ? function (element) { | |
var _element$getRootNode; | |
return element === null || element === void 0 ? void 0 : (_element$getRootNode = element.getRootNode) === null || _element$getRootNode === void 0 ? void 0 : _element$getRootNode.call(element); | |
} : function (element) { | |
return element === null || element === void 0 ? void 0 : element.ownerDocument; | |
}; | |
/** | |
* Determines if a node is inert or in an inert ancestor. | |
* @param {Element} [node] | |
* @param {boolean} [lookUp] If true and `node` is not inert, looks up at ancestors to | |
* see if any of them are inert. If false, only `node` itself is considered. | |
* @returns {boolean} True if inert itself or by way of being in an inert ancestor. | |
* False if `node` is falsy. | |
*/ | |
var isInert = function isInert(node, lookUp) { | |
var _node$getAttribute; | |
if (lookUp === void 0) { | |
lookUp = true; | |
} | |
// CAREFUL: JSDom does not support inert at all, so we can't use the `HTMLElement.inert` | |
// JS API property; we have to check the attribute, which can either be empty or 'true'; | |
// if it's `null` (not specified) or 'false', it's an active element | |
var inertAtt = node === null || node === void 0 ? void 0 : (_node$getAttribute = node.getAttribute) === null || _node$getAttribute === void 0 ? void 0 : _node$getAttribute.call(node, 'inert'); | |
var inert = inertAtt === '' || inertAtt === 'true'; | |
// NOTE: this could also be handled with `node.matches('[inert], :is([inert] *)')` | |
// if it weren't for `matches()` not being a function on shadow roots; the following | |
// code works for any kind of node | |
// CAREFUL: JSDom does not appear to support certain selectors like `:not([inert] *)` | |
// so it likely would not support `:is([inert] *)` either... | |
var result = inert || lookUp && node && isInert(node.parentNode); // recursive | |
return result; | |
}; | |
/** | |
* Determines if a node's content is editable. | |
* @param {Element} [node] | |
* @returns True if it's content-editable; false if it's not or `node` is falsy. | |
*/ | |
var isContentEditable = function isContentEditable(node) { | |
var _node$getAttribute2; | |
// CAREFUL: JSDom does not support the `HTMLElement.isContentEditable` API so we have | |
// to use the attribute directly to check for this, which can either be empty or 'true'; | |
// if it's `null` (not specified) or 'false', it's a non-editable element | |
var attValue = node === null || node === void 0 ? void 0 : (_node$getAttribute2 = node.getAttribute) === null || _node$getAttribute2 === void 0 ? void 0 : _node$getAttribute2.call(node, 'contenteditable'); | |
return attValue === '' || attValue === 'true'; | |
}; | |
/** | |
* @param {Element} el container to check in | |
* @param {boolean} includeContainer add container to check | |
* @param {(node: Element) => boolean} filter filter candidates | |
* @returns {Element[]} | |
*/ | |
var getCandidates = function getCandidates(el, includeContainer, filter) { | |
// even if `includeContainer=false`, we still have to check it for inertness because | |
// if it's inert, all its children are inert | |
if (isInert(el)) { | |
return []; | |
} | |
var candidates = Array.prototype.slice.apply(el.querySelectorAll(candidateSelector)); | |
if (includeContainer && matches.call(el, candidateSelector)) { | |
candidates.unshift(el); | |
} | |
candidates = candidates.filter(filter); | |
return candidates; | |
}; | |
/** | |
* @callback GetShadowRoot | |
* @param {Element} element to check for shadow root | |
* @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available. | |
*/ | |
/** | |
* @callback ShadowRootFilter | |
* @param {Element} shadowHostNode the element which contains shadow content | |
* @returns {boolean} true if a shadow root could potentially contain valid candidates. | |
*/ | |
/** | |
* @typedef {Object} CandidateScope | |
* @property {Element} scopeParent contains inner candidates | |
* @property {Element[]} candidates list of candidates found in the scope parent | |
*/ | |
/** | |
* @typedef {Object} IterativeOptions | |
* @property {GetShadowRoot|boolean} getShadowRoot true if shadow support is enabled; falsy if not; | |
* if a function, implies shadow support is enabled and either returns the shadow root of an element | |
* or a boolean stating if it has an undisclosed shadow root | |
* @property {(node: Element) => boolean} filter filter candidates | |
* @property {boolean} flatten if true then result will flatten any CandidateScope into the returned list | |
* @property {ShadowRootFilter} shadowRootFilter filter shadow roots; | |
*/ | |
/** | |
* @param {Element[]} elements list of element containers to match candidates from | |
* @param {boolean} includeContainer add container list to check | |
* @param {IterativeOptions} options | |
* @returns {Array.<Element|CandidateScope>} | |
*/ | |
var getCandidatesIteratively = function getCandidatesIteratively(elements, includeContainer, options) { | |
var candidates = []; | |
var elementsToCheck = Array.from(elements); | |
while (elementsToCheck.length) { | |
var element = elementsToCheck.shift(); | |
if (isInert(element, false)) { | |
// no need to look up since we're drilling down | |
// anything inside this container will also be inert | |
continue; | |
} | |
if (element.tagName === 'SLOT') { | |
// add shadow dom slot scope (slot itself cannot be focusable) | |
var assigned = element.assignedElements(); | |
var content = assigned.length ? assigned : element.children; | |
var nestedCandidates = getCandidatesIteratively(content, true, options); | |
if (options.flatten) { | |
candidates.push.apply(candidates, nestedCandidates); | |
} else { | |
candidates.push({ | |
scopeParent: element, | |
candidates: nestedCandidates | |
}); | |
} | |
} else { | |
// check candidate element | |
var validCandidate = matches.call(element, candidateSelector); | |
if (validCandidate && options.filter(element) && (includeContainer || !elements.includes(element))) { | |
candidates.push(element); | |
} | |
// iterate over shadow content if possible | |
var shadowRoot = element.shadowRoot || | |
// check for an undisclosed shadow | |
typeof options.getShadowRoot === 'function' && options.getShadowRoot(element); | |
// no inert look up because we're already drilling down and checking for inertness | |
// on the way down, so all containers to this root node should have already been | |
// vetted as non-inert | |
var validShadowRoot = !isInert(shadowRoot, false) && (!options.shadowRootFilter || options.shadowRootFilter(element)); | |
if (shadowRoot && validShadowRoot) { | |
// add shadow dom scope IIF a shadow root node was given; otherwise, an undisclosed | |
// shadow exists, so look at light dom children as fallback BUT create a scope for any | |
// child candidates found because they're likely slotted elements (elements that are | |
// children of the web component element (which has the shadow), in the light dom, but | |
// slotted somewhere _inside_ the undisclosed shadow) -- the scope is created below, | |
// _after_ we return from this recursive call | |
var _nestedCandidates = getCandidatesIteratively(shadowRoot === true ? element.children : shadowRoot.children, true, options); | |
if (options.flatten) { | |
candidates.push.apply(candidates, _nestedCandidates); | |
} else { | |
candidates.push({ | |
scopeParent: element, | |
candidates: _nestedCandidates | |
}); | |
} | |
} else { | |
// there's not shadow so just dig into the element's (light dom) children | |
// __without__ giving the element special scope treatment | |
elementsToCheck.unshift.apply(elementsToCheck, element.children); | |
} | |
} | |
} | |
return candidates; | |
}; | |
/** | |
* @private | |
* Determines if the node has an explicitly specified `tabindex` attribute. | |
* @param {HTMLElement} node | |
* @returns {boolean} True if so; false if not. | |
*/ | |
var hasTabIndex = function hasTabIndex(node) { | |
return !isNaN(parseInt(node.getAttribute('tabindex'), 10)); | |
}; | |
/** | |
* Determine the tab index of a given node. | |
* @param {HTMLElement} node | |
* @returns {number} Tab order (negative, 0, or positive number). | |
* @throws {Error} If `node` is falsy. | |
*/ | |
var getTabIndex = function getTabIndex(node) { | |
if (!node) { | |
throw new Error('No node provided'); | |
} | |
if (node.tabIndex < 0) { | |
// in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default | |
// `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM, | |
// yet they are still part of the regular tab order; in FF, they get a default | |
// `tabIndex` of 0; since Chrome still puts those elements in the regular tab | |
// order, consider their tab index to be 0. | |
// Also browsers do not return `tabIndex` correctly for contentEditable nodes; | |
// so if they don't have a tabindex attribute specifically set, assume it's 0. | |
if ((/^(AUDIO|VIDEO|DETAILS)$/.test(node.tagName) || isContentEditable(node)) && !hasTabIndex(node)) { | |
return 0; | |
} | |
} | |
return node.tabIndex; | |
}; | |
/** | |
* Determine the tab index of a given node __for sort order purposes__. | |
* @param {HTMLElement} node | |
* @param {boolean} [isScope] True for a custom element with shadow root or slot that, by default, | |
* has tabIndex -1, but needs to be sorted by document order in order for its content to be | |
* inserted into the correct sort position. | |
* @returns {number} Tab order (negative, 0, or positive number). | |
*/ | |
var getSortOrderTabIndex = function getSortOrderTabIndex(node, isScope) { | |
var tabIndex = getTabIndex(node); | |
if (tabIndex < 0 && isScope && !hasTabIndex(node)) { | |
return 0; | |
} | |
return tabIndex; | |
}; | |
var sortOrderedTabbables = function sortOrderedTabbables(a, b) { | |
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex; | |
}; | |
var isInput = function isInput(node) { | |
return node.tagName === 'INPUT'; | |
}; | |
var isHiddenInput = function isHiddenInput(node) { | |
return isInput(node) && node.type === 'hidden'; | |
}; | |
var isDetailsWithSummary = function isDetailsWithSummary(node) { | |
var r = node.tagName === 'DETAILS' && Array.prototype.slice.apply(node.children).some(function (child) { | |
return child.tagName === 'SUMMARY'; | |
}); | |
return r; | |
}; | |
var getCheckedRadio = function getCheckedRadio(nodes, form) { | |
for (var i = 0; i < nodes.length; i++) { | |
if (nodes[i].checked && nodes[i].form === form) { | |
return nodes[i]; | |
} | |
} | |
}; | |
var isTabbableRadio = function isTabbableRadio(node) { | |
if (!node.name) { | |
return true; | |
} | |
var radioScope = node.form || getRootNode(node); | |
var queryRadios = function queryRadios(name) { | |
return radioScope.querySelectorAll('input[type="radio"][name="' + name + '"]'); | |
}; | |
var radioSet; | |
if (typeof window !== 'undefined' && typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') { | |
radioSet = queryRadios(window.CSS.escape(node.name)); | |
} else { | |
try { | |
radioSet = queryRadios(node.name); | |
} catch (err) { | |
// eslint-disable-next-line no-console | |
console.error('Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s', err.message); | |
return false; | |
} | |
} | |
var checked = getCheckedRadio(radioSet, node.form); | |
return !checked || checked === node; | |
}; | |
var isRadio = function isRadio(node) { | |
return isInput(node) && node.type === 'radio'; | |
}; | |
var isNonTabbableRadio = function isNonTabbableRadio(node) { | |
return isRadio(node) && !isTabbableRadio(node); | |
}; | |
// determines if a node is ultimately attached to the window's document | |
var isNodeAttached = function isNodeAttached(node) { | |
var _nodeRoot; | |
// The root node is the shadow root if the node is in a shadow DOM; some document otherwise | |
// (but NOT _the_ document; see second 'If' comment below for more). | |
// If rootNode is shadow root, it'll have a host, which is the element to which the shadow | |
// is attached, and the one we need to check if it's in the document or not (because the | |
// shadow, and all nodes it contains, is never considered in the document since shadows | |
// behave like self-contained DOMs; but if the shadow's HOST, which is part of the document, | |
// is hidden, or is not in the document itself but is detached, it will affect the shadow's | |
// visibility, including all the nodes it contains). The host could be any normal node, | |
// or a custom element (i.e. web component). Either way, that's the one that is considered | |
// part of the document, not the shadow root, nor any of its children (i.e. the node being | |
// tested). | |
// To further complicate things, we have to look all the way up until we find a shadow HOST | |
// that is attached (or find none) because the node might be in nested shadows... | |
// If rootNode is not a shadow root, it won't have a host, and so rootNode should be the | |
// document (per the docs) and while it's a Document-type object, that document does not | |
// appear to be the same as the node's `ownerDocument` for some reason, so it's safer | |
// to ignore the rootNode at this point, and use `node.ownerDocument`. Otherwise, | |
// using `rootNode.contains(node)` will _always_ be true we'll get false-positives when | |
// node is actually detached. | |
// NOTE: If `nodeRootHost` or `node` happens to be the `document` itself (which is possible | |
// if a tabbable/focusable node was quickly added to the DOM, focused, and then removed | |
// from the DOM as in https://github.com/focus-trap/focus-trap-react/issues/905), then | |
// `ownerDocument` will be `null`, hence the optional chaining on it. | |
var nodeRoot = node && getRootNode(node); | |
var nodeRootHost = (_nodeRoot = nodeRoot) === null || _nodeRoot === void 0 ? void 0 : _nodeRoot.host; | |
// in some cases, a detached node will return itself as the root instead of a document or | |
// shadow root object, in which case, we shouldn't try to look further up the host chain | |
var attached = false; | |
if (nodeRoot && nodeRoot !== node) { | |
var _nodeRootHost, _nodeRootHost$ownerDo, _node$ownerDocument; | |
attached = !!((_nodeRootHost = nodeRootHost) !== null && _nodeRootHost !== void 0 && (_nodeRootHost$ownerDo = _nodeRootHost.ownerDocument) !== null && _nodeRootHost$ownerDo !== void 0 && _nodeRootHost$ownerDo.contains(nodeRootHost) || node !== null && node !== void 0 && (_node$ownerDocument = node.ownerDocument) !== null && _node$ownerDocument !== void 0 && _node$ownerDocument.contains(node)); | |
while (!attached && nodeRootHost) { | |
var _nodeRoot2, _nodeRootHost2, _nodeRootHost2$ownerD; | |
// since it's not attached and we have a root host, the node MUST be in a nested shadow DOM, | |
// which means we need to get the host's host and check if that parent host is contained | |
// in (i.e. attached to) the document | |
nodeRoot = getRootNode(nodeRootHost); | |
nodeRootHost = (_nodeRoot2 = nodeRoot) === null || _nodeRoot2 === void 0 ? void 0 : _nodeRoot2.host; | |
attached = !!((_nodeRootHost2 = nodeRootHost) !== null && _nodeRootHost2 !== void 0 && (_nodeRootHost2$ownerD = _nodeRootHost2.ownerDocument) !== null && _nodeRootHost2$ownerD !== void 0 && _nodeRootHost2$ownerD.contains(nodeRootHost)); | |
} | |
} | |
return attached; | |
}; | |
var isZeroArea = function isZeroArea(node) { | |
var _node$getBoundingClie = node.getBoundingClientRect(), | |
width = _node$getBoundingClie.width, | |
height = _node$getBoundingClie.height; | |
return width === 0 && height === 0; | |
}; | |
var isHidden = function isHidden(node, _ref) { | |
var displayCheck = _ref.displayCheck, | |
getShadowRoot = _ref.getShadowRoot; | |
// NOTE: visibility will be `undefined` if node is detached from the document | |
// (see notes about this further down), which means we will consider it visible | |
// (this is legacy behavior from a very long way back) | |
// NOTE: we check this regardless of `displayCheck="none"` because this is a | |
// _visibility_ check, not a _display_ check | |
if (getComputedStyle(node).visibility === 'hidden') { | |
return true; | |
} | |
var isDirectSummary = matches.call(node, 'details>summary:first-of-type'); | |
var nodeUnderDetails = isDirectSummary ? node.parentElement : node; | |
if (matches.call(nodeUnderDetails, 'details:not([open]) *')) { | |
return true; | |
} | |
if (!displayCheck || displayCheck === 'full' || displayCheck === 'legacy-full') { | |
if (typeof getShadowRoot === 'function') { | |
// figure out if we should consider the node to be in an undisclosed shadow and use the | |
// 'non-zero-area' fallback | |
var originalNode = node; | |
while (node) { | |
var parentElement = node.parentElement; | |
var rootNode = getRootNode(node); | |
if (parentElement && !parentElement.shadowRoot && getShadowRoot(parentElement) === true // check if there's an undisclosed shadow | |
) { | |
// node has an undisclosed shadow which means we can only treat it as a black box, so we | |
// fall back to a non-zero-area test | |
return isZeroArea(node); | |
} else if (node.assignedSlot) { | |
// iterate up slot | |
node = node.assignedSlot; | |
} else if (!parentElement && rootNode !== node.ownerDocument) { | |
// cross shadow boundary | |
node = rootNode.host; | |
} else { | |
// iterate up normal dom | |
node = parentElement; | |
} | |
} | |
node = originalNode; | |
} | |
// else, `getShadowRoot` might be true, but all that does is enable shadow DOM support | |
// (i.e. it does not also presume that all nodes might have undisclosed shadows); or | |
// it might be a falsy value, which means shadow DOM support is disabled | |
// Since we didn't find it sitting in an undisclosed shadow (or shadows are disabled) | |
// now we can just test to see if it would normally be visible or not, provided it's | |
// attached to the main document. | |
// NOTE: We must consider case where node is inside a shadow DOM and given directly to | |
// `isTabbable()` or `isFocusable()` -- regardless of `getShadowRoot` option setting. | |
if (isNodeAttached(node)) { | |
// this works wherever the node is: if there's at least one client rect, it's | |
// somehow displayed; it also covers the CSS 'display: contents' case where the | |
// node itself is hidden in place of its contents; and there's no need to search | |
// up the hierarchy either | |
return !node.getClientRects().length; | |
} | |
// Else, the node isn't attached to the document, which means the `getClientRects()` | |
// API will __always__ return zero rects (this can happen, for example, if React | |
// is used to render nodes onto a detached tree, as confirmed in this thread: | |
// https://github.com/facebook/react/issues/9117#issuecomment-284228870) | |
// | |
// It also means that even window.getComputedStyle(node).display will return `undefined` | |
// because styles are only computed for nodes that are in the document. | |
// | |
// NOTE: THIS HAS BEEN THE CASE FOR YEARS. It is not new, nor is it caused by tabbable | |
// somehow. Though it was never stated officially, anyone who has ever used tabbable | |
// APIs on nodes in detached containers has actually implicitly used tabbable in what | |
// was later (as of v5.2.0 on Apr 9, 2021) called `displayCheck="none"` mode -- essentially | |
// considering __everything__ to be visible because of the innability to determine styles. | |
// | |
// v6.0.0: As of this major release, the default 'full' option __no longer treats detached | |
// nodes as visible with the 'none' fallback.__ | |
if (displayCheck !== 'legacy-full') { | |
return true; // hidden | |
} | |
// else, fallback to 'none' mode and consider the node visible | |
} else if (displayCheck === 'non-zero-area') { | |
// NOTE: Even though this tests that the node's client rect is non-zero to determine | |
// whether it's displayed, and that a detached node will __always__ have a zero-area | |
// client rect, we don't special-case for whether the node is attached or not. In | |
// this mode, we do want to consider nodes that have a zero area to be hidden at all | |
// times, and that includes attached or not. | |
return isZeroArea(node); | |
} | |
// visible, as far as we can tell, or per current `displayCheck=none` mode, we assume | |
// it's visible | |
return false; | |
}; | |
// form fields (nested) inside a disabled fieldset are not focusable/tabbable | |
// unless they are in the _first_ <legend> element of the top-most disabled | |
// fieldset | |
var isDisabledFromFieldset = function isDisabledFromFieldset(node) { | |
if (/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(node.tagName)) { | |
var parentNode = node.parentElement; | |
// check if `node` is contained in a disabled <fieldset> | |
while (parentNode) { | |
if (parentNode.tagName === 'FIELDSET' && parentNode.disabled) { | |
// look for the first <legend> among the children of the disabled <fieldset> | |
for (var i = 0; i < parentNode.children.length; i++) { | |
var child = parentNode.children.item(i); | |
// when the first <legend> (in document order) is found | |
if (child.tagName === 'LEGEND') { | |
// if its parent <fieldset> is not nested in another disabled <fieldset>, | |
// return whether `node` is a descendant of its first <legend> | |
return matches.call(parentNode, 'fieldset[disabled] *') ? true : !child.contains(node); | |
} | |
} | |
// the disabled <fieldset> containing `node` has no <legend> | |
return true; | |
} | |
parentNode = parentNode.parentElement; | |
} | |
} | |
// else, node's tabbable/focusable state should not be affected by a fieldset's | |
// enabled/disabled state | |
return false; | |
}; | |
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) { | |
if (node.disabled || | |
// we must do an inert look up to filter out any elements inside an inert ancestor | |
// because we're limited in the type of selectors we can use in JSDom (see related | |
// note related to `candidateSelectors`) | |
isInert(node) || isHiddenInput(node) || isHidden(node, options) || | |
// For a details element with a summary, the summary element gets the focus | |
isDetailsWithSummary(node) || isDisabledFromFieldset(node)) { | |
return false; | |
} | |
return true; | |
}; | |
var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable(options, node) { | |
if (isNonTabbableRadio(node) || getTabIndex(node) < 0 || !isNodeMatchingSelectorFocusable(options, node)) { | |
return false; | |
} | |
return true; | |
}; | |
var isValidShadowRootTabbable = function isValidShadowRootTabbable(shadowHostNode) { | |
var tabIndex = parseInt(shadowHostNode.getAttribute('tabindex'), 10); | |
if (isNaN(tabIndex) || tabIndex >= 0) { | |
return true; | |
} | |
// If a custom element has an explicit negative tabindex, | |
// browsers will not allow tab targeting said element's children. | |
return false; | |
}; | |
/** | |
* @param {Array.<Element|CandidateScope>} candidates | |
* @returns Element[] | |
*/ | |
var sortByOrder = function sortByOrder(candidates) { | |
var regularTabbables = []; | |
var orderedTabbables = []; | |
candidates.forEach(function (item, i) { | |
var isScope = !!item.scopeParent; | |
var element = isScope ? item.scopeParent : item; | |
var candidateTabindex = getSortOrderTabIndex(element, isScope); | |
var elements = isScope ? sortByOrder(item.candidates) : element; | |
if (candidateTabindex === 0) { | |
isScope ? regularTabbables.push.apply(regularTabbables, elements) : regularTabbables.push(element); | |
} else { | |
orderedTabbables.push({ | |
documentOrder: i, | |
tabIndex: candidateTabindex, | |
item: item, | |
isScope: isScope, | |
content: elements | |
}); | |
} | |
}); | |
return orderedTabbables.sort(sortOrderedTabbables).reduce(function (acc, sortable) { | |
sortable.isScope ? acc.push.apply(acc, sortable.content) : acc.push(sortable.content); | |
return acc; | |
}, []).concat(regularTabbables); | |
}; | |
var tabbable = function tabbable(container, options) { | |
options = options || {}; | |
var candidates; | |
if (options.getShadowRoot) { | |
candidates = getCandidatesIteratively([container], options.includeContainer, { | |
filter: isNodeMatchingSelectorTabbable.bind(null, options), | |
flatten: false, | |
getShadowRoot: options.getShadowRoot, | |
shadowRootFilter: isValidShadowRootTabbable | |
}); | |
} else { | |
candidates = getCandidates(container, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | |
} | |
return sortByOrder(candidates); | |
}; | |
var focusable = function focusable(container, options) { | |
options = options || {}; | |
var candidates; | |
if (options.getShadowRoot) { | |
candidates = getCandidatesIteratively([container], options.includeContainer, { | |
filter: isNodeMatchingSelectorFocusable.bind(null, options), | |
flatten: true, | |
getShadowRoot: options.getShadowRoot | |
}); | |
} else { | |
candidates = getCandidates(container, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | |
} | |
return candidates; | |
}; | |
var isTabbable = function isTabbable(node, options) { | |
options = options || {}; | |
if (!node) { | |
throw new Error('No node provided'); | |
} | |
if (matches.call(node, candidateSelector) === false) { | |
return false; | |
} | |
return isNodeMatchingSelectorTabbable(options, node); | |
}; | |
var focusableCandidateSelector = /* #__PURE__ */candidateSelectors.concat('iframe').join(','); | |
var isFocusable = function isFocusable(node, options) { | |
options = options || {}; | |
if (!node) { | |
throw new Error('No node provided'); | |
} | |
if (matches.call(node, focusableCandidateSelector) === false) { | |
return false; | |
} | |
return isNodeMatchingSelectorFocusable(options, node); | |
}; | |
/*! | |
* focus-trap 7.5.4 | |
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | |
*/ | |
function ownKeys(e, r) { | |
var t = Object.keys(e); | |
if (Object.getOwnPropertySymbols) { | |
var o = Object.getOwnPropertySymbols(e); | |
r && (o = o.filter(function (r) { | |
return Object.getOwnPropertyDescriptor(e, r).enumerable; | |
})), t.push.apply(t, o); | |
} | |
return t; | |
} | |
function _objectSpread2(e) { | |
for (var r = 1; r < arguments.length; r++) { | |
var t = null != arguments[r] ? arguments[r] : {}; | |
r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { | |
_defineProperty(e, r, t[r]); | |
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { | |
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); | |
}); | |
} | |
return e; | |
} | |
function _defineProperty(obj, key, value) { | |
key = _toPropertyKey(key); | |
if (key in obj) { | |
Object.defineProperty(obj, key, { | |
value: value, | |
enumerable: true, | |
configurable: true, | |
writable: true | |
}); | |
} else { | |
obj[key] = value; | |
} | |
return obj; | |
} | |
function _toPrimitive(input, hint) { | |
if (typeof input !== "object" || input === null) return input; | |
var prim = input[Symbol.toPrimitive]; | |
if (prim !== undefined) { | |
var res = prim.call(input, hint || "default"); | |
if (typeof res !== "object") return res; | |
throw new TypeError("@@toPrimitive must return a primitive value."); | |
} | |
return (hint === "string" ? String : Number)(input); | |
} | |
function _toPropertyKey(arg) { | |
var key = _toPrimitive(arg, "string"); | |
return typeof key === "symbol" ? key : String(key); | |
} | |
var activeFocusTraps = { | |
activateTrap: function activateTrap(trapStack, trap) { | |
if (trapStack.length > 0) { | |
var activeTrap = trapStack[trapStack.length - 1]; | |
if (activeTrap !== trap) { | |
activeTrap.pause(); | |
} | |
} | |
var trapIndex = trapStack.indexOf(trap); | |
if (trapIndex === -1) { | |
trapStack.push(trap); | |
} else { | |
// move this existing trap to the front of the queue | |
trapStack.splice(trapIndex, 1); | |
trapStack.push(trap); | |
} | |
}, | |
deactivateTrap: function deactivateTrap(trapStack, trap) { | |
var trapIndex = trapStack.indexOf(trap); | |
if (trapIndex !== -1) { | |
trapStack.splice(trapIndex, 1); | |
} | |
if (trapStack.length > 0) { | |
trapStack[trapStack.length - 1].unpause(); | |
} | |
} | |
}; | |
var isSelectableInput = function isSelectableInput(node) { | |
return node.tagName && node.tagName.toLowerCase() === 'input' && typeof node.select === 'function'; | |
}; | |
var isEscapeEvent = function isEscapeEvent(e) { | |
return (e === null || e === void 0 ? void 0 : e.key) === 'Escape' || (e === null || e === void 0 ? void 0 : e.key) === 'Esc' || (e === null || e === void 0 ? void 0 : e.keyCode) === 27; | |
}; | |
var isTabEvent = function isTabEvent(e) { | |
return (e === null || e === void 0 ? void 0 : e.key) === 'Tab' || (e === null || e === void 0 ? void 0 : e.keyCode) === 9; | |
}; | |
// checks for TAB by default | |
var isKeyForward = function isKeyForward(e) { | |
return isTabEvent(e) && !e.shiftKey; | |
}; | |
// checks for SHIFT+TAB by default | |
var isKeyBackward = function isKeyBackward(e) { | |
return isTabEvent(e) && e.shiftKey; | |
}; | |
var delay = function delay(fn) { | |
return setTimeout(fn, 0); | |
}; | |
// Array.find/findIndex() are not supported on IE; this replicates enough | |
// of Array.findIndex() for our needs | |
var findIndex = function findIndex(arr, fn) { | |
var idx = -1; | |
arr.every(function (value, i) { | |
if (fn(value)) { | |
idx = i; | |
return false; // break | |
} | |
return true; // next | |
}); | |
return idx; | |
}; | |
/** | |
* Get an option's value when it could be a plain value, or a handler that provides | |
* the value. | |
* @param {*} value Option's value to check. | |
* @param {...*} [params] Any parameters to pass to the handler, if `value` is a function. | |
* @returns {*} The `value`, or the handler's returned value. | |
*/ | |
var valueOrHandler = function valueOrHandler(value) { | |
for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | |
params[_key - 1] = arguments[_key]; | |
} | |
return typeof value === 'function' ? value.apply(void 0, params) : value; | |
}; | |
var getActualTarget = function getActualTarget(event) { | |
// NOTE: If the trap is _inside_ a shadow DOM, event.target will always be the | |
// shadow host. However, event.target.composedPath() will be an array of | |
// nodes "clicked" from inner-most (the actual element inside the shadow) to | |
// outer-most (the host HTML document). If we have access to composedPath(), | |
// then use its first element; otherwise, fall back to event.target (and | |
// this only works for an _open_ shadow DOM; otherwise, | |
// composedPath()[0] === event.target always). | |
return event.target.shadowRoot && typeof event.composedPath === 'function' ? event.composedPath()[0] : event.target; | |
}; | |
// NOTE: this must be _outside_ `createFocusTrap()` to make sure all traps in this | |
// current instance use the same stack if `userOptions.trapStack` isn't specified | |
var internalTrapStack = []; | |
var createFocusTrap = function createFocusTrap(elements, userOptions) { | |
// SSR: a live trap shouldn't be created in this type of environment so this | |
// should be safe code to execute if the `document` option isn't specified | |
var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document; | |
var trapStack = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.trapStack) || internalTrapStack; | |
var config = _objectSpread2({ | |
returnFocusOnDeactivate: true, | |
escapeDeactivates: true, | |
delayInitialFocus: true, | |
isKeyForward: isKeyForward, | |
isKeyBackward: isKeyBackward | |
}, userOptions); | |
var state = { | |
// containers given to createFocusTrap() | |
// @type {Array<HTMLElement>} | |
containers: [], | |
// list of objects identifying tabbable nodes in `containers` in the trap | |
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap | |
// is active, but the trap should never get to a state where there isn't at least one group | |
// with at least one tabbable node in it (that would lead to an error condition that would | |
// result in an error being thrown) | |
// @type {Array<{ | |
// container: HTMLElement, | |
// tabbableNodes: Array<HTMLElement>, // empty if none | |
// focusableNodes: Array<HTMLElement>, // empty if none | |
// posTabIndexesFound: boolean, | |
// firstTabbableNode: HTMLElement|undefined, | |
// lastTabbableNode: HTMLElement|undefined, | |
// firstDomTabbableNode: HTMLElement|undefined, | |
// lastDomTabbableNode: HTMLElement|undefined, | |
// nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined | |
// }>} | |
containerGroups: [], | |
// same order/length as `containers` list | |
// references to objects in `containerGroups`, but only those that actually have | |
// tabbable nodes in them | |
// NOTE: same order as `containers` and `containerGroups`, but __not necessarily__ | |
// the same length | |
tabbableGroups: [], | |
nodeFocusedBeforeActivation: null, | |
mostRecentlyFocusedNode: null, | |
active: false, | |
paused: false, | |
// timer ID for when delayInitialFocus is true and initial focus in this trap | |
// has been delayed during activation | |
delayInitialFocusTimer: undefined, | |
// the most recent KeyboardEvent for the configured nav key (typically [SHIFT+]TAB), if any | |
recentNavEvent: undefined | |
}; | |
var trap; // eslint-disable-line prefer-const -- some private functions reference it, and its methods reference private functions, so we must declare here and define later | |
/** | |
* Gets a configuration option value. | |
* @param {Object|undefined} configOverrideOptions If true, and option is defined in this set, | |
* value will be taken from this object. Otherwise, value will be taken from base configuration. | |
* @param {string} optionName Name of the option whose value is sought. | |
* @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName` | |
* IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used. | |
*/ | |
var getOption = function getOption(configOverrideOptions, optionName, configOptionName) { | |
return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName]; | |
}; | |
/** | |
* Finds the index of the container that contains the element. | |
* @param {HTMLElement} element | |
* @param {Event} [event] If available, and `element` isn't directly found in any container, | |
* the event's composed path is used to see if includes any known trap containers in the | |
* case where the element is inside a Shadow DOM. | |
* @returns {number} Index of the container in either `state.containers` or | |
* `state.containerGroups` (the order/length of these lists are the same); -1 | |
* if the element isn't found. | |
*/ | |
var findContainerIndex = function findContainerIndex(element, event) { | |
var composedPath = typeof (event === null || event === void 0 ? void 0 : event.composedPath) === 'function' ? event.composedPath() : undefined; | |
// NOTE: search `containerGroups` because it's possible a group contains no tabbable | |
// nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`) | |
// and we still need to find the element in there | |
return state.containerGroups.findIndex(function (_ref) { | |
var container = _ref.container, | |
tabbableNodes = _ref.tabbableNodes; | |
return container.contains(element) || ( // fall back to explicit tabbable search which will take into consideration any | |
// web components if the `tabbableOptions.getShadowRoot` option was used for | |
// the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't | |
// look inside web components even if open) | |
composedPath === null || composedPath === void 0 ? void 0 : composedPath.includes(container)) || tabbableNodes.find(function (node) { | |
return node === element; | |
}); | |
}); | |
}; | |
/** | |
* Gets the node for the given option, which is expected to be an option that | |
* can be either a DOM node, a string that is a selector to get a node, `false` | |
* (if a node is explicitly NOT given), or a function that returns any of these | |
* values. | |
* @param {string} optionName | |
* @returns {undefined | false | HTMLElement | SVGElement} Returns | |
* `undefined` if the option is not specified; `false` if the option | |
* resolved to `false` (node explicitly not given); otherwise, the resolved | |
* DOM node. | |
* @throws {Error} If the option is set, not `false`, and is not, or does not | |
* resolve to a node. | |
*/ | |
var getNodeForOption = function getNodeForOption(optionName) { | |
var optionValue = config[optionName]; | |
if (typeof optionValue === 'function') { | |
for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | |
params[_key2 - 1] = arguments[_key2]; | |
} | |
optionValue = optionValue.apply(void 0, params); | |
} | |
if (optionValue === true) { | |
optionValue = undefined; // use default value | |
} | |
if (!optionValue) { | |
if (optionValue === undefined || optionValue === false) { | |
return optionValue; | |
} | |
// else, empty string (invalid), null (invalid), 0 (invalid) | |
throw new Error("`".concat(optionName, "` was specified but was not a node, or did not return a node")); | |
} | |
var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point | |
if (typeof optionValue === 'string') { | |
node = doc.querySelector(optionValue); // resolve to node, or null if fails | |
if (!node) { | |
throw new Error("`".concat(optionName, "` as selector refers to no known node")); | |
} | |
} | |
return node; | |
}; | |
var getInitialFocusNode = function getInitialFocusNode() { | |
var node = getNodeForOption('initialFocus'); | |
// false explicitly indicates we want no initialFocus at all | |
if (node === false) { | |
return false; | |
} | |
if (node === undefined || !isFocusable(node, config.tabbableOptions)) { | |
// option not specified nor focusable: use fallback options | |
if (findContainerIndex(doc.activeElement) >= 0) { | |
node = doc.activeElement; | |
} else { | |
var firstTabbableGroup = state.tabbableGroups[0]; | |
var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode; | |
// NOTE: `fallbackFocus` option function cannot return `false` (not supported) | |
node = firstTabbableNode || getNodeForOption('fallbackFocus'); | |
} | |
} | |
if (!node) { | |
throw new Error('Your focus-trap needs to have at least one focusable element'); | |
} | |
return node; | |
}; | |
var updateTabbableNodes = function updateTabbableNodes() { | |
state.containerGroups = state.containers.map(function (container) { | |
var tabbableNodes = tabbable(container, config.tabbableOptions); | |
// NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes | |
// are a superset of tabbable nodes since nodes with negative `tabindex` attributes | |
// are focusable but not tabbable | |
var focusableNodes = focusable(container, config.tabbableOptions); | |
var firstTabbableNode = tabbableNodes.length > 0 ? tabbableNodes[0] : undefined; | |
var lastTabbableNode = tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : undefined; | |
var firstDomTabbableNode = focusableNodes.find(function (node) { | |
return isTabbable(node); | |
}); | |
var lastDomTabbableNode = focusableNodes.slice().reverse().find(function (node) { | |
return isTabbable(node); | |
}); | |
var posTabIndexesFound = !!tabbableNodes.find(function (node) { | |
return getTabIndex(node) > 0; | |
}); | |
return { | |
container: container, | |
tabbableNodes: tabbableNodes, | |
focusableNodes: focusableNodes, | |
/** True if at least one node with positive `tabindex` was found in this container. */ | |
posTabIndexesFound: posTabIndexesFound, | |
/** First tabbable node in container, __tabindex__ order; `undefined` if none. */ | |
firstTabbableNode: firstTabbableNode, | |
/** Last tabbable node in container, __tabindex__ order; `undefined` if none. */ | |
lastTabbableNode: lastTabbableNode, | |
// NOTE: DOM order is NOT NECESSARILY "document position" order, but figuring that out | |
// would require more than just https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition | |
// because that API doesn't work with Shadow DOM as well as it should (@see | |
// https://github.com/whatwg/dom/issues/320) and since this first/last is only needed, so far, | |
// to address an edge case related to positive tabindex support, this seems like a much easier, | |
// "close enough most of the time" alternative for positive tabindexes which should generally | |
// be avoided anyway... | |
/** First tabbable node in container, __DOM__ order; `undefined` if none. */ | |
firstDomTabbableNode: firstDomTabbableNode, | |
/** Last tabbable node in container, __DOM__ order; `undefined` if none. */ | |
lastDomTabbableNode: lastDomTabbableNode, | |
/** | |
* Finds the __tabbable__ node that follows the given node in the specified direction, | |
* in this container, if any. | |
* @param {HTMLElement} node | |
* @param {boolean} [forward] True if going in forward tab order; false if going | |
* in reverse. | |
* @returns {HTMLElement|undefined} The next tabbable node, if any. | |
*/ | |
nextTabbableNode: function nextTabbableNode(node) { | |
var forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | |
var nodeIdx = tabbableNodes.indexOf(node); | |
if (nodeIdx < 0) { | |
// either not tabbable nor focusable, or was focused but not tabbable (negative tabindex): | |
// since `node` should at least have been focusable, we assume that's the case and mimic | |
// what browsers do, which is set focus to the next node in __document position order__, | |
// regardless of positive tabindexes, if any -- and for reasons explained in the NOTE | |
// above related to `firstDomTabbable` and `lastDomTabbable` properties, we fall back to | |
// basic DOM order | |
if (forward) { | |
return focusableNodes.slice(focusableNodes.indexOf(node) + 1).find(function (el) { | |
return isTabbable(el); | |
}); | |
} | |
return focusableNodes.slice(0, focusableNodes.indexOf(node)).reverse().find(function (el) { | |
return isTabbable(el); | |
}); | |
} | |
return tabbableNodes[nodeIdx + (forward ? 1 : -1)]; | |
} | |
}; | |
}); | |
state.tabbableGroups = state.containerGroups.filter(function (group) { | |
return group.tabbableNodes.length > 0; | |
}); | |
// throw if no groups have tabbable nodes and we don't have a fallback focus node either | |
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option | |
) { | |
throw new Error('Your focus-trap must have at least one container with at least one tabbable node in it at all times'); | |
} | |
// NOTE: Positive tabindexes are only properly supported in single-container traps because | |
// doing it across multiple containers where tabindexes could be all over the place | |
// would require Tabbable to support multiple containers, would require additional | |
// specialized Shadow DOM support, and would require Tabbable's multi-container support | |
// to look at those containers in document position order rather than user-provided | |
// order (as they are treated in Focus-trap, for legacy reasons). See discussion on | |
// https://github.com/focus-trap/focus-trap/issues/375 for more details. | |
if (state.containerGroups.find(function (g) { | |
return g.posTabIndexesFound; | |
}) && state.containerGroups.length > 1) { | |
throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps."); | |
} | |
}; | |
/** | |
* Gets the current activeElement. If it's a web-component and has open shadow-root | |
* it will recursively search inside shadow roots for the "true" activeElement. | |
* | |
* @param {Document | ShadowRoot} el | |
* | |
* @returns {HTMLElement} The element that currently has the focus | |
**/ | |
var getActiveElement = function getActiveElement(el) { | |
var activeElement = el.activeElement; | |
if (!activeElement) { | |
return; | |
} | |
if (activeElement.shadowRoot && activeElement.shadowRoot.activeElement !== null) { | |
return getActiveElement(activeElement.shadowRoot); | |
} | |
return activeElement; | |
}; | |
var tryFocus = function tryFocus(node) { | |
if (node === false) { | |
return; | |
} | |
if (node === getActiveElement(document)) { | |
return; | |
} | |
if (!node || !node.focus) { | |
tryFocus(getInitialFocusNode()); | |
return; | |
} | |
node.focus({ | |
preventScroll: !!config.preventScroll | |
}); | |
// NOTE: focus() API does not trigger focusIn event so set MRU node manually | |
state.mostRecentlyFocusedNode = node; | |
if (isSelectableInput(node)) { | |
node.select(); | |
} | |
}; | |
var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) { | |
var node = getNodeForOption('setReturnFocus', previousActiveElement); | |
return node ? node : node === false ? false : previousActiveElement; | |
}; | |
/** | |
* Finds the next node (in either direction) where focus should move according to a | |
* keyboard focus-in event. | |
* @param {Object} params | |
* @param {Node} [params.target] Known target __from which__ to navigate, if any. | |
* @param {KeyboardEvent|FocusEvent} [params.event] Event to use if `target` isn't known (event | |
* will be used to determine the `target`). Ignored if `target` is specified. | |
* @param {boolean} [params.isBackward] True if focus should move backward. | |
* @returns {Node|undefined} The next node, or `undefined` if a next node couldn't be | |
* determined given the current state of the trap. | |
*/ | |
var findNextNavNode = function findNextNavNode(_ref2) { | |
var target = _ref2.target, | |
event = _ref2.event, | |
_ref2$isBackward = _ref2.isBackward, | |
isBackward = _ref2$isBackward === void 0 ? false : _ref2$isBackward; | |
target = target || getActualTarget(event); | |
updateTabbableNodes(); | |
var destinationNode = null; | |
if (state.tabbableGroups.length > 0) { | |
// make sure the target is actually contained in a group | |
// NOTE: the target may also be the container itself if it's focusable | |
// with tabIndex='-1' and was given initial focus | |
var containerIndex = findContainerIndex(target, event); | |
var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined; | |
if (containerIndex < 0) { | |
// target not found in any group: quite possible focus has escaped the trap, | |
// so bring it back into... | |
if (isBackward) { | |
// ...the last node in the last group | |
destinationNode = state.tabbableGroups[state.tabbableGroups.length - 1].lastTabbableNode; | |
} else { | |
// ...the first node in the first group | |
destinationNode = state.tabbableGroups[0].firstTabbableNode; | |
} | |
} else if (isBackward) { | |
// REVERSE | |
// is the target the first tabbable node in a group? | |
var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) { | |
var firstTabbableNode = _ref3.firstTabbableNode; | |
return target === firstTabbableNode; | |
}); | |
if (startOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) { | |
// an exception case where the target is either the container itself, or | |
// a non-tabbable node that was given focus (i.e. tabindex is negative | |
// and user clicked on it or node was programmatically given focus) | |
// and is not followed by any other tabbable node, in which | |
// case, we should handle shift+tab as if focus were on the container's | |
// first tabbable node, and go to the last tabbable node of the LAST group | |
startOfGroupIndex = containerIndex; | |
} | |
if (startOfGroupIndex >= 0) { | |
// YES: then shift+tab should go to the last tabbable node in the | |
// previous group (and wrap around to the last tabbable node of | |
// the LAST group if it's the first tabbable node of the FIRST group) | |
var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1; | |
var destinationGroup = state.tabbableGroups[destinationGroupIndex]; | |
destinationNode = getTabIndex(target) >= 0 ? destinationGroup.lastTabbableNode : destinationGroup.lastDomTabbableNode; | |
} else if (!isTabEvent(event)) { | |
// user must have customized the nav keys so we have to move focus manually _within_ | |
// the active group: do this based on the order determined by tabbable() | |
destinationNode = containerGroup.nextTabbableNode(target, false); | |
} | |
} else { | |
// FORWARD | |
// is the target the last tabbable node in a group? | |
var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref4) { | |
var lastTabbableNode = _ref4.lastTabbableNode; | |
return target === lastTabbableNode; | |
}); | |
if (lastOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) { | |
// an exception case where the target is the container itself, or | |
// a non-tabbable node that was given focus (i.e. tabindex is negative | |
// and user clicked on it or node was programmatically given focus) | |
// and is not followed by any other tabbable node, in which | |
// case, we should handle tab as if focus were on the container's | |
// last tabbable node, and go to the first tabbable node of the FIRST group | |
lastOfGroupIndex = containerIndex; | |
} | |
if (lastOfGroupIndex >= 0) { | |
// YES: then tab should go to the first tabbable node in the next | |
// group (and wrap around to the first tabbable node of the FIRST | |
// group if it's the last tabbable node of the LAST group) | |
var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1; | |
var _destinationGroup = state.tabbableGroups[_destinationGroupIndex]; | |
destinationNode = getTabIndex(target) >= 0 ? _destinationGroup.firstTabbableNode : _destinationGroup.firstDomTabbableNode; | |
} else if (!isTabEvent(event)) { | |
// user must have customized the nav keys so we have to move focus manually _within_ | |
// the active group: do this based on the order determined by tabbable() | |
destinationNode = containerGroup.nextTabbableNode(target); | |
} | |
} | |
} else { | |
// no groups available | |
// NOTE: the fallbackFocus option does not support returning false to opt-out | |
destinationNode = getNodeForOption('fallbackFocus'); | |
} | |
return destinationNode; | |
}; | |
// This needs to be done on mousedown and touchstart instead of click | |
// so that it precedes the focus event. | |
var checkPointerDown = function checkPointerDown(e) { | |
var target = getActualTarget(e); | |
if (findContainerIndex(target, e) >= 0) { | |
// allow the click since it ocurred inside the trap | |
return; | |
} | |
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | |
// immediately deactivate the trap | |
trap.deactivate({ | |
// NOTE: by setting `returnFocus: false`, deactivate() will do nothing, | |
// which will result in the outside click setting focus to the node | |
// that was clicked (and if not focusable, to "nothing"); by setting | |
// `returnFocus: true`, we'll attempt to re-focus the node originally-focused | |
// on activation (or the configured `setReturnFocus` node), whether the | |
// outside click was on a focusable node or not | |
returnFocus: config.returnFocusOnDeactivate | |
}); | |
return; | |
} | |
// This is needed for mobile devices. | |
// (If we'll only let `click` events through, | |
// then on mobile they will be blocked anyways if `touchstart` is blocked.) | |
if (valueOrHandler(config.allowOutsideClick, e)) { | |
// allow the click outside the trap to take place | |
return; | |
} | |
// otherwise, prevent the click | |
e.preventDefault(); | |
}; | |
// In case focus escapes the trap for some strange reason, pull it back in. | |
// NOTE: the focusIn event is NOT cancelable, so if focus escapes, it may cause unexpected | |
// scrolling if the node that got focused was out of view; there's nothing we can do to | |
// prevent that from happening by the time we discover that focus escaped | |
var checkFocusIn = function checkFocusIn(event) { | |
var target = getActualTarget(event); | |
var targetContained = findContainerIndex(target, event) >= 0; | |
// In Firefox when you Tab out of an iframe the Document is briefly focused. | |
if (targetContained || target instanceof Document) { | |
if (targetContained) { | |
state.mostRecentlyFocusedNode = target; | |
} | |
} else { | |
// escaped! pull it back in to where it just left | |
event.stopImmediatePropagation(); | |
// focus will escape if the MRU node had a positive tab index and user tried to nav forward; | |
// it will also escape if the MRU node had a 0 tab index and user tried to nav backward | |
// toward a node with a positive tab index | |
var nextNode; // next node to focus, if we find one | |
var navAcrossContainers = true; | |
if (state.mostRecentlyFocusedNode) { | |
if (getTabIndex(state.mostRecentlyFocusedNode) > 0) { | |
// MRU container index must be >=0 otherwise we wouldn't have it as an MRU node... | |
var mruContainerIdx = findContainerIndex(state.mostRecentlyFocusedNode); | |
// there MAY not be any tabbable nodes in the container if there are at least 2 containers | |
// and the MRU node is focusable but not tabbable (focus-trap requires at least 1 container | |
// with at least one tabbable node in order to function, so this could be the other container | |
// with nothing tabbable in it) | |
var tabbableNodes = state.containerGroups[mruContainerIdx].tabbableNodes; | |
if (tabbableNodes.length > 0) { | |
// MRU tab index MAY not be found if the MRU node is focusable but not tabbable | |
var mruTabIdx = tabbableNodes.findIndex(function (node) { | |
return node === state.mostRecentlyFocusedNode; | |
}); | |
if (mruTabIdx >= 0) { | |
if (config.isKeyForward(state.recentNavEvent)) { | |
if (mruTabIdx + 1 < tabbableNodes.length) { | |
nextNode = tabbableNodes[mruTabIdx + 1]; | |
navAcrossContainers = false; | |
} | |
// else, don't wrap within the container as focus should move to next/previous | |
// container | |
} else { | |
if (mruTabIdx - 1 >= 0) { | |
nextNode = tabbableNodes[mruTabIdx - 1]; | |
navAcrossContainers = false; | |
} | |
// else, don't wrap within the container as focus should move to next/previous | |
// container | |
} | |
// else, don't find in container order without considering direction too | |
} | |
} | |
// else, no tabbable nodes in that container (which means we must have at least one other | |
// container with at least one tabbable node in it, otherwise focus-trap would've thrown | |
// an error the last time updateTabbableNodes() was run): find next node among all known | |
// containers | |
} else { | |
// check to see if there's at least one tabbable node with a positive tab index inside | |
// the trap because focus seems to escape when navigating backward from a tabbable node | |
// with tabindex=0 when this is the case (instead of wrapping to the tabbable node with | |
// the greatest positive tab index like it should) | |
if (!state.containerGroups.some(function (g) { | |
return g.tabbableNodes.some(function (n) { | |
return getTabIndex(n) > 0; | |
}); | |
})) { | |
// no containers with tabbable nodes with positive tab indexes which means the focus | |
// escaped for some other reason and we should just execute the fallback to the | |
// MRU node or initial focus node, if any | |
navAcrossContainers = false; | |
} | |
} | |
} else { | |
// no MRU node means we're likely in some initial condition when the trap has just | |
// been activated and initial focus hasn't been given yet, in which case we should | |
// fall through to trying to focus the initial focus node, which is what should | |
// happen below at this point in the logic | |
navAcrossContainers = false; | |
} | |
if (navAcrossContainers) { | |
nextNode = findNextNavNode({ | |
// move FROM the MRU node, not event-related node (which will be the node that is | |
// outside the trap causing the focus escape we're trying to fix) | |
target: state.mostRecentlyFocusedNode, | |
isBackward: config.isKeyBackward(state.recentNavEvent) | |
}); | |
} | |
if (nextNode) { | |
tryFocus(nextNode); | |
} else { | |
tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode()); | |
} | |
} | |
state.recentNavEvent = undefined; // clear | |
}; | |
// Hijack key nav events on the first and last focusable nodes of the trap, | |
// in order to prevent focus from escaping. If it escapes for even a | |
// moment it can end up scrolling the page and causing confusion so we | |
// kind of need to capture the action at the keydown phase. | |
var checkKeyNav = function checkKeyNav(event) { | |
var isBackward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | |
state.recentNavEvent = event; | |
var destinationNode = findNextNavNode({ | |
event: event, | |
isBackward: isBackward | |
}); | |
if (destinationNode) { | |
if (isTabEvent(event)) { | |
// since tab natively moves focus, we wouldn't have a destination node unless we | |
// were on the edge of a container and had to move to the next/previous edge, in | |
// which case we want to prevent default to keep the browser from moving focus | |
// to where it normally would | |
event.preventDefault(); | |
} | |
tryFocus(destinationNode); | |
} | |
// else, let the browser take care of [shift+]tab and move the focus | |
}; | |
var checkKey = function checkKey(event) { | |
if (isEscapeEvent(event) && valueOrHandler(config.escapeDeactivates, event) !== false) { | |
event.preventDefault(); | |
trap.deactivate(); | |
return; | |
} | |
if (config.isKeyForward(event) || config.isKeyBackward(event)) { | |
checkKeyNav(event, config.isKeyBackward(event)); | |
} | |
}; | |
var checkClick = function checkClick(e) { | |
var target = getActualTarget(e); | |
if (findContainerIndex(target, e) >= 0) { | |
return; | |
} | |
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | |
return; | |
} | |
if (valueOrHandler(config.allowOutsideClick, e)) { | |
return; | |
} | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
}; | |
// | |
// EVENT LISTENERS | |
// | |
var addListeners = function addListeners() { | |
if (!state.active) { | |
return; | |
} | |
// There can be only one listening focus trap at a time | |
activeFocusTraps.activateTrap(trapStack, trap); | |
// Delay ensures that the focused element doesn't capture the event | |
// that caused the focus trap activation. | |
state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () { | |
tryFocus(getInitialFocusNode()); | |
}) : tryFocus(getInitialFocusNode()); | |
doc.addEventListener('focusin', checkFocusIn, true); | |
doc.addEventListener('mousedown', checkPointerDown, { | |
capture: true, | |
passive: false | |
}); | |
doc.addEventListener('touchstart', checkPointerDown, { | |
capture: true, | |
passive: false | |
}); | |
doc.addEventListener('click', checkClick, { | |
capture: true, | |
passive: false | |
}); | |
doc.addEventListener('keydown', checkKey, { | |
capture: true, | |
passive: false | |
}); | |
return trap; | |
}; | |
var removeListeners = function removeListeners() { | |
if (!state.active) { | |
return; | |
} | |
doc.removeEventListener('focusin', checkFocusIn, true); | |
doc.removeEventListener('mousedown', checkPointerDown, true); | |
doc.removeEventListener('touchstart', checkPointerDown, true); | |
doc.removeEventListener('click', checkClick, true); | |
doc.removeEventListener('keydown', checkKey, true); | |
return trap; | |
}; | |
// | |
// MUTATION OBSERVER | |
// | |
var checkDomRemoval = function checkDomRemoval(mutations) { | |
var isFocusedNodeRemoved = mutations.some(function (mutation) { | |
var removedNodes = Array.from(mutation.removedNodes); | |
return removedNodes.some(function (node) { | |
return node === state.mostRecentlyFocusedNode; | |
}); | |
}); | |
// If the currently focused is removed then browsers will move focus to the | |
// <body> element. If this happens, try to move focus back into the trap. | |
if (isFocusedNodeRemoved) { | |
tryFocus(getInitialFocusNode()); | |
} | |
}; | |
// Use MutationObserver - if supported - to detect if focused node is removed | |
// from the DOM. | |
var mutationObserver = typeof window !== 'undefined' && 'MutationObserver' in window ? new MutationObserver(checkDomRemoval) : undefined; | |
var updateObservedNodes = function updateObservedNodes() { | |
if (!mutationObserver) { | |
return; | |
} | |
mutationObserver.disconnect(); | |
if (state.active && !state.paused) { | |
state.containers.map(function (container) { | |
mutationObserver.observe(container, { | |
subtree: true, | |
childList: true | |
}); | |
}); | |
} | |
}; | |
// | |
// TRAP DEFINITION | |
// | |
trap = { | |
get active() { | |
return state.active; | |
}, | |
get paused() { | |
return state.paused; | |
}, | |
activate: function activate(activateOptions) { | |
if (state.active) { | |
return this; | |
} | |
var onActivate = getOption(activateOptions, 'onActivate'); | |
var onPostActivate = getOption(activateOptions, 'onPostActivate'); | |
var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap'); | |
if (!checkCanFocusTrap) { | |
updateTabbableNodes(); | |
} | |
state.active = true; | |
state.paused = false; | |
state.nodeFocusedBeforeActivation = doc.activeElement; | |
onActivate === null || onActivate === void 0 || onActivate(); | |
var finishActivation = function finishActivation() { | |
if (checkCanFocusTrap) { | |
updateTabbableNodes(); | |
} | |
addListeners(); | |
updateObservedNodes(); | |
onPostActivate === null || onPostActivate === void 0 || onPostActivate(); | |
}; | |
if (checkCanFocusTrap) { | |
checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation); | |
return this; | |
} | |
finishActivation(); | |
return this; | |
}, | |
deactivate: function deactivate(deactivateOptions) { | |
if (!state.active) { | |
return this; | |
} | |
var options = _objectSpread2({ | |
onDeactivate: config.onDeactivate, | |
onPostDeactivate: config.onPostDeactivate, | |
checkCanReturnFocus: config.checkCanReturnFocus | |
}, deactivateOptions); | |
clearTimeout(state.delayInitialFocusTimer); // noop if undefined | |
state.delayInitialFocusTimer = undefined; | |
removeListeners(); | |
state.active = false; | |
state.paused = false; | |
updateObservedNodes(); | |
activeFocusTraps.deactivateTrap(trapStack, trap); | |
var onDeactivate = getOption(options, 'onDeactivate'); | |
var onPostDeactivate = getOption(options, 'onPostDeactivate'); | |
var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus'); | |
var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate'); | |
onDeactivate === null || onDeactivate === void 0 || onDeactivate(); | |
var finishDeactivation = function finishDeactivation() { | |
delay(function () { | |
if (returnFocus) { | |
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)); | |
} | |
onPostDeactivate === null || onPostDeactivate === void 0 || onPostDeactivate(); | |
}); | |
}; | |
if (returnFocus && checkCanReturnFocus) { | |
checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation); | |
return this; | |
} | |
finishDeactivation(); | |
return this; | |
}, | |
pause: function pause(pauseOptions) { | |
if (state.paused || !state.active) { | |
return this; | |
} | |
var onPause = getOption(pauseOptions, 'onPause'); | |
var onPostPause = getOption(pauseOptions, 'onPostPause'); | |
state.paused = true; | |
onPause === null || onPause === void 0 || onPause(); | |
removeListeners(); | |
updateObservedNodes(); | |
onPostPause === null || onPostPause === void 0 || onPostPause(); | |
return this; | |
}, | |
unpause: function unpause(unpauseOptions) { | |
if (!state.paused || !state.active) { | |
return this; | |
} | |
var onUnpause = getOption(unpauseOptions, 'onUnpause'); | |
var onPostUnpause = getOption(unpauseOptions, 'onPostUnpause'); | |
state.paused = false; | |
onUnpause === null || onUnpause === void 0 || onUnpause(); | |
updateTabbableNodes(); | |
addListeners(); | |
updateObservedNodes(); | |
onPostUnpause === null || onPostUnpause === void 0 || onPostUnpause(); | |
return this; | |
}, | |
updateContainerElements: function updateContainerElements(containerElements) { | |
var elementsAsArray = [].concat(containerElements).filter(Boolean); | |
state.containers = elementsAsArray.map(function (element) { | |
return typeof element === 'string' ? doc.querySelector(element) : element; | |
}); | |
if (state.active) { | |
updateTabbableNodes(); | |
} | |
updateObservedNodes(); | |
return this; | |
} | |
}; | |
// initialize container elements | |
trap.updateContainerElements(elements); | |
return trap; | |
}; | |
// src/dialog.anatomy.ts | |
var anatomy$4 = createAnatomy$1("dialog").parts( | |
"trigger", | |
"backdrop", | |
"positioner", | |
"content", | |
"title", | |
"description", | |
"closeTrigger" | |
); | |
var parts$3 = anatomy$4.build(); | |
var dom$4 = createScope({ | |
getPositionerId: (ctx) => ctx.ids?.positioner ?? `dialog:${ctx.id}:positioner`, | |
getBackdropId: (ctx) => ctx.ids?.backdrop ?? `dialog:${ctx.id}:backdrop`, | |
getContentId: (ctx) => ctx.ids?.content ?? `dialog:${ctx.id}:content`, | |
getTriggerId: (ctx) => ctx.ids?.trigger ?? `dialog:${ctx.id}:trigger`, | |
getTitleId: (ctx) => ctx.ids?.title ?? `dialog:${ctx.id}:title`, | |
getDescriptionId: (ctx) => ctx.ids?.description ?? `dialog:${ctx.id}:description`, | |
getCloseTriggerId: (ctx) => ctx.ids?.closeTrigger ?? `dialog:${ctx.id}:close`, | |
getContentEl: (ctx) => dom$4.getById(ctx, dom$4.getContentId(ctx)), | |
getPositionerEl: (ctx) => dom$4.getById(ctx, dom$4.getPositionerId(ctx)), | |
getBackdropEl: (ctx) => dom$4.getById(ctx, dom$4.getBackdropId(ctx)), | |
getTriggerEl: (ctx) => dom$4.getById(ctx, dom$4.getTriggerId(ctx)), | |
getTitleEl: (ctx) => dom$4.getById(ctx, dom$4.getTitleId(ctx)), | |
getDescriptionEl: (ctx) => dom$4.getById(ctx, dom$4.getDescriptionId(ctx)), | |
getCloseTriggerEl: (ctx) => dom$4.getById(ctx, dom$4.getCloseTriggerId(ctx)) | |
}); | |
// src/dialog.connect.ts | |
function connect$4(state, send, normalize) { | |
const ariaLabel = state.context["aria-label"]; | |
const open = state.matches("open"); | |
const rendered = state.context.renderedElements; | |
return { | |
open, | |
setOpen(nextOpen) { | |
if (nextOpen === open) return; | |
send(nextOpen ? "OPEN" : "CLOSE"); | |
}, | |
getTriggerProps() { | |
return normalize.button({ | |
...parts$3.trigger.attrs, | |
dir: state.context.dir, | |
id: dom$4.getTriggerId(state.context), | |
"aria-haspopup": "dialog", | |
type: "button", | |
"aria-expanded": open, | |
"data-state": open ? "open" : "closed", | |
"aria-controls": dom$4.getContentId(state.context), | |
onClick(event) { | |
if (event.defaultPrevented) return; | |
send("TOGGLE"); | |
} | |
}); | |
}, | |
getBackdropProps() { | |
return normalize.element({ | |
...parts$3.backdrop.attrs, | |
dir: state.context.dir, | |
hidden: !open, | |
id: dom$4.getBackdropId(state.context), | |
"data-state": open ? "open" : "closed" | |
}); | |
}, | |
getPositionerProps() { | |
return normalize.element({ | |
...parts$3.positioner.attrs, | |
dir: state.context.dir, | |
id: dom$4.getPositionerId(state.context), | |
style: { | |
pointerEvents: open ? void 0 : "none" | |
} | |
}); | |
}, | |
getContentProps() { | |
return normalize.element({ | |
...parts$3.content.attrs, | |
dir: state.context.dir, | |
role: state.context.role, | |
hidden: !open, | |
id: dom$4.getContentId(state.context), | |
tabIndex: -1, | |
"data-state": open ? "open" : "closed", | |
"aria-modal": true, | |
"aria-label": ariaLabel || void 0, | |
"aria-labelledby": ariaLabel || !rendered.title ? void 0 : dom$4.getTitleId(state.context), | |
"aria-describedby": rendered.description ? dom$4.getDescriptionId(state.context) : void 0 | |
}); | |
}, | |
getTitleProps() { | |
return normalize.element({ | |
...parts$3.title.attrs, | |
dir: state.context.dir, | |
id: dom$4.getTitleId(state.context) | |
}); | |
}, | |
getDescriptionProps() { | |
return normalize.element({ | |
...parts$3.description.attrs, | |
dir: state.context.dir, | |
id: dom$4.getDescriptionId(state.context) | |
}); | |
}, | |
getCloseTriggerProps() { | |
return normalize.button({ | |
...parts$3.closeTrigger.attrs, | |
dir: state.context.dir, | |
id: dom$4.getCloseTriggerId(state.context), | |
type: "button", | |
onClick(event) { | |
if (event.defaultPrevented) return; | |
event.stopPropagation(); | |
send("CLOSE"); | |
} | |
}); | |
} | |
}; | |
} | |
function machine$3(userContext) { | |
const ctx = compact(userContext); | |
return createMachine( | |
{ | |
id: "dialog", | |
initial: ctx.open ? "open" : "closed", | |
context: { | |
role: "dialog", | |
renderedElements: { | |
title: true, | |
description: true | |
}, | |
modal: true, | |
trapFocus: true, | |
preventScroll: true, | |
closeOnInteractOutside: true, | |
closeOnEscape: true, | |
restoreFocus: true, | |
...ctx | |
}, | |
created: ["checkInitialFocusEl"], | |
watch: { | |
open: ["toggleVisibility"] | |
}, | |
states: { | |
open: { | |
entry: ["checkRenderedElements", "syncZIndex"], | |
activities: ["trackDismissableElement", "trapFocus", "preventScroll", "hideContentBelow"], | |
on: { | |
"CONTROLLED.CLOSE": { | |
target: "closed", | |
actions: ["setFinalFocus"] | |
}, | |
CLOSE: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["invokeOnClose"] | |
}, | |
{ | |
target: "closed", | |
actions: ["invokeOnClose", "setFinalFocus"] | |
} | |
], | |
TOGGLE: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["invokeOnClose"] | |
}, | |
{ | |
target: "closed", | |
actions: ["invokeOnClose", "setFinalFocus"] | |
} | |
] | |
} | |
}, | |
closed: { | |
on: { | |
"CONTROLLED.OPEN": { | |
target: "open" | |
}, | |
OPEN: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["invokeOnOpen"] | |
}, | |
{ | |
target: "open", | |
actions: ["invokeOnOpen"] | |
} | |
], | |
TOGGLE: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["invokeOnOpen"] | |
}, | |
{ | |
target: "open", | |
actions: ["invokeOnOpen"] | |
} | |
] | |
} | |
} | |
} | |
}, | |
{ | |
guards: { | |
isOpenControlled: (ctx2) => !!ctx2["open.controlled"] | |
}, | |
activities: { | |
trackDismissableElement(ctx2, _evt, { send }) { | |
const getContentEl = () => dom$4.getContentEl(ctx2); | |
return trackDismissableElement(getContentEl, { | |
defer: true, | |
pointerBlocking: ctx2.modal, | |
exclude: [dom$4.getTriggerEl(ctx2)], | |
onInteractOutside(event) { | |
ctx2.onInteractOutside?.(event); | |
if (!ctx2.closeOnInteractOutside || ctx2.role === "alertdialog") { | |
event.preventDefault(); | |
} | |
}, | |
persistentElements: ctx2.persistentElements, | |
onFocusOutside: ctx2.onFocusOutside, | |
onPointerDownOutside: ctx2.onPointerDownOutside, | |
onEscapeKeyDown(event) { | |
ctx2.onEscapeKeyDown?.(event); | |
if (!ctx2.closeOnEscape) { | |
event.preventDefault(); | |
} else { | |
send({ type: "CLOSE", src: "escape-key" }); | |
} | |
}, | |
onDismiss() { | |
send({ type: "CLOSE", src: "interact-outside" }); | |
} | |
}); | |
}, | |
preventScroll(ctx2) { | |
if (!ctx2.preventScroll) return; | |
return preventBodyScroll(dom$4.getDoc(ctx2)); | |
}, | |
trapFocus(ctx2) { | |
if (!ctx2.trapFocus || !ctx2.modal) return; | |
let trap; | |
const cleanup = nextTick(() => { | |
const contentEl = dom$4.getContentEl(ctx2); | |
if (!contentEl) return; | |
trap = createFocusTrap(contentEl, { | |
document: dom$4.getDoc(ctx2), | |
escapeDeactivates: false, | |
preventScroll: true, | |
returnFocusOnDeactivate: false, | |
fallbackFocus: contentEl, | |
allowOutsideClick: true, | |
initialFocus: getInitialFocus({ | |
root: contentEl, | |
getInitialEl: ctx2.initialFocusEl | |
}) | |
}); | |
try { | |
trap.activate(); | |
} catch { | |
} | |
}); | |
return () => { | |
trap?.deactivate(); | |
cleanup(); | |
}; | |
}, | |
hideContentBelow(ctx2) { | |
if (!ctx2.modal) return; | |
const getElements = () => [dom$4.getContentEl(ctx2)]; | |
return ariaHidden(getElements, { defer: true }); | |
} | |
}, | |
actions: { | |
checkInitialFocusEl(ctx2) { | |
if (!ctx2.initialFocusEl && ctx2.role === "alertdialog") { | |
ctx2.initialFocusEl = () => dom$4.getCloseTriggerEl(ctx2); | |
} | |
}, | |
checkRenderedElements(ctx2) { | |
raf(() => { | |
ctx2.renderedElements.title = !!dom$4.getTitleEl(ctx2); | |
ctx2.renderedElements.description = !!dom$4.getDescriptionEl(ctx2); | |
}); | |
}, | |
syncZIndex(ctx2) { | |
raf(() => { | |
const contentEl = dom$4.getContentEl(ctx2); | |
if (!contentEl) return; | |
const win = dom$4.getWin(ctx2); | |
const styles = win.getComputedStyle(contentEl); | |
const elems = [dom$4.getPositionerEl(ctx2), dom$4.getBackdropEl(ctx2)]; | |
elems.forEach((node) => { | |
node?.style.setProperty("--z-index", styles.zIndex); | |
}); | |
}); | |
}, | |
invokeOnClose(ctx2) { | |
ctx2.onOpenChange?.({ open: false }); | |
}, | |
invokeOnOpen(ctx2) { | |
ctx2.onOpenChange?.({ open: true }); | |
}, | |
toggleVisibility(ctx2, evt, { send }) { | |
send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt }); | |
}, | |
setFinalFocus(ctx2) { | |
if (!ctx2.restoreFocus) return; | |
queueMicrotask(() => { | |
const el = ctx2.finalFocusEl?.() ?? dom$4.getTriggerEl(ctx2); | |
el?.focus({ preventScroll: true }); | |
}); | |
} | |
} | |
} | |
); | |
} | |
createProps()([ | |
"aria-label", | |
"closeOnEscape", | |
"closeOnInteractOutside", | |
"dir", | |
"finalFocusEl", | |
"getRootNode", | |
"getRootNode", | |
"id", | |
"id", | |
"ids", | |
"initialFocusEl", | |
"modal", | |
"onEscapeKeyDown", | |
"onFocusOutside", | |
"onInteractOutside", | |
"onOpenChange", | |
"onPointerDownOutside", | |
"open.controlled", | |
"open", | |
"persistentElements", | |
"preventScroll", | |
"restoreFocus", | |
"role", | |
"trapFocus" | |
]); | |
// src/rect.ts | |
var createPoint = (x, y) => ({ x, y }); | |
function createRect(r) { | |
const { x, y, width, height } = r; | |
const midX = x + width / 2; | |
const midY = y + height / 2; | |
return { | |
x, | |
y, | |
width, | |
height, | |
minX: x, | |
minY: y, | |
maxX: x + width, | |
maxY: y + height, | |
midX, | |
midY, | |
center: createPoint(midX, midY) | |
}; | |
} | |
function getRectCorners(v) { | |
const top = createPoint(v.minX, v.minY); | |
const right = createPoint(v.maxX, v.minY); | |
const bottom = createPoint(v.maxX, v.maxY); | |
const left = createPoint(v.minX, v.maxY); | |
return { top, right, bottom, left }; | |
} | |
// src/polygon.ts | |
function getElementPolygon(rectValue, placement) { | |
const rect = createRect(rectValue); | |
const { top, right, left, bottom } = getRectCorners(rect); | |
const [base] = placement.split("-"); | |
return { | |
top: [left, top, right, bottom], | |
right: [top, right, bottom, left], | |
bottom: [top, left, bottom, right], | |
left: [right, top, left, bottom] | |
}[base]; | |
} | |
function isPointInPolygon(polygon, point) { | |
const { x, y } = point; | |
let c = false; | |
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { | |
const xi = polygon[i].x; | |
const yi = polygon[i].y; | |
const xj = polygon[j].x; | |
const yj = polygon[j].y; | |
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) { | |
c = !c; | |
} | |
} | |
return c; | |
} | |
// src/menu.anatomy.ts | |
var anatomy$3 = createAnatomy$1("menu").parts( | |
"arrow", | |
"arrowTip", | |
"content", | |
"contextTrigger", | |
"indicator", | |
"item", | |
"itemGroup", | |
"itemGroupLabel", | |
"itemIndicator", | |
"itemText", | |
"positioner", | |
"separator", | |
"trigger", | |
"triggerItem" | |
); | |
var parts$2 = anatomy$3.build(); | |
var dom$3 = createScope({ | |
getTriggerId: (ctx) => ctx.ids?.trigger ?? `menu:${ctx.id}:trigger`, | |
getContextTriggerId: (ctx) => ctx.ids?.contextTrigger ?? `menu:${ctx.id}:ctx-trigger`, | |
getContentId: (ctx) => ctx.ids?.content ?? `menu:${ctx.id}:content`, | |
getArrowId: (ctx) => ctx.ids?.arrow ?? `menu:${ctx.id}:arrow`, | |
getPositionerId: (ctx) => ctx.ids?.positioner ?? `menu:${ctx.id}:popper`, | |
getGroupId: (ctx, id) => ctx.ids?.group?.(id) ?? `menu:${ctx.id}:group:${id}`, | |
getGroupLabelId: (ctx, id) => ctx.ids?.label?.(id) ?? `menu:${ctx.id}:label:${id}`, | |
getContentEl: (ctx) => dom$3.getById(ctx, dom$3.getContentId(ctx)), | |
getPositionerEl: (ctx) => dom$3.getById(ctx, dom$3.getPositionerId(ctx)), | |
getTriggerEl: (ctx) => dom$3.getById(ctx, dom$3.getTriggerId(ctx)), | |
getHighlightedItemEl: (ctx) => ctx.highlightedValue ? dom$3.getById(ctx, ctx.highlightedValue) : null, | |
getArrowEl: (ctx) => dom$3.getById(ctx, dom$3.getArrowId(ctx)), | |
getElements: (ctx) => { | |
const ownerId = CSS.escape(dom$3.getContentId(ctx)); | |
const selector = `[role^="menuitem"][data-ownedby=${ownerId}]:not([data-disabled])`; | |
return queryAll(dom$3.getContentEl(ctx), selector); | |
}, | |
getFirstEl: (ctx) => first(dom$3.getElements(ctx)), | |
getLastEl: (ctx) => last(dom$3.getElements(ctx)), | |
getNextEl: (ctx, loop) => nextById(dom$3.getElements(ctx), ctx.highlightedValue, loop ?? ctx.loopFocus), | |
getPrevEl: (ctx, loop) => prevById(dom$3.getElements(ctx), ctx.highlightedValue, loop ?? ctx.loopFocus), | |
getElemByKey: (ctx, key) => getByTypeahead(dom$3.getElements(ctx), { state: ctx.typeaheadState, key, activeId: ctx.highlightedValue }), | |
isTargetDisabled: (v) => { | |
return isHTMLElement$1(v) && (v.dataset.disabled === "" || v.hasAttribute("disabled")); | |
}, | |
isTriggerItem: (el) => { | |
return !!el?.getAttribute("role")?.startsWith("menuitem") && !!el?.hasAttribute("aria-controls"); | |
}, | |
getOptionFromItemEl(el) { | |
return { | |
id: el.id, | |
name: el.dataset.name, | |
value: el.dataset.value, | |
valueText: el.dataset.valueText, | |
type: el.dataset.type | |
}; | |
} | |
}); | |
// src/menu.connect.ts | |
function connect$3(state, send, normalize) { | |
const isSubmenu = state.context.isSubmenu; | |
const isTypingAhead = state.context.isTypingAhead; | |
const composite = state.context.composite; | |
const open = state.hasTag("open"); | |
const popperStyles = getPlacementStyles({ | |
...state.context.positioning, | |
placement: state.context.anchorPoint ? "bottom" : state.context.currentPlacement | |
}); | |
function getItemState(props2) { | |
return { | |
disabled: !!props2.disabled, | |
highlighted: state.context.highlightedValue === props2.value | |
}; | |
} | |
function getOptionItemProps(props2) { | |
const valueText = props2.valueText ?? props2.value; | |
return { ...props2, id: props2.value, valueText }; | |
} | |
function getOptionItemState(props2) { | |
const itemState = getItemState(getOptionItemProps(props2)); | |
return { | |
...itemState, | |
checked: !!props2.checked | |
}; | |
} | |
function getItemProps(props2) { | |
const { value: id, closeOnSelect, valueText } = props2; | |
const itemState = getItemState(props2); | |
return normalize.element({ | |
...parts$2.item.attrs, | |
id, | |
role: "menuitem", | |
"aria-disabled": itemState.disabled, | |
"data-disabled": dataAttr(itemState.disabled), | |
"data-ownedby": dom$3.getContentId(state.context), | |
"data-highlighted": dataAttr(itemState.highlighted), | |
"data-valuetext": valueText, | |
onDragStart(event) { | |
const isLink = event.currentTarget.matches("a[href]"); | |
if (isLink) event.preventDefault(); | |
}, | |
onPointerMove(event) { | |
if (itemState.disabled) return; | |
if (event.pointerType !== "mouse") return; | |
const target = event.currentTarget; | |
if (itemState.highlighted) return; | |
send({ type: "ITEM_POINTERMOVE", id, target, closeOnSelect }); | |
}, | |
onPointerLeave(event) { | |
if (itemState.disabled) return; | |
if (event.pointerType !== "mouse") return; | |
const mouseMoved = state.previousEvent.type.includes("POINTER"); | |
if (!mouseMoved) return; | |
const target = event.currentTarget; | |
send({ type: "ITEM_POINTERLEAVE", id, target, closeOnSelect }); | |
}, | |
onPointerDown(event) { | |
if (itemState.disabled) return; | |
const target = event.currentTarget; | |
send({ type: "ITEM_POINTERDOWN", target, id, closeOnSelect }); | |
}, | |
onPointerUp(event) { | |
if (isDownloadingEvent(event)) return; | |
if (isOpeningInNewTab(event)) return; | |
if (itemState.disabled) return; | |
if (!isLeftClick(event)) return; | |
const target = event.currentTarget; | |
send({ type: "ITEM_CLICK", src: "pointerup", target, id, closeOnSelect }); | |
if (event.pointerType === "touch") clickIfLink(target); | |
}, | |
onTouchEnd(event) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
} | |
}); | |
} | |
return { | |
highlightedValue: state.context.highlightedValue, | |
open, | |
setOpen(nextOpen) { | |
if (nextOpen === open) return; | |
send(nextOpen ? "OPEN" : "CLOSE"); | |
}, | |
setHighlightedValue(value) { | |
send({ type: "HIGHLIGHTED.SET", id: value }); | |
}, | |
setParent(parent) { | |
send({ type: "PARENT.SET", value: parent, id: parent.state.context.id }); | |
}, | |
setChild(child) { | |
send({ type: "CHILD.SET", value: child, id: child.state.context.id }); | |
}, | |
reposition(options = {}) { | |
send({ type: "POSITIONING.SET", options }); | |
}, | |
getContextTriggerProps() { | |
return normalize.element({ | |
...parts$2.contextTrigger.attrs, | |
dir: state.context.dir, | |
id: dom$3.getContextTriggerId(state.context), | |
onPointerDown(event) { | |
if (event.pointerType === "mouse") return; | |
const point = getEventPoint(event); | |
send({ type: "CONTEXT_MENU_START", point }); | |
}, | |
onPointerCancel(event) { | |
if (event.pointerType === "mouse") return; | |
send("CONTEXT_MENU_CANCEL"); | |
}, | |
onPointerMove(event) { | |
if (event.pointerType === "mouse") return; | |
send("CONTEXT_MENU_CANCEL"); | |
}, | |
onPointerUp(event) { | |
if (event.pointerType === "mouse") return; | |
send("CONTEXT_MENU_CANCEL"); | |
}, | |
onContextMenu(event) { | |
const point = getEventPoint(event); | |
send({ type: "CONTEXT_MENU", point }); | |
event.preventDefault(); | |
}, | |
style: { | |
WebkitTouchCallout: "none", | |
userSelect: "none" | |
} | |
}); | |
}, | |
getTriggerItemProps(childApi) { | |
return mergeProps$1( | |
getItemProps({ value: childApi.getTriggerProps().id }), | |
childApi.getTriggerProps() | |
); | |
}, | |
getTriggerProps() { | |
return normalize.button({ | |
...isSubmenu ? parts$2.triggerItem.attrs : parts$2.trigger.attrs, | |
"data-placement": state.context.currentPlacement, | |
type: "button", | |
dir: state.context.dir, | |
id: dom$3.getTriggerId(state.context), | |
"data-uid": state.context.id, | |
"aria-haspopup": composite ? "menu" : "dialog", | |
"aria-controls": dom$3.getContentId(state.context), | |
"aria-expanded": open || void 0, | |
"data-state": open ? "open" : "closed", | |
onPointerMove(event) { | |
if (event.pointerType !== "mouse") return; | |
const disabled = dom$3.isTargetDisabled(event.currentTarget); | |
if (disabled || !isSubmenu) return; | |
send({ type: "TRIGGER_POINTERMOVE", target: event.currentTarget }); | |
}, | |
onPointerLeave(event) { | |
if (event.pointerType !== "mouse") return; | |
const disabled = dom$3.isTargetDisabled(event.currentTarget); | |
if (disabled || !isSubmenu) return; | |
const point = getEventPoint(event); | |
send({ type: "TRIGGER_POINTERLEAVE", target: event.currentTarget, point }); | |
}, | |
onClick(event) { | |
if (dom$3.isTriggerItem(event.currentTarget)) { | |
send({ type: "TRIGGER_CLICK", target: event.currentTarget }); | |
} | |
}, | |
onPointerDown(event) { | |
const disabled = dom$3.isTargetDisabled(event.currentTarget); | |
if (!isLeftClick(event) || disabled || isContextMenuEvent(event)) return; | |
event.preventDefault(); | |
if (!dom$3.isTriggerItem(event.currentTarget)) { | |
send({ type: "TRIGGER_CLICK", target: event.currentTarget }); | |
} | |
}, | |
onBlur() { | |
send("TRIGGER_BLUR"); | |
}, | |
onFocus() { | |
send("TRIGGER_FOCUS"); | |
}, | |
onKeyDown(event) { | |
if (event.defaultPrevented) return; | |
const keyMap = { | |
ArrowDown() { | |
send("ARROW_DOWN"); | |
}, | |
ArrowUp() { | |
send("ARROW_UP"); | |
}, | |
Enter() { | |
send({ type: "ARROW_DOWN", src: "enter" }); | |
}, | |
Space() { | |
send({ type: "ARROW_DOWN", src: "space" }); | |
} | |
}; | |
const key = getEventKey(event, state.context); | |
const exec = keyMap[key]; | |
if (exec) { | |
event.preventDefault(); | |
exec(event); | |
} | |
} | |
}); | |
}, | |
getIndicatorProps() { | |
return normalize.element({ | |
...parts$2.indicator.attrs, | |
dir: state.context.dir, | |
"data-state": open ? "open" : "closed" | |
}); | |
}, | |
getPositionerProps() { | |
return normalize.element({ | |
...parts$2.positioner.attrs, | |
dir: state.context.dir, | |
id: dom$3.getPositionerId(state.context), | |
style: popperStyles.floating | |
}); | |
}, | |
getArrowProps() { | |
return normalize.element({ | |
id: dom$3.getArrowId(state.context), | |
...parts$2.arrow.attrs, | |
dir: state.context.dir, | |
style: popperStyles.arrow | |
}); | |
}, | |
getArrowTipProps() { | |
return normalize.element({ | |
...parts$2.arrowTip.attrs, | |
dir: state.context.dir, | |
style: popperStyles.arrowTip | |
}); | |
}, | |
getContentProps() { | |
return normalize.element({ | |
...parts$2.content.attrs, | |
id: dom$3.getContentId(state.context), | |
"aria-label": state.context["aria-label"], | |
hidden: !open, | |
"data-state": open ? "open" : "closed", | |
role: composite ? "menu" : "dialog", | |
tabIndex: 0, | |
dir: state.context.dir, | |
"aria-activedescendant": state.context.highlightedValue ?? void 0, | |
"aria-labelledby": dom$3.getTriggerId(state.context), | |
"data-placement": state.context.currentPlacement, | |
onPointerEnter(event) { | |
if (event.pointerType !== "mouse") return; | |
send("MENU_POINTERENTER"); | |
}, | |
onKeyDown(event) { | |
if (event.defaultPrevented) return; | |
if (!isSelfTarget(event)) return; | |
const target = getEventTarget(event); | |
const sameMenu = target?.closest("[role=menu]") === event.currentTarget || target === event.currentTarget; | |
if (!sameMenu) return; | |
if (event.key === "Tab") { | |
const valid = isValidTabEvent(event); | |
if (!valid) { | |
event.preventDefault(); | |
return; | |
} | |
} | |
const item = dom$3.getHighlightedItemEl(state.context); | |
const keyMap = { | |
ArrowDown() { | |
send("ARROW_DOWN"); | |
}, | |
ArrowUp() { | |
send("ARROW_UP"); | |
}, | |
ArrowLeft() { | |
send("ARROW_LEFT"); | |
}, | |
ArrowRight() { | |
send("ARROW_RIGHT"); | |
}, | |
Enter() { | |
send("ENTER"); | |
clickIfLink(item); | |
}, | |
Space(event2) { | |
if (isTypingAhead) { | |
send({ type: "TYPEAHEAD", key: event2.key }); | |
} else { | |
keyMap.Enter?.(event2); | |
} | |
}, | |
Home() { | |
send("HOME"); | |
}, | |
End() { | |
send("END"); | |
} | |
}; | |
const key = getEventKey(event, { dir: state.context.dir }); | |
const exec = keyMap[key]; | |
if (exec) { | |
exec(event); | |
event.stopPropagation(); | |
event.preventDefault(); | |
return; | |
} | |
if (!state.context.typeahead) return; | |
if (!isPrintableKey(event)) return; | |
if (isModifierKey(event)) return; | |
if (isEditableElement(target)) return; | |
send({ type: "TYPEAHEAD", key: event.key }); | |
event.preventDefault(); | |
} | |
}); | |
}, | |
getSeparatorProps() { | |
return normalize.element({ | |
...parts$2.separator.attrs, | |
role: "separator", | |
dir: state.context.dir, | |
"aria-orientation": "horizontal" | |
}); | |
}, | |
getItemState, | |
getItemProps, | |
getOptionItemState, | |
getOptionItemProps(props2) { | |
const { type, disabled, onCheckedChange, closeOnSelect } = props2; | |
const option = getOptionItemProps(props2); | |
const itemState = getOptionItemState(props2); | |
return { | |
...getItemProps(option), | |
...normalize.element({ | |
"data-type": type, | |
...parts$2.item.attrs, | |
dir: state.context.dir, | |
"data-value": option.value, | |
role: `menuitem${type}`, | |
"aria-checked": !!itemState.checked, | |
"data-state": itemState.checked ? "checked" : "unchecked", | |
onPointerUp(event) { | |
if (!isLeftClick(event) || disabled) return; | |
if (isDownloadingEvent(event)) return; | |
if (isOpeningInNewTab(event)) return; | |
const target = event.currentTarget; | |
send({ type: "ITEM_CLICK", src: "pointerup", target, option, closeOnSelect }); | |
onCheckedChange?.(!itemState.checked); | |
} | |
}) | |
}; | |
}, | |
getItemIndicatorProps(props2) { | |
const itemState = getOptionItemState(props2); | |
return normalize.element({ | |
...parts$2.itemIndicator.attrs, | |
dir: state.context.dir, | |
"data-disabled": dataAttr(itemState.disabled), | |
"data-highlighted": dataAttr(itemState.highlighted), | |
"data-state": itemState.checked ? "checked" : "unchecked", | |
hidden: !itemState.checked | |
}); | |
}, | |
getItemTextProps(props2) { | |
const itemState = getOptionItemState(props2); | |
return normalize.element({ | |
...parts$2.itemText.attrs, | |
dir: state.context.dir, | |
"data-disabled": dataAttr(itemState.disabled), | |
"data-highlighted": dataAttr(itemState.highlighted), | |
"data-state": itemState.checked ? "checked" : "unchecked" | |
}); | |
}, | |
getItemGroupLabelProps(props2) { | |
return normalize.element({ | |
id: dom$3.getGroupLabelId(state.context, props2.htmlFor), | |
dir: state.context.dir, | |
...parts$2.itemGroupLabel.attrs | |
}); | |
}, | |
getItemGroupProps(props2) { | |
return normalize.element({ | |
id: dom$3.getGroupId(state.context, props2.id), | |
...parts$2.itemGroup.attrs, | |
dir: state.context.dir, | |
"aria-labelledby": dom$3.getGroupLabelId(state.context, props2.id), | |
role: "group" | |
}); | |
} | |
}; | |
} | |
var { not: not$1, and: and$1, or: or$1 } = guards; | |
function machine$2(userContext) { | |
const ctx = compact(userContext); | |
return createMachine( | |
{ | |
id: "menu", | |
initial: ctx.open ? "open" : "idle", | |
context: { | |
highlightedValue: null, | |
loopFocus: false, | |
anchorPoint: null, | |
closeOnSelect: true, | |
typeahead: true, | |
composite: true, | |
...ctx, | |
positioning: { | |
placement: "bottom-start", | |
gutter: 8, | |
...ctx.positioning | |
}, | |
intentPolygon: null, | |
parent: null, | |
lastHighlightedValue: null, | |
children: cast(ref({})), | |
suspendPointer: false, | |
restoreFocus: true, | |
typeaheadState: getByTypeahead.defaultOptions | |
}, | |
computed: { | |
isSubmenu: (ctx2) => ctx2.parent !== null, | |
isRtl: (ctx2) => ctx2.dir === "rtl", | |
isTypingAhead: (ctx2) => ctx2.typeaheadState.keysSoFar !== "" | |
}, | |
watch: { | |
isSubmenu: "setSubmenuPlacement", | |
anchorPoint: "reposition", | |
open: "toggleVisibility" | |
}, | |
on: { | |
"PARENT.SET": { | |
actions: "setParentMenu" | |
}, | |
"CHILD.SET": { | |
actions: "setChildMenu" | |
}, | |
OPEN: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: "invokeOnOpen" | |
} | |
], | |
OPEN_AUTOFOCUS: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["invokeOnOpen"] | |
}, | |
{ | |
internal: true, | |
target: "open", | |
actions: ["highlightFirstItem", "invokeOnOpen"] | |
} | |
], | |
CLOSE: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnClose" | |
}, | |
{ | |
target: "closed", | |
actions: "invokeOnClose" | |
} | |
], | |
"HIGHLIGHTED.RESTORE": { | |
actions: "restoreHighlightedItem" | |
}, | |
"HIGHLIGHTED.SET": { | |
actions: "setHighlightedItem" | |
} | |
}, | |
states: { | |
idle: { | |
tags: ["closed"], | |
on: { | |
"CONTROLLED.OPEN": "open", | |
"CONTROLLED.CLOSE": "closed", | |
CONTEXT_MENU_START: { | |
target: "opening:contextmenu", | |
actions: "setAnchorPoint" | |
}, | |
CONTEXT_MENU: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["setAnchorPoint", "invokeOnOpen"] | |
}, | |
{ | |
target: "open", | |
actions: ["setAnchorPoint", "invokeOnOpen"] | |
} | |
], | |
TRIGGER_CLICK: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: "invokeOnOpen" | |
} | |
], | |
TRIGGER_FOCUS: { | |
guard: not$1("isSubmenu"), | |
target: "closed" | |
}, | |
TRIGGER_POINTERMOVE: { | |
guard: "isSubmenu", | |
target: "opening" | |
} | |
} | |
}, | |
"opening:contextmenu": { | |
tags: ["closed"], | |
after: { | |
LONG_PRESS_DELAY: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: "invokeOnOpen" | |
} | |
] | |
}, | |
on: { | |
"CONTROLLED.OPEN": "open", | |
"CONTROLLED.CLOSE": "closed", | |
CONTEXT_MENU_CANCEL: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnClose" | |
}, | |
{ | |
target: "closed", | |
actions: "invokeOnClose" | |
} | |
] | |
} | |
}, | |
opening: { | |
tags: ["closed"], | |
after: { | |
SUBMENU_OPEN_DELAY: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: "invokeOnOpen" | |
} | |
] | |
}, | |
on: { | |
"CONTROLLED.OPEN": "open", | |
"CONTROLLED.CLOSE": "closed", | |
BLUR: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnClose" | |
}, | |
{ | |
target: "closed", | |
actions: "invokeOnClose" | |
} | |
], | |
TRIGGER_POINTERLEAVE: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnClose" | |
}, | |
{ | |
target: "closed", | |
actions: "invokeOnClose" | |
} | |
] | |
} | |
}, | |
closing: { | |
tags: ["open"], | |
activities: ["trackPointerMove", "trackInteractOutside"], | |
after: { | |
SUBMENU_CLOSE_DELAY: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["invokeOnClose"] | |
}, | |
{ | |
target: "closed", | |
actions: ["focusParentMenu", "restoreParentHiglightedItem", "invokeOnClose"] | |
} | |
] | |
}, | |
on: { | |
"CONTROLLED.OPEN": "open", | |
"CONTROLLED.CLOSE": { | |
target: "closed", | |
actions: ["focusParentMenu", "restoreParentHiglightedItem"] | |
}, | |
// don't invoke on open here since the menu is still open (we're only keeping it open) | |
MENU_POINTERENTER: { | |
target: "open", | |
actions: "clearIntentPolygon" | |
}, | |
POINTER_MOVED_AWAY_FROM_SUBMENU: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnClose" | |
}, | |
{ | |
target: "closed", | |
actions: ["focusParentMenu", "restoreParentHiglightedItem"] | |
} | |
] | |
} | |
}, | |
closed: { | |
tags: ["closed"], | |
entry: ["clearHighlightedItem", "focusTrigger", "clearAnchorPoint", "resumePointer"], | |
on: { | |
"CONTROLLED.OPEN": [ | |
{ | |
guard: or$1("isOpenAutoFocusEvent", "isArrowDownEvent"), | |
target: "open", | |
actions: "highlightFirstItem" | |
}, | |
{ | |
guard: "isArrowUpEvent", | |
target: "open", | |
actions: "highlightLastItem" | |
}, | |
{ | |
target: "open" | |
} | |
], | |
CONTEXT_MENU_START: { | |
target: "opening:contextmenu", | |
actions: "setAnchorPoint" | |
}, | |
CONTEXT_MENU: [ | |
{ | |
guard: "isOpenControlled", | |
actions: ["setAnchorPoint", "invokeOnOpen"] | |
}, | |
{ | |
target: "open", | |
actions: ["setAnchorPoint", "invokeOnOpen"] | |
} | |
], | |
TRIGGER_CLICK: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: "invokeOnOpen" | |
} | |
], | |
TRIGGER_POINTERMOVE: { | |
guard: "isTriggerItem", | |
target: "opening" | |
}, | |
TRIGGER_BLUR: "idle", | |
ARROW_DOWN: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: ["highlightFirstItem", "invokeOnOpen"] | |
} | |
], | |
ARROW_UP: [ | |
{ | |
guard: "isOpenControlled", | |
actions: "invokeOnOpen" | |
}, | |
{ | |
target: "open", | |
actions: ["highlightLastItem", "invokeOnOpen"] | |
} | |
] | |
} | |
}, | |
open: { | |
tags: ["open"], | |
activities: ["trackInteractOutside", "trackPositioning", "scrollToHighlightedItem"], | |
entry: ["focusMenu", "resumePointer"], | |
on: { | |
"CONTROLLED.CLOSE": [ | |
{ | |
target: "closed", | |
guard: "isArrowLeftEvent", | |
actions: ["focusParentMenu"] | |
}, | |
{ | |
target: "closed" | |
} | |
], | |
TRIGGER_CLICK: [ | |
{ | |
guard: and$1(not$1("isTriggerItem"), "isOpenControlled"), | |
actions: "invokeOnClose" | |
}, | |
{ | |
guard: not$1("isTriggerItem"), | |
target: "closed", | |
actions: "invokeOnClose" | |
} | |
], | |
ARROW_UP: { | |
actions: ["highlightPrevItem", "focusMenu"] | |
}, | |
ARROW_DOWN: { | |
actions: ["highlightNextItem", "focusMenu"] | |
}, | |
ARROW_LEFT: [ | |
{ | |
guard: and$1("isSubmenu", "isOpenControlled"), | |
actions: "invokeOnClose" | |
}, | |
{ | |
guard: "isSubmenu", | |
target: "closed", | |
actions: ["focusParentMenu", "invokeOnClose"] | |
} | |
], | |
HOME: { | |
actions: ["highlightFirstItem", "focusMenu"] | |
}, | |
END: { | |
actions: ["highlightLastItem", "focusMenu"] | |
}, | |
ARROW_RIGHT: { | |
guard: "isTriggerItemHighlighted", | |
actions: "openSubmenu" | |
}, | |
ENTER: [ | |
{ | |
guard: "isTriggerItemHighlighted", | |
actions: "openSubmenu" | |
}, | |
// == grouped == | |
{ | |
guard: and$1("closeOnSelect", "isOpenControlled"), | |
actions: ["clickHighlightedItem", "invokeOnClose"] | |
}, | |
{ | |
guard: "closeOnSelect", | |
target: "closed", | |
actions: "clickHighlightedItem" | |
}, | |
// | |
{ | |
actions: "clickHighlightedItem" | |
} | |
], | |
ITEM_POINTERMOVE: [ | |
{ | |
guard: not$1("suspendPointer"), | |
actions: ["setHighlightedItem", "focusMenu"] | |
}, | |
{ | |
actions: "setLastHighlightedItem" | |
} | |
], | |
ITEM_POINTERLEAVE: { | |
guard: and$1(not$1("suspendPointer"), not$1("isTriggerItem")), | |
actions: "clearHighlightedItem" | |
}, | |
ITEM_CLICK: [ | |
// == grouped == | |
{ | |
guard: and$1( | |
not$1("isTriggerItemHighlighted"), | |
not$1("isHighlightedItemEditable"), | |
"closeOnSelect", | |
"isOpenControlled" | |
), | |
actions: ["invokeOnSelect", "setOptionState", "closeRootMenu", "invokeOnClose"] | |
}, | |
{ | |
guard: and$1(not$1("isTriggerItemHighlighted"), not$1("isHighlightedItemEditable"), "closeOnSelect"), | |
target: "closed", | |
actions: ["invokeOnSelect", "setOptionState", "closeRootMenu", "invokeOnClose"] | |
}, | |
// | |
{ | |
guard: and$1(not$1("isTriggerItemHighlighted"), not$1("isHighlightedItemEditable")), | |
actions: ["invokeOnSelect", "setOptionState"] | |
}, | |
{ actions: "setHighlightedItem" } | |
], | |
TRIGGER_POINTERLEAVE: { | |
target: "closing", | |
actions: "setIntentPolygon" | |
}, | |
ITEM_POINTERDOWN: { | |
actions: "setHighlightedItem" | |
}, | |
TYPEAHEAD: { | |
actions: "highlightMatchedItem" | |
}, | |
FOCUS_MENU: { | |
actions: "focusMenu" | |
}, | |
"POSITIONING.SET": { | |
actions: "reposition" | |
} | |
} | |
} | |
} | |
}, | |
{ | |
delays: { | |
LONG_PRESS_DELAY: 700, | |
SUBMENU_OPEN_DELAY: 100, | |
SUBMENU_CLOSE_DELAY: 100 | |
}, | |
guards: { | |
closeOnSelect: (ctx2, evt) => !!(evt?.closeOnSelect ?? ctx2.closeOnSelect), | |
// whether the trigger is also a menu item | |
isTriggerItem: (_ctx, evt) => dom$3.isTriggerItem(evt.target), | |
// whether the trigger item is the active item | |
isTriggerItemHighlighted: (ctx2, evt) => { | |
const target = evt.target ?? dom$3.getHighlightedItemEl(ctx2); | |
return !!target?.hasAttribute("aria-controls"); | |
}, | |
isSubmenu: (ctx2) => ctx2.isSubmenu, | |
suspendPointer: (ctx2) => ctx2.suspendPointer, | |
isHighlightedItemEditable: (ctx2) => isEditableElement(dom$3.getHighlightedItemEl(ctx2)), | |
isWithinPolygon: (ctx2, evt) => { | |
if (!ctx2.intentPolygon) return false; | |
return isPointInPolygon(ctx2.intentPolygon, evt.point); | |
}, | |
// guard assertions (for controlled mode) | |
isOpenControlled: (ctx2) => !!ctx2["open.controlled"], | |
isArrowLeftEvent: (_ctx, evt) => evt.previousEvent?.type === "ARROW_LEFT", | |
isArrowUpEvent: (_ctx, evt) => evt.previousEvent?.type === "ARROW_UP", | |
isArrowDownEvent: (_ctx, evt) => evt.previousEvent?.type === "ARROW_DOWN", | |
isOpenAutoFocusEvent: (_ctx, evt) => evt.previousEvent?.type === "OPEN_AUTOFOCUS" | |
}, | |
activities: { | |
trackPositioning(ctx2) { | |
if (ctx2.anchorPoint) return; | |
ctx2.currentPlacement = ctx2.positioning.placement; | |
const getPositionerEl = () => dom$3.getPositionerEl(ctx2); | |
return getPlacement(dom$3.getTriggerEl(ctx2), getPositionerEl, { | |
...ctx2.positioning, | |
defer: true, | |
onComplete(data) { | |
ctx2.currentPlacement = data.placement; | |
} | |
}); | |
}, | |
trackInteractOutside(ctx2, _evt, { send }) { | |
const getContentEl = () => dom$3.getContentEl(ctx2); | |
return trackDismissableElement(getContentEl, { | |
defer: true, | |
exclude: [dom$3.getTriggerEl(ctx2)], | |
onInteractOutside: ctx2.onInteractOutside, | |
onFocusOutside: ctx2.onFocusOutside, | |
onEscapeKeyDown(event) { | |
ctx2.onEscapeKeyDown?.(event); | |
if (ctx2.isSubmenu) event.preventDefault(); | |
closeRootMenu(ctx2); | |
}, | |
onPointerDownOutside(event) { | |
ctx2.restoreFocus = !event.detail.focusable; | |
ctx2.onPointerDownOutside?.(event); | |
}, | |
onDismiss() { | |
send({ type: "CLOSE", src: "interact-outside" }); | |
} | |
}); | |
}, | |
trackPointerMove(ctx2, _evt, { guards: guards2, send }) { | |
const { isWithinPolygon } = guards2; | |
ctx2.parent.state.context.suspendPointer = true; | |
const doc = dom$3.getDoc(ctx2); | |
return addDomEvent(doc, "pointermove", (e) => { | |
const point = { x: e.clientX, y: e.clientY }; | |
const isMovingToSubmenu = isWithinPolygon(ctx2, { point }); | |
if (!isMovingToSubmenu) { | |
send("POINTER_MOVED_AWAY_FROM_SUBMENU"); | |
ctx2.parent.state.context.suspendPointer = false; | |
} | |
}); | |
}, | |
scrollToHighlightedItem(ctx2, _evt, { getState }) { | |
const exec = () => { | |
const state = getState(); | |
if (state.event.type.startsWith("ITEM_POINTER")) return; | |
const itemEl = dom$3.getHighlightedItemEl(ctx2); | |
const contentEl2 = dom$3.getContentEl(ctx2); | |
scrollIntoView(itemEl, { rootEl: contentEl2, block: "nearest" }); | |
}; | |
raf(() => exec()); | |
const contentEl = () => dom$3.getContentEl(ctx2); | |
return observeAttributes(contentEl, { | |
defer: true, | |
attributes: ["aria-activedescendant"], | |
callback: exec | |
}); | |
} | |
}, | |
actions: { | |
setAnchorPoint(ctx2, evt) { | |
ctx2.anchorPoint = evt.point; | |
}, | |
clearAnchorPoint(ctx2) { | |
ctx2.anchorPoint = null; | |
}, | |
setSubmenuPlacement(ctx2) { | |
if (!ctx2.isSubmenu) return; | |
ctx2.positioning.placement = ctx2.isRtl ? "left-start" : "right-start"; | |
ctx2.positioning.gutter = 0; | |
}, | |
reposition(ctx2, evt) { | |
const getPositionerEl = () => dom$3.getPositionerEl(ctx2); | |
const getAnchorRect = ctx2.anchorPoint ? () => ({ width: 0, height: 0, ...ctx2.anchorPoint }) : void 0; | |
getPlacement(dom$3.getTriggerEl(ctx2), getPositionerEl, { | |
...ctx2.positioning, | |
getAnchorRect, | |
...evt.options ?? {}, | |
listeners: false, | |
onComplete(data) { | |
ctx2.currentPlacement = data.placement; | |
} | |
}); | |
}, | |
setOptionState(_ctx, evt) { | |
if (!evt.option) return; | |
const { checked, onCheckedChange, type } = evt.option; | |
if (type === "radio") { | |
onCheckedChange?.(true); | |
} else if (type === "checkbox") { | |
onCheckedChange?.(!checked); | |
} | |
}, | |
clickHighlightedItem(ctx2, _evt, { send }) { | |
const itemEl = dom$3.getHighlightedItemEl(ctx2); | |
if (!itemEl || itemEl.dataset.disabled) return; | |
const option = dom$3.getOptionFromItemEl(itemEl); | |
send({ | |
type: "ITEM_CLICK", | |
src: "enter", | |
target: itemEl, | |
id: option.id, | |
option, | |
closeOnSelect: ctx2.closeOnSelect | |
}); | |
}, | |
setIntentPolygon(ctx2, evt) { | |
const menu = dom$3.getContentEl(ctx2); | |
const placement = ctx2.currentPlacement; | |
if (!menu || !placement) return; | |
const rect = menu.getBoundingClientRect(); | |
const polygon = getElementPolygon(rect, placement); | |
if (!polygon) return; | |
const rightSide = getPlacementSide(placement) === "right"; | |
const bleed = rightSide ? -5 : 5; | |
ctx2.intentPolygon = [{ ...evt.point, x: evt.point.x + bleed }, ...polygon]; | |
}, | |
clearIntentPolygon(ctx2) { | |
ctx2.intentPolygon = null; | |
}, | |
resumePointer(ctx2) { | |
if (!ctx2.parent) return; | |
ctx2.parent.state.context.suspendPointer = false; | |
}, | |
setHighlightedItem(ctx2, evt) { | |
set.highlighted(ctx2, evt.id); | |
}, | |
clearHighlightedItem(ctx2) { | |
set.highlighted(ctx2, null); | |
}, | |
focusMenu(ctx2) { | |
raf(() => { | |
const contentEl = dom$3.getContentEl(ctx2); | |
const initialFocusEl = getInitialFocus({ | |
root: contentEl, | |
enabled: !contains(contentEl, dom$3.getActiveElement(ctx2)), | |
filter(node) { | |
return !node.role?.startsWith("menuitem"); | |
} | |
}); | |
initialFocusEl?.focus({ preventScroll: true }); | |
}); | |
}, | |
highlightFirstItem(ctx2) { | |
const first2 = dom$3.getFirstEl(ctx2); | |
if (!first2) return; | |
set.highlighted(ctx2, first2.id); | |
}, | |
highlightLastItem(ctx2) { | |
const last2 = dom$3.getLastEl(ctx2); | |
if (!last2) return; | |
set.highlighted(ctx2, last2.id); | |
}, | |
highlightNextItem(ctx2, evt) { | |
const next = dom$3.getNextEl(ctx2, evt.loop); | |
set.highlighted(ctx2, next?.id ?? null); | |
}, | |
highlightPrevItem(ctx2, evt) { | |
const prev = dom$3.getPrevEl(ctx2, evt.loop); | |
set.highlighted(ctx2, prev?.id ?? null); | |
}, | |
invokeOnSelect(ctx2) { | |
if (!ctx2.highlightedValue) return; | |
ctx2.onSelect?.({ value: ctx2.highlightedValue }); | |
}, | |
focusTrigger(ctx2) { | |
if (ctx2.isSubmenu || ctx2.anchorPoint || !ctx2.restoreFocus) return; | |
raf(() => dom$3.getTriggerEl(ctx2)?.focus({ preventScroll: true })); | |
}, | |
highlightMatchedItem(ctx2, evt) { | |
const node = dom$3.getElemByKey(ctx2, evt.key); | |
if (!node) return; | |
set.highlighted(ctx2, node.id); | |
}, | |
setParentMenu(ctx2, evt) { | |
ctx2.parent = ref(evt.value); | |
}, | |
setChildMenu(ctx2, evt) { | |
ctx2.children[evt.id] = ref(evt.value); | |
}, | |
closeRootMenu(ctx2) { | |
closeRootMenu(ctx2); | |
}, | |
openSubmenu(ctx2) { | |
const item = dom$3.getHighlightedItemEl(ctx2); | |
const id = item?.getAttribute("data-uid"); | |
const child = id ? ctx2.children[id] : null; | |
child?.send("OPEN_AUTOFOCUS"); | |
}, | |
focusParentMenu(ctx2) { | |
ctx2.parent?.send("FOCUS_MENU"); | |
}, | |
setLastHighlightedItem(ctx2, evt) { | |
ctx2.lastHighlightedValue = evt.id; | |
}, | |
restoreHighlightedItem(ctx2) { | |
if (!ctx2.lastHighlightedValue) return; | |
set.highlighted(ctx2, ctx2.lastHighlightedValue); | |
ctx2.lastHighlightedValue = null; | |
}, | |
restoreParentHiglightedItem(ctx2) { | |
ctx2.parent?.send("HIGHLIGHTED.RESTORE"); | |
}, | |
invokeOnOpen(ctx2) { | |
ctx2.onOpenChange?.({ open: true }); | |
}, | |
invokeOnClose(ctx2) { | |
ctx2.onOpenChange?.({ open: false }); | |
}, | |
toggleVisibility(ctx2, evt, { send }) { | |
send({ type: ctx2.open ? "CONTROLLED.OPEN" : "CONTROLLED.CLOSE", previousEvent: evt }); | |
} | |
} | |
} | |
); | |
} | |
function closeRootMenu(ctx) { | |
let parent = ctx.parent; | |
while (parent && parent.state.context.isSubmenu) { | |
parent = parent.state.context.parent; | |
} | |
parent?.send("CLOSE"); | |
} | |
var set = { | |
highlighted(ctx, value) { | |
if (isEqual(ctx.highlightedValue, value)) return; | |
ctx.highlightedValue = value; | |
ctx.onHighlightChange?.({ highlightedValue: value }); | |
} | |
}; | |
createProps()([ | |
"anchorPoint", | |
"aria-label", | |
"closeOnSelect", | |
"dir", | |
"getRootNode", | |
"highlightedValue", | |
"id", | |
"ids", | |
"loopFocus", | |
"onFocusOutside", | |
"onInteractOutside", | |
"onOpenChange", | |
"onPointerDownOutside", | |
"onEscapeKeyDown", | |
"onSelect", | |
"onHighlightChange", | |
"open", | |
"open.controlled", | |
"positioning", | |
"typeahead", | |
"composite" | |
]); | |
createProps()(["closeOnSelect", "disabled", "value", "valueText"]); | |
createProps()(["htmlFor"]); | |
createProps()(["id"]); | |
createProps()([ | |
"disabled", | |
"valueText", | |
"closeOnSelect", | |
"type", | |
"value", | |
"checked", | |
"onCheckedChange" | |
]); | |
// src/progress.anatomy.ts | |
var anatomy$2 = createAnatomy$1("progress").parts( | |
"root", | |
"label", | |
"track", | |
"range", | |
"valueText", | |
"view", | |
"circle", | |
"circleTrack", | |
"circleRange" | |
); | |
var parts$1 = anatomy$2.build(); | |
var dom$2 = createScope({ | |
getRootId: (ctx) => ctx.ids?.root ?? `progress-${ctx.id}`, | |
getTrackId: (ctx) => ctx.ids?.track ?? `progress-${ctx.id}-track`, | |
getLabelId: (ctx) => ctx.ids?.label ?? `progress-${ctx.id}-label`, | |
getCircleId: (ctx) => ctx.ids?.circle ?? `progress-${ctx.id}-circle` | |
}); | |
// src/progress.connect.ts | |
function connect$2(state, send, normalize) { | |
const percent = state.context.percent; | |
const max = state.context.max; | |
const min = state.context.min; | |
const orientation = state.context.orientation; | |
const translations = state.context.translations; | |
const indeterminate = state.context.isIndeterminate; | |
const value = state.context.value; | |
const valueAsString = translations.value({ value, max, percent, min }); | |
const progressState = getProgressState(value, max); | |
const progressbarProps = { | |
role: "progressbar", | |
"aria-label": valueAsString, | |
"data-max": max, | |
"aria-valuemin": min, | |
"aria-valuemax": max, | |
"aria-valuenow": value ?? void 0, | |
"data-orientation": orientation, | |
"data-state": progressState | |
}; | |
const circleProps = getCircleProps(state.context); | |
return { | |
value, | |
valueAsString, | |
setValue(value2) { | |
send({ type: "VALUE.SET", value: value2 }); | |
}, | |
setToMax() { | |
send({ type: "VALUE.SET", value: max }); | |
}, | |
getRootProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
...parts$1.root.attrs, | |
id: dom$2.getRootId(state.context), | |
"data-max": max, | |
"data-value": value ?? void 0, | |
"data-state": progressState, | |
"data-orientation": orientation, | |
style: { | |
"--percent": indeterminate ? void 0 : percent | |
} | |
}); | |
}, | |
getLabelProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
id: dom$2.getLabelId(state.context), | |
...parts$1.label.attrs, | |
"data-orientation": orientation | |
}); | |
}, | |
getValueTextProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
"aria-live": "polite", | |
...parts$1.valueText.attrs | |
}); | |
}, | |
getTrackProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
id: dom$2.getTrackId(state.context), | |
...parts$1.track.attrs, | |
...progressbarProps | |
}); | |
}, | |
getRangeProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
...parts$1.range.attrs, | |
"data-orientation": orientation, | |
"data-state": progressState, | |
style: { | |
[state.context.isHorizontal ? "width" : "height"]: indeterminate ? void 0 : `${percent}%` | |
} | |
}); | |
}, | |
getCircleProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
id: dom$2.getCircleId(state.context), | |
...parts$1.circle.attrs, | |
...progressbarProps, | |
...circleProps.root | |
}); | |
}, | |
getCircleTrackProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
"data-orientation": orientation, | |
...parts$1.circleTrack.attrs, | |
...circleProps.track | |
}); | |
}, | |
getCircleRangeProps() { | |
return normalize.element({ | |
dir: state.context.dir, | |
...parts$1.circleRange.attrs, | |
...circleProps.range, | |
"data-state": progressState | |
}); | |
}, | |
getViewProps(props2) { | |
return normalize.element({ | |
dir: state.context.dir, | |
...parts$1.view.attrs, | |
"data-state": props2.state, | |
hidden: props2.state !== progressState | |
}); | |
} | |
}; | |
} | |
function getProgressState(value, maxValue) { | |
return value == null ? "indeterminate" : value === maxValue ? "complete" : "loading"; | |
} | |
function getCircleProps(ctx) { | |
const circleProps = { | |
style: { | |
"--radius": "calc(var(--size) / 2 - var(--thickness) / 2)", | |
cx: "calc(var(--size) / 2)", | |
cy: "calc(var(--size) / 2)", | |
r: "var(--radius)", | |
fill: "transparent", | |
strokeWidth: "var(--thickness)" | |
} | |
}; | |
return { | |
root: { | |
viewBox: "0 0 var(--size) var(--size)", | |
style: { | |
width: "var(--size)", | |
height: "var(--size)" | |
} | |
}, | |
track: circleProps, | |
range: { | |
opacity: ctx.value === 0 ? 0 : void 0, | |
style: { | |
...circleProps.style, | |
"--percent": ctx.percent, | |
"--circumference": `calc(2 * 3.14159 * var(--radius))`, | |
"--offset": `calc(var(--circumference) * (100 - var(--percent)) / 100}))`, | |
strokeDashoffset: `calc(var(--circumference) * ((100 - var(--percent)) / 100))`, | |
strokeDasharray: ctx.isIndeterminate ? void 0 : `var(--circumference)`, | |
transformOrigin: "center", | |
transform: "rotate(-90deg)" | |
} | |
} | |
}; | |
} | |
function midValue(min, max) { | |
return min + (max - min) / 2; | |
} | |
function machine$1(userContext) { | |
const ctx = compact(userContext); | |
return createMachine( | |
{ | |
id: "progress", | |
initial: "idle", | |
context: { | |
max: ctx.max ?? 100, | |
min: ctx.min ?? 0, | |
value: midValue(ctx.min ?? 0, ctx.max ?? 100), | |
orientation: "horizontal", | |
translations: { | |
value: ({ percent }) => percent === -1 ? "loading..." : `${percent} percent`, | |
...ctx.translations | |
}, | |
...ctx | |
}, | |
created: ["validateContext"], | |
computed: { | |
isIndeterminate: (ctx2) => ctx2.value === null, | |
percent(ctx2) { | |
if (!isNumber(ctx2.value)) return -1; | |
return Math.round((ctx2.value - ctx2.min) / (ctx2.max - ctx2.min) * 100); | |
}, | |
isAtMax: (ctx2) => ctx2.value === ctx2.max, | |
isHorizontal: (ctx2) => ctx2.orientation === "horizontal", | |
isRtl: (ctx2) => ctx2.dir === "rtl" | |
}, | |
states: { | |
idle: { | |
on: { | |
"VALUE.SET": { | |
actions: ["setValue"] | |
} | |
} | |
} | |
} | |
}, | |
{ | |
actions: { | |
setValue: (ctx2, evt) => { | |
ctx2.value = evt.value === null ? null : Math.max(0, Math.min(evt.value, ctx2.max)); | |
}, | |
validateContext: (ctx2) => { | |
if (ctx2.value == null) return; | |
if (!isValidNumber(ctx2.max)) { | |
throw new Error(`[progress] The max value passed \`${ctx2.max}\` is not a valid number`); | |
} | |
if (!isValidMax(ctx2.value, ctx2.max)) { | |
throw new Error(`[progress] The value passed \`${ctx2.value}\` exceeds the max value \`${ctx2.max}\``); | |
} | |
if (!isValidMin(ctx2.value, ctx2.min)) { | |
throw new Error(`[progress] The value passed \`${ctx2.value}\` exceeds the min value \`${ctx2.min}\``); | |
} | |
} | |
} | |
} | |
); | |
} | |
function isValidNumber(max) { | |
return isNumber(max) && !isNaN(max); | |
} | |
function isValidMax(value, max) { | |
return isValidNumber(value) && value <= max; | |
} | |
function isValidMin(value, min) { | |
return isValidNumber(value) && value >= min; | |
} | |
createProps()([ | |
"dir", | |
"getRootNode", | |
"id", | |
"ids", | |
"max", | |
"min", | |
"orientation", | |
"translations", | |
"value" | |
]); | |
// src/toast-group.connect.ts | |
var anatomy$1 = createAnatomy$1("toast").parts( | |
"group", | |
"root", | |
"title", | |
"description", | |
"actionTrigger", | |
"closeTrigger" | |
); | |
var parts = anatomy$1.build(); | |
var dom$1 = createScope({ | |
getRegionId: (placement) => `toast-group:${placement}`, | |
getRegionEl: (ctx, placement) => dom$1.getById(ctx, `toast-group:${placement}`), | |
getRootId: (ctx) => `toast:${ctx.id}`, | |
getRootEl: (ctx) => dom$1.getById(ctx, dom$1.getRootId(ctx)), | |
getTitleId: (ctx) => `toast:${ctx.id}:title`, | |
getDescriptionId: (ctx) => `toast:${ctx.id}:description`, | |
getCloseTriggerId: (ctx) => `toast${ctx.id}:close` | |
}); | |
function getToastsByPlacement(toasts, placement) { | |
return toasts.filter((toast) => toast.state.context.placement === placement); | |
} | |
var defaultTimeouts = { | |
info: 5e3, | |
error: 5e3, | |
success: 2e3, | |
loading: Infinity, | |
DEFAULT: 5e3 | |
}; | |
function getToastDuration(duration, type) { | |
return duration ?? defaultTimeouts[type] ?? defaultTimeouts.DEFAULT; | |
} | |
function getGroupPlacementStyle(ctx, placement) { | |
const offset = ctx.offsets; | |
const computedOffset = typeof offset === "string" ? { left: offset, right: offset, bottom: offset, top: offset } : offset; | |
const rtl = ctx.dir === "rtl"; | |
const computedPlacement = placement.replace("-start", rtl ? "-right" : "-left").replace("-end", rtl ? "-left" : "-right"); | |
const isRighty = computedPlacement.includes("right"); | |
const isLefty = computedPlacement.includes("left"); | |
const styles = { | |
position: "fixed", | |
pointerEvents: ctx.count > 0 ? void 0 : "none", | |
display: "flex", | |
flexDirection: "column", | |
"--gap": `${ctx.gap}px`, | |
"--first-height": `${ctx.heights[0]?.height || 0}px`, | |
zIndex: MAX_Z_INDEX | |
}; | |
let alignItems = "center"; | |
if (isRighty) alignItems = "flex-end"; | |
if (isLefty) alignItems = "flex-start"; | |
styles.alignItems = alignItems; | |
if (computedPlacement.includes("top")) { | |
const offset2 = computedOffset.top; | |
styles.top = `max(env(safe-area-inset-top, 0px), ${offset2})`; | |
} | |
if (computedPlacement.includes("bottom")) { | |
const offset2 = computedOffset.bottom; | |
styles.bottom = `max(env(safe-area-inset-bottom, 0px), ${offset2})`; | |
} | |
if (!computedPlacement.includes("left")) { | |
const offset2 = computedOffset.right; | |
styles.insetInlineEnd = `calc(env(safe-area-inset-right, 0px) + ${offset2})`; | |
} | |
if (!computedPlacement.includes("right")) { | |
const offset2 = computedOffset.left; | |
styles.insetInlineStart = `calc(env(safe-area-inset-left, 0px) + ${offset2})`; | |
} | |
return styles; | |
} | |
function getPlacementStyle(ctx, visible) { | |
const [side] = ctx.placement.split("-"); | |
const sibling = !ctx.frontmost; | |
const overlap = !ctx.stacked; | |
const styles = { | |
position: "absolute", | |
pointerEvents: "auto", | |
"--opacity": "0", | |
"--remove-delay": `${ctx.removeDelay}ms`, | |
"--duration": `${ctx.type === "loading" ? Number.MAX_SAFE_INTEGER : ctx.duration}ms`, | |
"--initial-height": `${ctx.height}px`, | |
"--offset": `${ctx.offset}px`, | |
"--index": ctx.index, | |
"--z-index": ctx.zIndex, | |
"--lift-amount": "calc(var(--lift) * var(--gap))", | |
"--y": "100%", | |
"--x": "0" | |
}; | |
const assign = (overrides) => Object.assign(styles, overrides); | |
if (side === "top") { | |
assign({ | |
top: "0", | |
"--sign": "-1", | |
"--y": "-100%", | |
"--lift": "1" | |
}); | |
} else if (side === "bottom") { | |
assign({ | |
bottom: "0", | |
"--sign": "1", | |
"--y": "100%", | |
"--lift": "-1" | |
}); | |
} | |
if (ctx.mounted) { | |
assign({ | |
"--y": "0", | |
"--opacity": "1" | |
}); | |
if (ctx.stacked) { | |
assign({ | |
"--y": "calc(var(--lift) * var(--offset))", | |
"--height": "var(--initial-height)" | |
}); | |
} | |
} | |
if (!visible) { | |
assign({ | |
"--opacity": "0", | |
pointerEvents: "none" | |
}); | |
} | |
if (sibling && overlap) { | |
assign({ | |
"--base-scale": "var(--index) * 0.05 + 1", | |
"--y": "calc(var(--lift-amount) * var(--index))", | |
"--scale": "calc(-1 * var(--base-scale))", | |
"--height": "var(--first-height)" | |
}); | |
if (!visible) { | |
assign({ | |
"--y": "calc(var(--sign) * 40%)" | |
}); | |
} | |
} | |
if (sibling && ctx.stacked && !visible) { | |
assign({ | |
"--y": "calc(var(--lift) * var(--offset) + var(--lift) * -100%)" | |
}); | |
} | |
if (ctx.frontmost && !visible) { | |
assign({ | |
"--y": "calc(var(--lift) * -100%)" | |
}); | |
} | |
return styles; | |
} | |
function getGhostBeforeStyle(ctx, visible) { | |
const styles = { | |
position: "absolute", | |
inset: "0", | |
scale: "1 2", | |
pointerEvents: visible ? "none" : "auto" | |
}; | |
const assign = (overrides) => Object.assign(styles, overrides); | |
if (ctx.frontmost && !visible) { | |
assign({ | |
height: "calc(var(--initial-height) + 80%)" | |
}); | |
} | |
return styles; | |
} | |
function getGhostAfterStyle(_ctx, _visible) { | |
return { | |
position: "absolute", | |
left: "0", | |
height: "calc(var(--gap) + 2px)", | |
bottom: "100%", | |
width: "100%" | |
}; | |
} | |
// src/toast-group.connect.ts | |
function groupConnect(serviceOrState, send, normalize) { | |
function getState() { | |
const result = isMachine(serviceOrState) ? serviceOrState.getState() : serviceOrState; | |
return result; | |
} | |
function getToastsByPlacementImpl(placement) { | |
return getToastsByPlacement(getState().context.toasts, placement); | |
} | |
function isVisible(id) { | |
const toasts = getState().context.toasts; | |
if (!toasts.length) return false; | |
return !!toasts.find((toast) => toast.id == id); | |
} | |
function create(options) { | |
const uid = `toast:${uuid()}`; | |
const id = options.id ? options.id : uid; | |
if (isVisible(id)) return id; | |
send({ type: "ADD_TOAST", toast: { ...options, id } }); | |
return id; | |
} | |
function update(id, options) { | |
if (!isVisible(id)) return id; | |
send({ type: "UPDATE_TOAST", id, toast: options }); | |
return id; | |
} | |
function upsert(options) { | |
const { id } = options; | |
const visible = id ? isVisible(id) : false; | |
if (visible && id != null) { | |
return update(id, options); | |
} else { | |
return create(options); | |
} | |
} | |
function dismiss(id) { | |
if (id == null) { | |
send("DISMISS_ALL"); | |
} else if (isVisible(id)) { | |
send({ type: "DISMISS_TOAST", id }); | |
} | |
} | |
return { | |
getCount() { | |
return getState().context.count; | |
}, | |
getPlacements() { | |
const toasts = getState().context.toasts; | |
const placements = toasts.map((toast) => toast.state.context.placement); | |
return Array.from(new Set(placements)); | |
}, | |
getToastsByPlacement: getToastsByPlacementImpl, | |
isVisible, | |
create, | |
update, | |
upsert, | |
dismiss, | |
remove(id) { | |
if (id == null) { | |
send("REMOVE_ALL"); | |
} else if (isVisible(id)) { | |
send({ type: "REMOVE_TOAST", id }); | |
} | |
}, | |
dismissByPlacement(placement) { | |
const toasts = getToastsByPlacementImpl(placement); | |
toasts.forEach((toast) => dismiss(toast.id)); | |
}, | |
loading(options) { | |
return upsert({ ...options, type: "loading" }); | |
}, | |
success(options) { | |
return upsert({ ...options, type: "success" }); | |
}, | |
error(options) { | |
return upsert({ ...options, type: "error" }); | |
}, | |
promise(promise, options, shared = {}) { | |
const id = upsert({ ...shared, ...options.loading, type: "loading" }); | |
runIfFn(promise).then((response) => { | |
const successOptions = runIfFn(options.success, response); | |
upsert({ ...shared, ...successOptions, id, type: "success" }); | |
}).catch((error) => { | |
const errorOptions = runIfFn(options.error, error); | |
upsert({ ...shared, ...errorOptions, id, type: "error" }); | |
}).finally(() => { | |
options.finally?.(); | |
}); | |
return id; | |
}, | |
pause(id) { | |
if (id == null) { | |
send("PAUSE_ALL"); | |
} else if (isVisible(id)) { | |
send({ type: "PAUSE_TOAST", id }); | |
} | |
}, | |
resume(id) { | |
if (id == null) { | |
send("RESUME_ALL"); | |
} else if (isVisible(id)) { | |
send({ type: "RESUME_TOAST", id }); | |
} | |
}, | |
getGroupProps(options) { | |
const { placement, label = "Notifications" } = options; | |
const state = getState(); | |
const hotkeyLabel = state.context.hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, ""); | |
const [side, align = "center"] = placement.split("-"); | |
return normalize.element({ | |
...parts.group.attrs, | |
dir: state.context.dir, | |
tabIndex: -1, | |
"aria-label": `${placement} ${label} ${hotkeyLabel}`, | |
id: dom$1.getRegionId(placement), | |
"data-placement": placement, | |
"data-side": side, | |
"data-align": align, | |
"aria-live": "polite", | |
role: "region", | |
style: getGroupPlacementStyle(state.context, placement), | |
onMouseMove() { | |
send({ type: "REGION.POINTER_ENTER", placement }); | |
}, | |
onMouseLeave() { | |
send({ type: "REGION.POINTER_LEAVE", placement }); | |
}, | |
onFocus(event) { | |
send({ type: "REGION.FOCUS", target: event.relatedTarget }); | |
}, | |
onBlur(event) { | |
if (state.context.isFocusWithin && !contains(event.currentTarget, event.relatedTarget)) { | |
send({ type: "REGION.BLUR" }); | |
} | |
} | |
}); | |
}, | |
subscribe(fn) { | |
const state = getState(); | |
return subscribe(state.context.toasts, () => { | |
const toasts = getToastsByPlacementImpl(state.context.placement); | |
const contexts = toasts.map((toast) => toast.getState().context); | |
fn(contexts); | |
}); | |
} | |
}; | |
} | |
var { not, and, or } = guards; | |
function createToastMachine(options) { | |
const { type = "info", duration, id = "1", placement = "bottom", removeDelay = 200, ...restProps } = options; | |
const ctx = compact(restProps); | |
const computedDuration = getToastDuration(duration, type); | |
return createMachine( | |
{ | |
id, | |
context: { | |
id, | |
type, | |
remaining: computedDuration, | |
duration: computedDuration, | |
removeDelay, | |
createdAt: Date.now(), | |
placement, | |
...ctx, | |
height: 0, | |
offset: 0, | |
frontmost: false, | |
mounted: false, | |
index: -1, | |
zIndex: 0 | |
}, | |
initial: type === "loading" ? "visible:persist" : "visible", | |
on: { | |
UPDATE: [ | |
{ | |
guard: and("hasTypeChanged", "isChangingToLoading"), | |
target: "visible:persist", | |
actions: ["setContext"] | |
}, | |
{ | |
guard: or("hasDurationChanged", "hasTypeChanged"), | |
target: "visible:updating", | |
actions: ["setContext"] | |
}, | |
{ | |
actions: ["setContext"] | |
} | |
], | |
MEASURE: { | |
actions: ["measureHeight"] | |
} | |
}, | |
entry: ["invokeOnVisible"], | |
activities: ["trackHeight"], | |
states: { | |
"visible:updating": { | |
tags: ["visible", "updating"], | |
after: { | |
0: "visible" | |
} | |
}, | |
"visible:persist": { | |
tags: ["visible", "paused"], | |
on: { | |
RESUME: { | |
guard: not("isLoadingType"), | |
target: "visible", | |
actions: ["setCreatedAt"] | |
}, | |
DISMISS: "dismissing" | |
} | |
}, | |
visible: { | |
tags: ["visible"], | |
after: { | |
VISIBLE_DURATION: "dismissing" | |
}, | |
on: { | |
DISMISS: "dismissing", | |
PAUSE: { | |
target: "visible:persist", | |
actions: "setRemainingDuration" | |
} | |
} | |
}, | |
dismissing: { | |
entry: "invokeOnDismiss", | |
after: { | |
REMOVE_DELAY: { | |
target: "unmounted", | |
actions: "notifyParentToRemove" | |
} | |
} | |
}, | |
unmounted: { | |
entry: "invokeOnUnmount", | |
type: "final" | |
} | |
} | |
}, | |
{ | |
activities: { | |
trackHeight(ctx2, _evt, { self }) { | |
let cleanup; | |
raf(() => { | |
const rootEl = dom$1.getRootEl(ctx2); | |
if (!rootEl) return; | |
ctx2.mounted = true; | |
const ghosts = queryAll(rootEl, "[data-ghost]"); | |
warn( | |
ghosts.length !== 2, | |
"[toast] No ghost element found in toast. Render the `ghostBefore` and `ghostAfter` elements" | |
); | |
const syncHeight = () => { | |
const originalHeight = rootEl.style.height; | |
rootEl.style.height = "auto"; | |
const newHeight = rootEl.getBoundingClientRect().height; | |
rootEl.style.height = originalHeight; | |
ctx2.height = newHeight; | |
self.sendParent({ type: "UPDATE_HEIGHT", id: self.id, height: newHeight, placement: ctx2.placement }); | |
}; | |
syncHeight(); | |
const win = dom$1.getWin(ctx2); | |
const observer = new win.MutationObserver(syncHeight); | |
observer.observe(rootEl, { childList: true, subtree: true, characterData: true }); | |
cleanup = () => observer.disconnect(); | |
}); | |
return () => cleanup?.(); | |
} | |
}, | |
guards: { | |
isChangingToLoading: (_, evt) => evt.toast?.type === "loading", | |
isLoadingType: (ctx2) => ctx2.type === "loading", | |
hasTypeChanged: (ctx2, evt) => evt.toast?.type != null && evt.toast.type !== ctx2.type, | |
hasDurationChanged: (ctx2, evt) => evt.toast?.duration != null && evt.toast.duration !== ctx2.duration | |
}, | |
delays: { | |
VISIBLE_DURATION: (ctx2) => ctx2.remaining, | |
REMOVE_DELAY: (ctx2) => ctx2.removeDelay | |
}, | |
actions: { | |
measureHeight(ctx2, _evt, { self }) { | |
raf(() => { | |
const rootEl = dom$1.getRootEl(ctx2); | |
if (!rootEl) return; | |
ctx2.mounted = true; | |
const originalHeight = rootEl.style.height; | |
rootEl.style.height = "auto"; | |
const newHeight = rootEl.getBoundingClientRect().height; | |
rootEl.style.height = originalHeight; | |
ctx2.height = newHeight; | |
self.sendParent({ type: "UPDATE_HEIGHT", id: self.id, height: newHeight, placement: ctx2.placement }); | |
}); | |
}, | |
setRemainingDuration(ctx2) { | |
ctx2.remaining -= Date.now() - ctx2.createdAt; | |
}, | |
setCreatedAt(ctx2) { | |
ctx2.createdAt = Date.now(); | |
}, | |
notifyParentToRemove(_ctx, _evt, { self }) { | |
self.sendParent({ type: "REMOVE_TOAST", id: self.id }); | |
}, | |
invokeOnDismiss(ctx2) { | |
ctx2.onStatusChange?.({ status: "dismissing" }); | |
}, | |
invokeOnUnmount(ctx2) { | |
ctx2.onStatusChange?.({ status: "unmounted" }); | |
}, | |
invokeOnVisible(ctx2) { | |
ctx2.onStatusChange?.({ status: "visible" }); | |
}, | |
setContext(ctx2, evt) { | |
const duration2 = evt.toast?.duration; | |
const type2 = evt.toast?.type ?? ctx2.type; | |
const computedDuration2 = getToastDuration(duration2, type2); | |
Object.assign(ctx2, { | |
...evt.toast, | |
duration: computedDuration2, | |
remaining: computedDuration2 | |
}); | |
} | |
} | |
} | |
); | |
} | |
// src/toast-group.machine.ts | |
function groupMachine(userContext) { | |
const ctx = compact(userContext); | |
return createMachine( | |
{ | |
id: "toaster", | |
initial: ctx.overlap ? "overlap" : "stack", | |
context: { | |
dir: "ltr", | |
max: Number.MAX_SAFE_INTEGER, | |
gap: 16, | |
pauseOnPageIdle: false, | |
hotkey: ["altKey", "KeyT"], | |
offsets: "1rem", | |
placement: "bottom", | |
removeDelay: 200, | |
...ctx, | |
toasts: [], | |
lastFocusedEl: null, | |
isFocusWithin: false, | |
heights: [] | |
}, | |
computed: { | |
count: (ctx2) => ctx2.toasts.length | |
}, | |
activities: ["trackDocumentVisibility", "trackHotKeyPress"], | |
watch: { | |
toasts: ["collapsedIfEmpty", "setDismissableBranch"] | |
}, | |
exit: ["removeToasts", "clearDismissableBranch", "clearLastFocusedEl"], | |
on: { | |
PAUSE_TOAST: { | |
actions: ["pauseToast"] | |
}, | |
PAUSE_ALL: { | |
actions: ["pauseToasts"] | |
}, | |
RESUME_TOAST: { | |
actions: ["resumeToast"] | |
}, | |
RESUME_ALL: { | |
actions: ["resumeToasts"] | |
}, | |
ADD_TOAST: { | |
guard: "isWithinRange", | |
actions: ["createToast", "syncToastIndex"] | |
}, | |
UPDATE_TOAST: { | |
actions: ["updateToast"] | |
}, | |
DISMISS_TOAST: { | |
actions: ["dismissToast"] | |
}, | |
DISMISS_ALL: { | |
actions: ["dismissToasts"] | |
}, | |
REMOVE_TOAST: { | |
actions: ["removeToast", "syncToastIndex", "syncToastOffset"] | |
}, | |
REMOVE_ALL: { | |
actions: ["removeToasts"] | |
}, | |
UPDATE_HEIGHT: { | |
actions: ["syncHeights", "syncToastOffset"] | |
}, | |
"DOC.HOTKEY": { | |
actions: ["focusRegionEl"] | |
}, | |
"REGION.BLUR": [ | |
{ | |
guard: "isOverlapping", | |
target: "overlap", | |
actions: ["resumeToasts", "restoreLastFocusedEl"] | |
}, | |
{ | |
actions: ["resumeToasts", "restoreLastFocusedEl"] | |
} | |
] | |
}, | |
states: { | |
stack: { | |
entry: ["expandToasts"], | |
on: { | |
"REGION.POINTER_LEAVE": [ | |
{ | |
guard: "isOverlapping", | |
target: "overlap", | |
actions: ["resumeToasts"] | |
}, | |
{ | |
actions: ["resumeToasts"] | |
} | |
], | |
"REGION.OVERLAP": { | |
target: "overlap" | |
}, | |
"REGION.FOCUS": { | |
actions: ["setLastFocusedEl", "pauseToasts"] | |
}, | |
"REGION.POINTER_ENTER": { | |
actions: ["pauseToasts"] | |
} | |
} | |
}, | |
overlap: { | |
entry: ["collapseToasts"], | |
on: { | |
"REGION.STACK": { | |
target: "stack" | |
}, | |
"REGION.POINTER_ENTER": { | |
target: "stack", | |
actions: ["pauseToasts"] | |
}, | |
"REGION.FOCUS": { | |
target: "stack", | |
actions: ["setLastFocusedEl", "pauseToasts"] | |
} | |
} | |
} | |
} | |
}, | |
{ | |
guards: { | |
isWithinRange: (ctx2) => ctx2.toasts.length < ctx2.max, | |
isOverlapping: (ctx2) => !!ctx2.overlap | |
}, | |
activities: { | |
trackHotKeyPress(ctx2, _evt, { send }) { | |
const handleKeyDown = (event) => { | |
const isHotkeyPressed = ctx2.hotkey.every((key) => event[key] || event.code === key); | |
if (!isHotkeyPressed) return; | |
send({ type: "DOC.HOTKEY" }); | |
}; | |
return addDomEvent(document, "keydown", handleKeyDown, { capture: true }); | |
}, | |
trackDocumentVisibility(ctx2, _evt, { send }) { | |
if (!ctx2.pauseOnPageIdle) return; | |
const doc = dom$1.getDoc(ctx2); | |
return addDomEvent(doc, "visibilitychange", () => { | |
send(doc.visibilityState === "hidden" ? "PAUSE_ALL" : "RESUME_ALL"); | |
}); | |
} | |
}, | |
actions: { | |
setDismissableBranch(ctx2) { | |
const currentToasts = getToastsByPlacement(ctx2.toasts, ctx2.placement); | |
const hasToasts = currentToasts.length > 0; | |
if (!hasToasts) { | |
ctx2._cleanup?.(); | |
return; | |
} | |
if (hasToasts && ctx2._cleanup) { | |
return; | |
} | |
const groupEl = () => dom$1.getRegionEl(ctx2, ctx2.placement); | |
ctx2._cleanup = trackDismissableBranch(groupEl, { defer: true }); | |
}, | |
clearDismissableBranch(ctx2) { | |
ctx2._cleanup?.(); | |
}, | |
focusRegionEl(ctx2) { | |
queueMicrotask(() => { | |
dom$1.getRegionEl(ctx2, ctx2.placement)?.focus(); | |
}); | |
}, | |
expandToasts(ctx2) { | |
each(ctx2, (toast) => { | |
toast.state.context.stacked = true; | |
}); | |
}, | |
collapseToasts(ctx2) { | |
each(ctx2, (toast) => { | |
toast.state.context.stacked = false; | |
}); | |
}, | |
collapsedIfEmpty(ctx2, _evt, { send }) { | |
if (!ctx2.overlap || ctx2.toasts.length > 1) return; | |
send("REGION.OVERLAP"); | |
}, | |
pauseToast(_ctx, evt, { self }) { | |
self.sendChild("PAUSE", evt.id); | |
}, | |
pauseToasts(ctx2) { | |
ctx2.toasts.forEach((toast) => toast.send("PAUSE")); | |
}, | |
resumeToast(_ctx, evt, { self }) { | |
self.sendChild("RESUME", evt.id); | |
}, | |
resumeToasts(ctx2) { | |
ctx2.toasts.forEach((toast) => toast.send("RESUME")); | |
}, | |
measureToasts(ctx2) { | |
ctx2.toasts.forEach((toast) => toast.send("MEASURE")); | |
}, | |
createToast(ctx2, evt, { self, getState }) { | |
const options = { | |
placement: ctx2.placement, | |
duration: ctx2.duration, | |
removeDelay: ctx2.removeDelay, | |
...evt.toast, | |
dir: ctx2.dir, | |
getRootNode: ctx2.getRootNode, | |
stacked: getState().matches("stack") | |
}; | |
const toast = createToastMachine(options); | |
const actor = self.spawn(toast); | |
ctx2.toasts = [actor, ...ctx2.toasts]; | |
}, | |
updateToast(_ctx, evt, { self }) { | |
self.sendChild({ type: "UPDATE", toast: evt.toast }, evt.id); | |
}, | |
dismissToast(_ctx, evt, { self }) { | |
self.sendChild("DISMISS", evt.id); | |
}, | |
dismissToasts(ctx2) { | |
ctx2.toasts.forEach((toast) => toast.send("DISMISS")); | |
}, | |
removeToast(ctx2, evt, { self }) { | |
self.stopChild(evt.id); | |
ctx2.toasts = ctx2.toasts.filter((toast) => toast.id !== evt.id); | |
ctx2.heights = ctx2.heights.filter((height) => height.id !== evt.id); | |
}, | |
removeToasts(ctx2, _evt, { self }) { | |
ctx2.toasts.forEach((toast) => self.stopChild(toast.id)); | |
ctx2.toasts = []; | |
ctx2.heights = []; | |
}, | |
syncHeights(ctx2, evt) { | |
const existing = ctx2.heights.find((height) => height.id === evt.id); | |
if (existing) { | |
existing.height = evt.height; | |
existing.placement = evt.placement; | |
} else { | |
const newHeight = { id: evt.id, height: evt.height, placement: evt.placement }; | |
ctx2.heights = [newHeight, ...ctx2.heights]; | |
} | |
}, | |
syncToastIndex(ctx2) { | |
each(ctx2, (toast, index, toasts) => { | |
toast.state.context.index = index; | |
toast.state.context.frontmost = index === 0; | |
toast.state.context.zIndex = toasts.length - index; | |
}); | |
}, | |
syncToastOffset(ctx2, evt) { | |
const placement = evt.placement ?? ctx2.placement; | |
each({ ...ctx2, placement }, (toast) => { | |
const heightIndex = Math.max( | |
ctx2.heights.findIndex((height) => height.id === toast.id), | |
0 | |
); | |
const toastsHeightBefore = ctx2.heights.reduce((prev, curr, reducerIndex) => { | |
if (reducerIndex >= heightIndex) return prev; | |
return prev + curr.height; | |
}, 0); | |
toast.state.context.offset = heightIndex * ctx2.gap + toastsHeightBefore; | |
}); | |
}, | |
setLastFocusedEl(ctx2, evt) { | |
if (ctx2.isFocusWithin || !evt.target) return; | |
ctx2.isFocusWithin = true; | |
ctx2.lastFocusedEl = ref(evt.target); | |
}, | |
restoreLastFocusedEl(ctx2) { | |
ctx2.isFocusWithin = false; | |
if (!ctx2.lastFocusedEl) return; | |
ctx2.lastFocusedEl.focus({ preventScroll: true }); | |
ctx2.lastFocusedEl = null; | |
}, | |
clearLastFocusedEl(ctx2) { | |
if (!ctx2.lastFocusedEl) return; | |
ctx2.lastFocusedEl.focus({ preventScroll: true }); | |
ctx2.lastFocusedEl = null; | |
ctx2.isFocusWithin = false; | |
} | |
} | |
} | |
); | |
} | |
function each(ctx, fn) { | |
const currentToasts = getToastsByPlacement(ctx.toasts, ctx.placement); | |
currentToasts.forEach(fn); | |
} | |
function connect$1(state, send, normalize) { | |
const visible = state.hasTag("visible"); | |
const paused = state.hasTag("paused"); | |
const placement = state.context.placement; | |
const type = state.context.type; | |
const [side, align = "center"] = placement.split("-"); | |
return { | |
type, | |
title: state.context.title, | |
description: state.context.description, | |
placement, | |
visible, | |
paused, | |
pause() { | |
send("PAUSE"); | |
}, | |
resume() { | |
send("RESUME"); | |
}, | |
dismiss() { | |
send("DISMISS"); | |
}, | |
getRootProps() { | |
return normalize.element({ | |
...parts.root.attrs, | |
dir: state.context.dir, | |
id: dom$1.getRootId(state.context), | |
"data-state": visible ? "open" : "closed", | |
"data-type": type, | |
"data-placement": placement, | |
"data-align": align, | |
"data-side": side, | |
"data-mounted": dataAttr(state.context.mounted), | |
"data-paused": dataAttr(paused), | |
"data-first": dataAttr(state.context.frontmost), | |
"data-sibling": dataAttr(!state.context.frontmost), | |
"data-stack": dataAttr(state.context.stacked), | |
"data-overlap": dataAttr(!state.context.stacked), | |
role: "status", | |
"aria-atomic": "true", | |
tabIndex: 0, | |
style: getPlacementStyle(state.context, visible), | |
onKeyDown(event) { | |
if (event.defaultPrevented) return; | |
if (event.key == "Escape") { | |
send("DISMISS"); | |
event.preventDefault(); | |
} | |
} | |
}); | |
}, | |
/* Leave a ghost div to avoid setting hover to false when transitioning out */ | |
getGhostBeforeProps() { | |
return normalize.element({ | |
"data-ghost": "before", | |
style: getGhostBeforeStyle(state.context, visible) | |
}); | |
}, | |
/* Needed to avoid setting hover to false when in between toasts */ | |
getGhostAfterProps() { | |
return normalize.element({ | |
"data-ghost": "after", | |
style: getGhostAfterStyle(state.context) | |
}); | |
}, | |
getTitleProps() { | |
return normalize.element({ | |
...parts.title.attrs, | |
id: dom$1.getTitleId(state.context) | |
}); | |
}, | |
getDescriptionProps() { | |
return normalize.element({ | |
...parts.description.attrs, | |
id: dom$1.getDescriptionId(state.context) | |
}); | |
}, | |
getActionTriggerProps() { | |
return normalize.button({ | |
...parts.actionTrigger.attrs, | |
type: "button", | |
onClick(event) { | |
if (event.defaultPrevented) return; | |
send("DISMISS"); | |
} | |
}); | |
}, | |
getCloseTriggerProps() { | |
return normalize.button({ | |
id: dom$1.getCloseTriggerId(state.context), | |
...parts.closeTrigger.attrs, | |
type: "button", | |
"aria-label": "Dismiss notification", | |
onClick(event) { | |
if (event.defaultPrevented) return; | |
send("DISMISS"); | |
} | |
}); | |
} | |
}; | |
} | |
// src/index.ts | |
var group = { | |
connect: groupConnect, | |
machine: groupMachine | |
}; | |
// src/presence.connect.ts | |
function connect(state, send, _normalize) { | |
const present = state.matches("mounted", "unmountSuspended"); | |
return { | |
skip: !state.context.initial && present, | |
present, | |
setNode(node) { | |
if (!node) return; | |
send({ type: "NODE.SET", node }); | |
}, | |
unmount() { | |
send({ type: "UNMOUNT" }); | |
} | |
}; | |
} | |
function getAnimationName(styles) { | |
return styles?.animationName || "none"; | |
} | |
function machine(ctx) { | |
return createMachine( | |
{ | |
initial: ctx.present ? "mounted" : "unmounted", | |
context: { | |
node: null, | |
styles: null, | |
unmountAnimationName: null, | |
prevAnimationName: null, | |
present: false, | |
initial: false, | |
...ctx | |
}, | |
exit: ["clearInitial"], | |
watch: { | |
present: ["setInitial", "syncPresence"] | |
}, | |
on: { | |
"NODE.SET": { | |
actions: ["setNode", "setStyles"] | |
} | |
}, | |
states: { | |
mounted: { | |
on: { | |
UNMOUNT: { | |
target: "unmounted", | |
actions: ["invokeOnExitComplete"] | |
}, | |
"UNMOUNT.SUSPEND": "unmountSuspended" | |
} | |
}, | |
unmountSuspended: { | |
activities: ["trackAnimationEvents"], | |
on: { | |
MOUNT: { | |
target: "mounted", | |
actions: ["setPrevAnimationName"] | |
}, | |
"ANIMATION.END": { | |
target: "unmounted", | |
actions: ["invokeOnExitComplete"] | |
}, | |
UNMOUNT: { | |
target: "unmounted", | |
actions: ["invokeOnExitComplete"] | |
} | |
} | |
}, | |
unmounted: { | |
entry: ["clearPrevAnimationName"], | |
on: { | |
MOUNT: { | |
target: "mounted", | |
actions: ["setPrevAnimationName"] | |
} | |
} | |
} | |
} | |
}, | |
{ | |
actions: { | |
setInitial(ctx2) { | |
ctx2.initial = true; | |
}, | |
clearInitial(ctx2) { | |
ctx2.initial = false; | |
}, | |
invokeOnExitComplete(ctx2) { | |
ctx2.onExitComplete?.(); | |
}, | |
setNode(ctx2, evt) { | |
ctx2.node = ref(evt.node); | |
}, | |
setStyles(ctx2, evt) { | |
const win = evt.node.ownerDocument.defaultView || window; | |
ctx2.styles = ref(win.getComputedStyle(evt.node)); | |
}, | |
syncPresence(ctx2, _evt, { send }) { | |
if (ctx2.present) { | |
send({ type: "MOUNT", src: "presence.changed" }); | |
return; | |
} | |
const animationName = getAnimationName(ctx2.styles); | |
const exec = ctx2.immediate ? queueMicrotask : requestAnimationFrame; | |
exec(() => { | |
ctx2.unmountAnimationName = animationName; | |
if (animationName === "none" || animationName === ctx2.prevAnimationName || ctx2.styles?.display === "none") { | |
send({ type: "UNMOUNT", src: "presence.changed" }); | |
} else { | |
send({ type: "UNMOUNT.SUSPEND" }); | |
} | |
}); | |
}, | |
setPrevAnimationName(ctx2) { | |
const exec = ctx2.immediate ? queueMicrotask : requestAnimationFrame; | |
exec(() => { | |
ctx2.prevAnimationName = getAnimationName(ctx2.styles); | |
}); | |
}, | |
clearPrevAnimationName(ctx2) { | |
ctx2.prevAnimationName = null; | |
} | |
}, | |
activities: { | |
trackAnimationEvents(ctx2, _evt, { send }) { | |
const node = ctx2.node; | |
if (!node) return; | |
const onStart = (event) => { | |
if (event.target === node) { | |
ctx2.prevAnimationName = getAnimationName(ctx2.styles); | |
} | |
}; | |
const onEnd = (event) => { | |
const animationName = getAnimationName(ctx2.styles); | |
if (event.target === node && animationName === ctx2.unmountAnimationName) { | |
send({ type: "UNMOUNT", src: "animationend" }); | |
} | |
}; | |
node.addEventListener("animationstart", onStart); | |
node.addEventListener("animationcancel", onEnd); | |
node.addEventListener("animationend", onEnd); | |
return () => { | |
node.removeEventListener("animationstart", onStart); | |
node.removeEventListener("animationcancel", onEnd); | |
node.removeEventListener("animationend", onEnd); | |
}; | |
} | |
} | |
} | |
); | |
} | |
createProps()(["onExitComplete", "present", "immediate"]); | |
function getErrorMessage(hook, provider) { | |
return `${hook} returned \`undefined\`. Seems you forgot to wrap component within ${provider}`; | |
} | |
function createContext(options = {}) { | |
const { | |
strict = true, | |
hookName = 'useContext', | |
providerName = 'Provider', | |
errorMessage, | |
defaultValue | |
} = options; | |
const Context = createContext$1(defaultValue); | |
function useContext$1() { | |
const context = useContext(Context); | |
if (!context && strict) { | |
const error = new Error(errorMessage ?? getErrorMessage(hookName, providerName)); | |
error.name = 'ContextError'; | |
Error.captureStackTrace?.(error, useContext$1); | |
throw error; | |
} | |
return context; | |
} | |
return [Context.Provider, useContext$1, Context]; | |
} | |
const createSplitProps = () => (props, keys) => splitProps(props, keys); | |
const [RenderStrategyProvider, useRenderStrategyContext] = createContext({ | |
hookName: 'useRenderStrategyContext', | |
providerName: '<RenderStrategyProvider />' | |
}); | |
const splitRenderStrategyProps = props => createSplitProps()(props, ['lazyMount', 'unmountOnExit']); | |
const withAsProp = Component => { | |
const ArkComponent = props => { | |
const [localProps, parentProps] = splitProps(props, ['asChild']); | |
if (localProps.asChild) { | |
// @ts-expect-error | |
const propsFn = userProps => { | |
const [, restProps] = splitProps(parentProps, ['ref']); | |
return mergeProps(restProps, userProps); | |
}; | |
return localProps.asChild(propsFn); | |
} | |
// @ts-expect-error | |
return createComponent(Dynamic, mergeProps$2({ | |
component: Component | |
}, parentProps)); | |
}; | |
return ArkComponent; | |
}; | |
function jsxFactory() { | |
const cache = new Map(); | |
return new Proxy(withAsProp, { | |
apply(_target, _thisArg, argArray) { | |
return withAsProp(argArray[0]); | |
}, | |
get(_, element) { | |
const asElement = element; | |
if (!cache.has(asElement)) { | |
cache.set(asElement, withAsProp(asElement)); | |
} | |
return cache.get(asElement); | |
} | |
}); | |
} | |
const ark = jsxFactory(); | |
const isFunction$1 = value => typeof value === 'function'; | |
const runIfFn$1 = (valueOrFn, ...args) => isFunction$1(valueOrFn) ? valueOrFn(...args) : valueOrFn; | |
const [EnvironmentContextProvider, useEnvironmentContext] = createContext({ | |
hookName: 'useEnvironmentContext', | |
providerName: '<EnvironmentProvider />', | |
strict: false, | |
defaultValue: () => ({ | |
getRootNode: () => document, | |
getDocument: () => document, | |
getWindow: () => window | |
}) | |
}); | |
var _tmpl$$5$1 = /*#__PURE__*/template(`<span hidden>`); | |
const EnvironmentProvider = props => { | |
const [spanRef, setSpanRef] = createSignal(); | |
const getRootNode = () => runIfFn$1(props.value) ?? spanRef()?.ownerDocument ?? document; | |
const environment = createMemo(() => ({ | |
getRootNode, | |
getDocument: () => getDocument(getRootNode()), | |
getWindow: () => getWindow$1(getRootNode()) | |
})); | |
return createComponent(EnvironmentContextProvider, { | |
value: environment, | |
get children() { | |
return [createMemo(() => props.children), createComponent(Show, { | |
get when() { | |
return !props.value; | |
}, | |
get children() { | |
var _el$ = _tmpl$$5$1(); | |
use(setSpanRef, _el$); | |
return _el$; | |
} | |
})]; | |
} | |
}); | |
}; | |
const [LocaleContextProvider, useLocaleContext] = createContext({ | |
hookName: 'useEnvironmentContext', | |
providerName: '<EnvironmentProvider />', | |
strict: false, | |
defaultValue: () => ({ | |
dir: 'ltr', | |
locale: 'en-US' | |
}) | |
}); | |
const splitPresenceProps = props => createSplitProps()(props, ['immediate', 'lazyMount', 'onExitComplete', 'present', 'unmountOnExit']); | |
const usePresence = props => { | |
const [renderStrategyProps, context] = splitRenderStrategyProps(props); | |
const [wasEverPresent, setWasEverPresent] = createSignal(false); | |
const [state, send] = useMachine(machine(context), { | |
context | |
}); | |
const api = createMemo(() => connect(state, send)); | |
createEffect(() => { | |
const present = api().present; | |
if (present) setWasEverPresent(true); | |
}); | |
return createMemo(() => ({ | |
unmounted: !api().present && !wasEverPresent() && renderStrategyProps.lazyMount || renderStrategyProps.unmountOnExit && !api().present && wasEverPresent(), | |
present: api().present, | |
presenceProps: { | |
ref: api().setNode, | |
hidden: !api().present, | |
'data-state': context.present ? 'open' : 'closed' | |
} | |
})); | |
}; | |
const [PresenceProvider, usePresenceContext] = createContext({ | |
hookName: 'usePresenceContext', | |
providerName: '<PresenceProvider />' | |
}); | |
const [DialogProvider, useDialogContext] = createContext({ | |
hookName: 'useDialogContext', | |
providerName: '<DialogProvider />' | |
}); | |
const DialogBackdrop = props => { | |
const api = useDialogContext(); | |
const renderStrategyProps = useRenderStrategyContext(); | |
const presenceApi = usePresence(mergeProps(renderStrategyProps, () => ({ | |
present: api().open | |
}))); | |
const mergedProps = mergeProps(() => api().getBackdropProps(), () => presenceApi().presenceProps, props); | |
return createComponent(Show, { | |
get when() { | |
return !presenceApi().unmounted; | |
}, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const DialogCloseTrigger = props => { | |
const dialog = useDialogContext(); | |
const mergedProps = mergeProps(() => dialog().getCloseTriggerProps(), props); | |
return createComponent(ark.button, mergedProps); | |
}; | |
const DialogContent = props => { | |
const api = useDialogContext(); | |
const presenceApi = usePresenceContext(); | |
const mergedProps = mergeProps(() => api().getContentProps(), () => presenceApi().presenceProps, props); | |
return createComponent(Show, { | |
get when() { | |
return !presenceApi().unmounted; | |
}, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const DialogContext = props => props.children(useDialogContext()); | |
const DialogDescription = props => { | |
const dialog = useDialogContext(); | |
const mergedProps = mergeProps(() => dialog().getDescriptionProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const DialogPositioner = props => { | |
const api = useDialogContext(); | |
const presenceApi = usePresenceContext(); | |
const mergedProps = mergeProps(() => api().getPositionerProps(), props); | |
return createComponent(Show, { | |
get when() { | |
return !presenceApi().unmounted; | |
}, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const useDialog = props => { | |
const locale = useLocaleContext(); | |
const environment = useEnvironmentContext(); | |
const id = createUniqueId(); | |
const context = createMemo(() => ({ | |
id, | |
dir: locale().dir, | |
getRootNode: environment().getRootNode, | |
open: props.defaultOpen, | |
'open.controlled': props.open !== undefined, | |
...props | |
})); | |
const [state, send] = useMachine(machine$3(context()), { | |
context | |
}); | |
return createMemo(() => connect$4(state, send, normalizeProps)); | |
}; | |
const DialogRoot = props => { | |
const [presenceProps, dialogProps] = splitPresenceProps(props); | |
const [renderStrategyProps] = splitRenderStrategyProps(presenceProps); | |
const [useDialogProps, localProps] = createSplitProps()(dialogProps, ['aria-label', 'closeOnEscape', 'closeOnInteractOutside', 'defaultOpen', 'finalFocusEl', 'id', 'ids', 'initialFocusEl', 'modal', 'onEscapeKeyDown', 'onFocusOutside', 'onInteractOutside', 'onOpenChange', 'onPointerDownOutside', 'open', 'persistentElements', 'preventScroll', 'restoreFocus', 'role', 'trapFocus']); | |
const api = useDialog(useDialogProps); | |
const apiPresence = usePresence(mergeProps(presenceProps, () => ({ | |
present: api().open | |
}))); | |
return createComponent(DialogProvider, { | |
value: api, | |
get children() { | |
return createComponent(RenderStrategyProvider, { | |
value: renderStrategyProps, | |
get children() { | |
return createComponent(PresenceProvider, { | |
value: apiPresence, | |
get children() { | |
return localProps.children; | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
}; | |
const DialogRootProvider = props => { | |
const [presenceProps, dialogProps] = splitPresenceProps(props); | |
const [renderStrategyProps] = splitRenderStrategyProps(presenceProps); | |
const apiPresence = usePresence(mergeProps(presenceProps, () => ({ | |
present: dialogProps.value().open | |
}))); | |
return createComponent(DialogProvider, { | |
get value() { | |
return dialogProps.value; | |
}, | |
get children() { | |
return createComponent(RenderStrategyProvider, { | |
value: renderStrategyProps, | |
get children() { | |
return createComponent(PresenceProvider, { | |
value: apiPresence, | |
get children() { | |
return dialogProps.children; | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
}; | |
const DialogTitle = props => { | |
const dialog = useDialogContext(); | |
const mergedProps = mergeProps(() => dialog().getTitleProps(), props); | |
return createComponent(ark.h2, mergedProps); | |
}; | |
const DialogTrigger = props => { | |
const api = useDialogContext(); | |
const presenceApi = usePresenceContext(); | |
const mergedProps = mergeProps(() => api().getTriggerProps(), () => ({ | |
'aria-controls': presenceApi().unmounted && null | |
}), props); | |
return createComponent(ark.button, mergedProps); | |
}; | |
var dialog = /*#__PURE__*/Object.freeze({ | |
__proto__: null, | |
Backdrop: DialogBackdrop, | |
CloseTrigger: DialogCloseTrigger, | |
Content: DialogContent, | |
Context: DialogContext, | |
Description: DialogDescription, | |
Positioner: DialogPositioner, | |
Root: DialogRoot, | |
RootProvider: DialogRootProvider, | |
Title: DialogTitle, | |
Trigger: DialogTrigger | |
}); | |
const [MenuProvider, useMenuContext] = createContext({ | |
hookName: 'useMenuContext', | |
providerName: '<MenuProvider />', | |
strict: false | |
}); | |
const MenuArrow = props => { | |
const context = useMenuContext(); | |
const mergedProps = mergeProps(() => context().getArrowProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const MenuArrowTip = props => { | |
const context = useMenuContext(); | |
const mergedProps = mergeProps(() => context().getArrowTipProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const [MenuItemProvider, useMenuItemContext] = createContext({ | |
hookName: 'useMenuItemContext', | |
providerName: '<MenuItemProvider />' | |
}); | |
const [MenuOptionItemPropsProvider, useMenuOptionItemPropsContext] = createContext({ | |
hookName: 'useMenuOptionItemPropsContext', | |
providerName: '<MenuOptionItemPropsProvider />' | |
}); | |
const MenuCheckboxItem = props => { | |
const [partialOptionItemProps, localProps] = createSplitProps()(props, ['checked', 'closeOnSelect', 'disabled', 'onCheckedChange', 'value', 'valueText']); | |
const optionItemProps = mergeProps(partialOptionItemProps, { | |
type: 'checkbox' | |
}); | |
const context = useMenuContext(); | |
const mergedProps = mergeProps(() => context().getOptionItemProps(optionItemProps), localProps); | |
const itemState = createMemo(() => context().getItemState(optionItemProps)); | |
return createComponent(MenuOptionItemPropsProvider, { | |
value: optionItemProps, | |
get children() { | |
return createComponent(MenuItemProvider, { | |
value: itemState, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
} | |
}); | |
}; | |
const MenuContent = props => { | |
const context = useMenuContext(); | |
const presenceContext = usePresenceContext(); | |
const mergedProps = mergeProps(() => context().getContentProps(), () => presenceContext().presenceProps, props); | |
return createComponent(Show, { | |
get when() { | |
return !presenceContext().unmounted; | |
}, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const MenuContext = props => props.children(useMenuContext()); | |
const MenuContextTrigger = props => { | |
const context = useMenuContext(); | |
const mergedProps = mergeProps(() => context().getContextTriggerProps(), props); | |
return createComponent(ark.button, mergedProps); | |
}; | |
const MenuIndicator = props => { | |
const context = useMenuContext(); | |
const mergedProps = mergeProps(() => context().getIndicatorProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const MenuItem = props => { | |
const [itemProps, localProps] = createSplitProps()(props, ['closeOnSelect', 'disabled', 'value', 'valueText']); | |
const context = useMenuContext(); | |
const mergedProps = mergeProps(() => context().getItemProps(itemProps), localProps); | |
const itemState = createMemo(() => context().getItemState(itemProps)); | |
return createComponent(MenuItemProvider, { | |
value: itemState, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const MenuItemContext = props => props.children(useMenuItemContext()); | |
const [MenuItemGroupProvider, useMenuItemGroupContext] = createContext({ | |
hookName: 'useMenuItemGroupContext', | |
providerName: '<MenuItemGroupProvider />' | |
}); | |
const MenuItemGroup = props => { | |
const [optionalItemGroupProps, localProps] = createSplitProps()(props, ['id']); | |
const itemGroupProps = mergeProps({ | |
id: createUniqueId() | |
}, optionalItemGroupProps); | |
const menu = useMenuContext(); | |
const mergedProps = mergeProps(() => menu().getItemGroupProps(itemGroupProps), localProps); | |
return createComponent(MenuItemGroupProvider, { | |
value: itemGroupProps, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const MenuItemGroupLabel = props => { | |
const context = useMenuContext(); | |
const itemGroupContext = useMenuItemGroupContext(); | |
const mergedProps = mergeProps(context().getItemGroupLabelProps({ | |
htmlFor: itemGroupContext.id | |
}), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const MenuItemIndicator = props => { | |
const context = useMenuContext(); | |
const optionItemProps = useMenuOptionItemPropsContext(); | |
const mergedProps = mergeProps(() => context().getItemIndicatorProps(optionItemProps), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const MenuItemText = props => { | |
const context = useMenuContext(); | |
const optionItemProps = useMenuOptionItemPropsContext(); | |
const mergedProps = mergeProps(() => context().getItemTextProps(optionItemProps), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const MenuPositioner = props => { | |
const context = useMenuContext(); | |
const presence = usePresenceContext(); | |
const mergedProps = mergeProps(() => context().getPositionerProps(), props); | |
return createComponent(Show, { | |
get when() { | |
return !presence().unmounted; | |
}, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const MenuRadioItem = props => { | |
const [partialItemProps, localProps] = createSplitProps()(props, ['closeOnSelect', 'disabled', 'value', 'valueText']); | |
const context = useMenuContext(); | |
const itemGroup = useMenuItemGroupContext(); | |
const optionItemProps = mergeProps(partialItemProps, () => ({ | |
type: 'radio', | |
checked: itemGroup.value === partialItemProps.value, | |
onCheckedChange: () => itemGroup.onValueChange?.({ | |
value: partialItemProps.value | |
}) | |
})); | |
const mergedProps = mergeProps(() => context().getOptionItemProps(optionItemProps), localProps); | |
const itemState = createMemo(() => context().getItemState(optionItemProps)); | |
return createComponent(MenuOptionItemPropsProvider, { | |
value: optionItemProps, | |
get children() { | |
return createComponent(MenuItemProvider, { | |
value: itemState, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
} | |
}); | |
}; | |
const MenuRadioItemGroup = props => { | |
const [optionalItemGroupProps, localProps] = createSplitProps()(props, ['id', 'onValueChange', 'value']); | |
const context = useMenuContext(); | |
const itemGroupProps = mergeProps({ | |
id: createUniqueId() | |
}, optionalItemGroupProps); | |
const mergedProps = mergeProps(() => context().getItemGroupProps(itemGroupProps), localProps); | |
return createComponent(MenuItemGroupProvider, { | |
value: itemGroupProps, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const useMenu = props => { | |
const locale = useLocaleContext(); | |
const environment = useEnvironmentContext(); | |
const id = createUniqueId(); | |
const context = createMemo(() => ({ | |
id, | |
dir: locale().dir, | |
getRootNode: environment().getRootNode, | |
open: props.defaultOpen, | |
'open.controlled': props.open !== undefined, | |
...props | |
})); | |
const [state, send, machine] = useMachine(machine$2(context()), { | |
context | |
}); | |
const api = createMemo(() => connect$3(state, send, normalizeProps)); | |
return { | |
api, | |
machine | |
}; | |
}; | |
const [MenuMachineProvider, useMenuMachineContext] = createContext({ | |
hookName: 'useMenuMachineContext', | |
providerName: '<MenuMachineProvider />', | |
strict: false | |
}); | |
const [MenuTriggerItemProvider, useMenuTriggerItemContext] = createContext({ | |
hookName: 'useMenuMachineContext', | |
providerName: '<MenuMachineProvider />', | |
strict: false | |
}); | |
const MenuRoot = props => { | |
const [presenceProps, menuProps] = splitPresenceProps(props); | |
const [useMenuProps, localProps] = createSplitProps()(menuProps, ['anchorPoint', 'aria-label', 'closeOnSelect', 'composite', 'defaultOpen', 'highlightedValue', 'id', 'ids', 'loopFocus', 'onEscapeKeyDown', 'onFocusOutside', 'onHighlightChange', 'onInteractOutside', 'onOpenChange', 'onPointerDownOutside', 'onSelect', 'open', 'positioning', 'typeahead']); | |
const parentApi = useMenuContext(); | |
const parentMachine = useMenuMachineContext(); | |
const menu = useMenu(useMenuProps); | |
const presenceApi = usePresence(mergeProps(presenceProps, () => ({ | |
present: menu.api().open | |
}))); | |
createEffect(() => { | |
if (!parentMachine) return; | |
parentApi?.().setChild(menu.machine); | |
menu.api().setParent(parentMachine); | |
}); | |
const triggerItemContext = () => parentApi?.().getTriggerItemProps(menu.api()); | |
return createComponent(MenuTriggerItemProvider, { | |
value: triggerItemContext, | |
get children() { | |
return createComponent(MenuMachineProvider, { | |
get value() { | |
return menu.machine; | |
}, | |
get children() { | |
return createComponent(MenuProvider, { | |
get value() { | |
return menu.api; | |
}, | |
get children() { | |
return createComponent(PresenceProvider, { | |
value: presenceApi, | |
get children() { | |
return localProps.children; | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
}; | |
const MenuRootProvider = props => { | |
const parentApi = useMenuContext(); | |
const parentMachine = useMenuMachineContext(); | |
const [presenceProps, menuProps] = splitPresenceProps(props); | |
const presenceApi = usePresence(mergeProps(presenceProps, () => ({ | |
present: menuProps.value.api().open | |
}))); | |
createEffect(() => { | |
if (!parentMachine) return; | |
parentApi?.().setChild(menuProps.value.machine); | |
menuProps.value.api().setParent(parentMachine); | |
}); | |
const triggerItemContext = () => parentApi?.().getTriggerItemProps(menuProps.value.api()); | |
return createComponent(MenuTriggerItemProvider, { | |
value: triggerItemContext, | |
get children() { | |
return createComponent(MenuMachineProvider, { | |
get value() { | |
return menuProps.value.machine; | |
}, | |
get children() { | |
return createComponent(MenuProvider, { | |
get value() { | |
return menuProps.value.api; | |
}, | |
get children() { | |
return createComponent(PresenceProvider, { | |
value: presenceApi, | |
get children() { | |
return menuProps.children; | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
}; | |
const MenuSeparator = props => { | |
const menu = useMenuContext(); | |
const mergedProps = mergeProps(() => menu().getSeparatorProps(), props); | |
return createComponent(ark.hr, mergedProps); | |
}; | |
const MenuTrigger = props => { | |
const api = useMenuContext(); | |
const presenceApi = usePresenceContext(); | |
const mergedProps = mergeProps(() => api().getTriggerProps(), () => ({ | |
'aria-controls': presenceApi().unmounted && null | |
}), props); | |
return createComponent(ark.button, mergedProps); | |
}; | |
const MenuTriggerItem = props => { | |
const getTriggerItemProps = useMenuTriggerItemContext(); | |
const mergedProps = mergeProps(() => getTriggerItemProps?.(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
var menu = /*#__PURE__*/Object.freeze({ | |
__proto__: null, | |
Arrow: MenuArrow, | |
ArrowTip: MenuArrowTip, | |
CheckboxItem: MenuCheckboxItem, | |
Content: MenuContent, | |
Context: MenuContext, | |
ContextTrigger: MenuContextTrigger, | |
Indicator: MenuIndicator, | |
Item: MenuItem, | |
ItemContext: MenuItemContext, | |
ItemGroup: MenuItemGroup, | |
ItemGroupLabel: MenuItemGroupLabel, | |
ItemIndicator: MenuItemIndicator, | |
ItemText: MenuItemText, | |
Positioner: MenuPositioner, | |
RadioItem: MenuRadioItem, | |
RadioItemGroup: MenuRadioItemGroup, | |
Root: MenuRoot, | |
RootProvider: MenuRootProvider, | |
Separator: MenuSeparator, | |
Trigger: MenuTrigger, | |
TriggerItem: MenuTriggerItem | |
}); | |
const [ProgressProvider, useProgressContext] = createContext({ | |
hookName: 'useProgressContext', | |
providerName: '<ProgressProvider />' | |
}); | |
const ProgressCircle = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getCircleProps(), props); | |
return createComponent(ark.svg, mergedProps); | |
}; | |
const ProgressCircleRange = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getCircleRangeProps(), props); | |
return createComponent(ark.circle, mergedProps); | |
}; | |
const ProgressCircleTrack = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getCircleTrackProps(), props); | |
return createComponent(ark.circle, mergedProps); | |
}; | |
const ProgressContext = props => props.children(useProgressContext()); | |
const ProgressLabel = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getLabelProps(), props); | |
return createComponent(ark.label, mergedProps); | |
}; | |
const ProgressRange = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getRangeProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const useProgress = props => { | |
const locale = useLocaleContext(); | |
const environment = useEnvironmentContext(); | |
const id = createUniqueId(); | |
const context = createMemo(() => ({ | |
id, | |
dir: locale().dir, | |
getRootNode: environment().getRootNode, | |
...props | |
})); | |
const [state, send] = useMachine(machine$1(context()), { | |
context | |
}); | |
return createMemo(() => connect$2(state, send, normalizeProps)); | |
}; | |
const ProgressRoot = props => { | |
const [progressProps, localProps] = createSplitProps()(props, ['id', 'ids', 'max', 'min', 'orientation', 'translations', 'value']); | |
const api = useProgress(progressProps); | |
const mergedProps = mergeProps(() => api().getRootProps(), localProps); | |
return createComponent(ProgressProvider, { | |
value: api, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const ProgressRootProvider = props => { | |
const [{ | |
value: progress | |
}, localProps] = createSplitProps()(props, ['value']); | |
const mergedProps = mergeProps(() => progress().getRootProps(), localProps); | |
return createComponent(ProgressProvider, { | |
value: progress, | |
get children() { | |
return createComponent(ark.div, mergedProps); | |
} | |
}); | |
}; | |
const ProgressTrack = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getTrackProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const ProgressValueText = props => { | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getValueTextProps(), props); | |
return createComponent(ark.span, mergeProps$2(mergedProps, { | |
get children() { | |
return props.children || api().valueAsString; | |
} | |
})); | |
}; | |
const ProgressView = props => { | |
const [state, localProps] = createSplitProps()(props, ['state']); | |
const api = useProgressContext(); | |
const mergedProps = mergeProps(() => api().getViewProps(state), localProps); | |
return createComponent(ark.span, mergedProps); | |
}; | |
var progress = /*#__PURE__*/Object.freeze({ | |
__proto__: null, | |
Circle: ProgressCircle, | |
CircleRange: ProgressCircleRange, | |
CircleTrack: ProgressCircleTrack, | |
Context: ProgressContext, | |
Label: ProgressLabel, | |
Range: ProgressRange, | |
Root: ProgressRoot, | |
RootProvider: ProgressRootProvider, | |
Track: ProgressTrack, | |
ValueText: ProgressValueText, | |
View: ProgressView | |
}); | |
// src/create-anatomy.ts | |
var createAnatomy = (name, parts = []) => ({ | |
parts: (...values) => { | |
if (isEmpty(parts)) { | |
return createAnatomy(name, values); | |
} | |
throw new Error("createAnatomy().parts(...) should only be called once. Did you mean to use .extendWith(...) ?"); | |
}, | |
extendWith: (...values) => createAnatomy(name, [...parts, ...values]), | |
rename: (newName) => createAnatomy(newName, parts), | |
keys: () => parts, | |
build: () => [...new Set(parts)].reduce( | |
(prev, part) => Object.assign(prev, { | |
[part]: { | |
selector: [ | |
`&[data-scope="${toKebabCase(name)}"][data-part="${toKebabCase(part)}"]`, | |
`& [data-scope="${toKebabCase(name)}"][data-part="${toKebabCase(part)}"]` | |
].join(", "), | |
attrs: { "data-scope": toKebabCase(name), "data-part": toKebabCase(part) } | |
} | |
}), | |
{} | |
) | |
}); | |
var toKebabCase = (value) => value.replace(/([A-Z])([A-Z])/g, "$1-$2").replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase(); | |
var isEmpty = (v) => v.length === 0; | |
// src/global.ts | |
function getGlobal$1() { | |
if (typeof globalThis !== "undefined") return globalThis; | |
if (typeof self !== "undefined") return self; | |
if (typeof window !== "undefined") return window; | |
if (typeof global !== "undefined") return global; | |
} | |
function makeGlobal(key, value) { | |
const g = getGlobal$1(); | |
if (!g) return value(); | |
g[key] || (g[key] = value()); | |
return g[key]; | |
} | |
process.env.NODE_ENV !== "production"; | |
makeGlobal("__zag__proxyStateMap", () => /* @__PURE__ */ new WeakMap()); | |
makeGlobal("__zag__refSet", () => /* @__PURE__ */ new WeakSet()); | |
// src/signature-pad.anatomy.ts | |
var anatomy = createAnatomy("signature-pad").parts( | |
"root", | |
"control", | |
"segment", | |
"segmentPath", | |
"guide", | |
"clearTrigger", | |
"label" | |
); | |
anatomy.build(); | |
var dom = createScope({ | |
getRootId: (ctx) => ctx.ids?.root ?? `signature-${ctx.id}`, | |
getControlId: (ctx) => ctx.ids?.control ?? `signature-control-${ctx.id}`, | |
getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `signature-input-${ctx.id}`, | |
getControlEl: (ctx) => dom.getById(ctx, dom.getControlId(ctx)), | |
getSegmentEl: (ctx) => query(dom.getControlEl(ctx), "[data-part=segment]"), | |
getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)), | |
getDataUrl: (ctx, options) => { | |
if (ctx.isEmpty) return Promise.resolve(""); | |
return getDataUrl(dom.getSegmentEl(ctx), options); | |
} | |
}); | |
createProps()([ | |
"dir", | |
"disabled", | |
"getRootNode", | |
"id", | |
"ids", | |
"onDraw", | |
"onDrawEnd", | |
"readOnly", | |
"drawing", | |
"name" | |
]); | |
const createToaster = props => { | |
const machine = group.machine({ | |
id: '1', | |
...props | |
}); | |
const api = group.connect(machine, machine.send, normalizeProps); | |
return { | |
...api, | |
machine | |
}; | |
}; | |
const [ToastProvider, useToastContext] = createContext({ | |
hookName: 'useToastContext', | |
providerName: '<ToastProvider />' | |
}); | |
const ToastActionTrigger = props => { | |
const toast = useToastContext(); | |
const mergedProps = mergeProps(() => toast().getActionTriggerProps(), props); | |
return createComponent(ark.button, mergedProps); | |
}; | |
const ToastCloseTrigger = props => { | |
const toast = useToastContext(); | |
const mergedProps = mergeProps(() => toast().getCloseTriggerProps(), props); | |
return createComponent(ark.button, mergedProps); | |
}; | |
const ToastContext = props => props.children(useToastContext()); | |
const ToastDescription = props => { | |
const toast = useToastContext(); | |
const mergedProps = mergeProps(() => toast().getDescriptionProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
var _tmpl$$D = /*#__PURE__*/template(`<div><div></div><div>`); | |
const ToastRoot = props => { | |
const toast = useToastContext(); | |
const mergedProps = mergeProps(() => toast().getRootProps(), props); | |
return (() => { | |
var _el$ = _tmpl$$D(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.nextSibling; | |
spread(_el$, mergedProps, false, true); | |
spread(_el$2, mergeProps$2(() => toast().getGhostBeforeProps()), false, false); | |
insert(_el$, () => props.children, _el$3); | |
spread(_el$3, mergeProps$2(() => toast().getGhostAfterProps()), false, false); | |
return _el$; | |
})(); | |
}; | |
const ToastTitle = props => { | |
const toast = useToastContext(); | |
const mergedProps = mergeProps(() => toast().getTitleProps(), props); | |
return createComponent(ark.div, mergedProps); | |
}; | |
const Toaster = props => { | |
const [toasterProps, localProps] = splitProps(props, ['toaster', 'children']); | |
const [state, send] = useMachine(toasterProps.toaster.machine); | |
const placement = state.context.placement; | |
const api = createMemo(() => group.connect(state, send, normalizeProps)); | |
const toasts = createMemo(() => api().getToastsByPlacement(placement)); | |
const mergedProps = mergeProps(api().getGroupProps({ | |
placement | |
}), localProps); | |
return createComponent(ark.div, mergeProps$2(mergedProps, { | |
get children() { | |
return createComponent(For, { | |
get each() { | |
return toasts(); | |
}, | |
children: toast => createComponent(ToastActor, { | |
value: toast, | |
children: ctx => toasterProps.children(ctx) | |
}) | |
}); | |
} | |
})); | |
}; | |
const ToastActor = props => { | |
const [state, send] = useActor(props.value); | |
const api = createMemo(() => connect$1(state, send, normalizeProps)); | |
const ctx = createMemo(() => state.context); | |
return createComponent(ToastProvider, { | |
value: api, | |
get children() { | |
return props.children(ctx); | |
} | |
}); | |
}; | |
var toast = /*#__PURE__*/Object.freeze({ | |
__proto__: null, | |
ActionTrigger: ToastActionTrigger, | |
CloseTrigger: ToastCloseTrigger, | |
Context: ToastContext, | |
Description: ToastDescription, | |
Root: ToastRoot, | |
Title: ToastTitle | |
}); | |
const _tmpl$$C = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2px" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18">`); | |
const CloseIcon = props => (() => { | |
const _el$ = _tmpl$$C(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
const Modal = props => { | |
return createComponent(dialog.Root, { | |
get open() { | |
return props.isOpen; | |
}, | |
lazyMount: true, | |
unmountOnExit: true, | |
onOpenChange: e => !e.open ? props.onClose?.() : undefined, | |
get children() { | |
return [createComponent(dialog.Backdrop, { | |
"class": "fixed inset-0 bg-[rgba(0,0,0,0.5)] h-screen z-50" | |
}), createComponent(dialog.Positioner, { | |
"class": "fixed inset-0 z-50 flex items-center justify-center px-2", | |
get children() { | |
return [createComponent(dialog.Content, { | |
get children() { | |
return props.children; | |
} | |
}), createComponent(dialog.CloseTrigger, { | |
"class": "fixed top-2 right-2 z-50 rounded-md bg-white p-2 text-black", | |
get children() { | |
return createComponent(CloseIcon, { | |
"class": "w-6 h-6" | |
}); | |
} | |
})]; | |
} | |
})]; | |
} | |
}); | |
}; | |
const _tmpl$$B = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2px" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path><path d="M14 2v4a2 2 0 0 0 2 2h4">`); | |
const FileIcon = props => (() => { | |
const _el$ = _tmpl$$B(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
const _tmpl$$A = /*#__PURE__*/template(`<div class="flex items-center gap-4 border bg-white border-gray-200 rounded-md p-2 text-gray-900 min-w-[250px]"><div></div><div class="flex flex-col"><span class="text-md font-semibold text-sm"></span><span class="text-gray-500 text-xs">`); | |
const FilePreview = props => { | |
const fileColor = getFileAssociatedColor(props.file); | |
return (() => { | |
const _el$ = _tmpl$$A(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.nextSibling, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.nextSibling; | |
insert(_el$2, createComponent(FileIcon, { | |
"class": "w-6 h-6" | |
})); | |
insert(_el$4, () => props.file.name); | |
insert(_el$5, () => formatFileExtensionHumanReadable(props.file)); | |
createRenderEffect(() => className(_el$2, clsx$1('rounded-md text-white p-2 flex items-center', fileColor === 'pink' && 'bg-pink-400', fileColor === 'blue' && 'bg-blue-400', fileColor === 'green' && 'bg-green-400', fileColor === 'gray' && 'bg-gray-400', fileColor === 'orange' && 'bg-orange-400'))); | |
return _el$; | |
})(); | |
}; | |
const formatFileExtensionHumanReadable = file => { | |
const extension = file.name.split('.').pop(); | |
switch (extension) { | |
case 'pdf': | |
return 'PDF'; | |
case 'doc': | |
case 'docx': | |
return 'Word'; | |
case 'xls': | |
case 'xlsx': | |
case 'csv': | |
return 'Sheet'; | |
case 'json': | |
return 'JSON'; | |
case 'md': | |
return 'Markdown'; | |
default: | |
return 'DOCUMENT'; | |
} | |
}; | |
const getFileAssociatedColor = file => { | |
const extension = file.name.split('.').pop(); | |
if (!extension) return 'gray'; | |
switch (extension) { | |
case 'pdf': | |
return 'pink'; | |
case 'doc': | |
case 'docx': | |
return 'blue'; | |
case 'xls': | |
case 'xlsx': | |
case 'csv': | |
return 'green'; | |
case 'json': | |
return 'orange'; | |
default: | |
return 'gray'; | |
} | |
}; | |
const _tmpl$$z = /*#__PURE__*/template(`<div>`), | |
_tmpl$2$k = /*#__PURE__*/template(`<span class="px-[15px] py-[7px]">`), | |
_tmpl$3$b = /*#__PURE__*/template(`<img alt="Attachment">`), | |
_tmpl$4$6 = /*#__PURE__*/template(`<div class="flex justify-end items-end animate-fade-in gap-2 guest-container"><div class="flex flex-col gap-1 items-end"><div class="p-[1px] whitespace-pre-wrap max-w-full typebot-guest-bubble flex flex-col" data-testid="guest-bubble">`), | |
_tmpl$5$4 = /*#__PURE__*/template(`<img>`); | |
const GuestBubble = props => { | |
const [clickedImageSrc, setClickedImageSrc] = createSignal(); | |
return (() => { | |
const _el$ = _tmpl$4$6(), | |
_el$2 = _el$.firstChild, | |
_el$5 = _el$2.firstChild; | |
insert(_el$2, createComponent(Show, { | |
get when() { | |
return (props.message.attachments ?? []).length > 0; | |
}, | |
get children() { | |
return [(() => { | |
const _el$3 = _tmpl$$z(); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return props.message.attachments?.filter(attachment => attachment.type.startsWith('image')); | |
}, | |
children: (attachment, idx) => (() => { | |
const _el$8 = _tmpl$5$4(); | |
_el$8.$$click = () => setClickedImageSrc(attachment.url); | |
createRenderEffect(_p$ => { | |
const _v$ = attachment.url, | |
_v$2 = `Attached image ${idx() + 1}`, | |
_v$3 = clsx$1('typebot-guest-bubble-image-attachment cursor-pointer', props.message.attachments.filter(attachment => attachment.type.startsWith('image')).length > 1 && 'max-w-[90%]'); | |
_v$ !== _p$._v$ && setAttribute(_el$8, "src", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$8, "alt", _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && className(_el$8, _p$._v$3 = _v$3); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined | |
}); | |
return _el$8; | |
})() | |
})); | |
createRenderEffect(() => className(_el$3, clsx$1('flex gap-1 overflow-auto max-w-[350px]', isMobile() ? 'flex-wrap justify-end' : 'items-center'))); | |
return _el$3; | |
})(), (() => { | |
const _el$4 = _tmpl$$z(); | |
insert(_el$4, createComponent(For, { | |
get each() { | |
return props.message.attachments?.filter(attachment => !attachment.type.startsWith('image')); | |
}, | |
children: attachment => createComponent(FilePreview, { | |
get file() { | |
return { | |
name: attachment.url.split('/').at(-1) | |
}; | |
} | |
}) | |
})); | |
createRenderEffect(() => className(_el$4, clsx$1('flex gap-1 overflow-auto max-w-[350px]', isMobile() ? 'flex-wrap justify-end' : 'items-center'))); | |
return _el$4; | |
})()]; | |
} | |
}), _el$5); | |
insert(_el$5, createComponent(Show, { | |
get when() { | |
return isNotEmpty(props.message.text); | |
}, | |
get children() { | |
const _el$6 = _tmpl$2$k(); | |
insert(_el$6, () => props.message.text); | |
return _el$6; | |
} | |
})); | |
insert(_el$, createComponent(Modal, { | |
get isOpen() { | |
return clickedImageSrc() !== undefined; | |
}, | |
onClose: () => setClickedImageSrc(undefined), | |
get children() { | |
const _el$7 = _tmpl$3$b(); | |
_el$7.style.setProperty("border-radius", "6px"); | |
createRenderEffect(() => setAttribute(_el$7, "src", clickedImageSrc())); | |
return _el$7; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.showAvatar; | |
}, | |
get children() { | |
return createComponent(Avatar, { | |
get initialAvatarSrc() { | |
return props.avatarSrc; | |
} | |
}); | |
} | |
}), null); | |
createRenderEffect(() => (props.hasHostAvatar ? isMobile() ? '28px' : '50px' : undefined) != null ? _el$.style.setProperty("margin-left", props.hasHostAvatar ? isMobile() ? '28px' : '50px' : undefined) : _el$.style.removeProperty("margin-left")); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["click"]); | |
const defaultButtonLabel = 'Send'; | |
let InputBlockType = /*#__PURE__*/function (InputBlockType) { | |
InputBlockType["TEXT"] = "text input"; | |
InputBlockType["NUMBER"] = "number input"; | |
InputBlockType["EMAIL"] = "email input"; | |
InputBlockType["URL"] = "url input"; | |
InputBlockType["DATE"] = "date input"; | |
InputBlockType["PHONE"] = "phone number input"; | |
InputBlockType["CHOICE"] = "choice input"; | |
InputBlockType["PICTURE_CHOICE"] = "picture choice input"; | |
InputBlockType["PAYMENT"] = "payment input"; | |
InputBlockType["RATING"] = "rating input"; | |
InputBlockType["FILE"] = "file input"; | |
return InputBlockType; | |
}({}); | |
const defaultTextInputOptions = { | |
isLong: false, | |
labels: { | |
button: defaultButtonLabel, | |
placeholder: 'Type your answer...' | |
}, | |
attachments: { | |
isEnabled: false, | |
visibility: 'Auto' | |
} | |
}; | |
const _tmpl$$y = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2px" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path><path d="M14 2v4a2 2 0 0 0 2 2h4"></path><circle cx="10" cy="12" r="2"></circle><path d="m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22">`); | |
const PictureIcon = props => (() => { | |
const _el$ = _tmpl$$y(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
const _tmpl$$x = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48">`); | |
const PaperClipIcon = props => { | |
return (() => { | |
const _el$ = _tmpl$$x(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
}; | |
const _tmpl$$w = /*#__PURE__*/template(`<input type="file" id="document-upload" multiple class="hidden">`), | |
_tmpl$2$j = /*#__PURE__*/template(`<input type="file" id="photos-upload" accept="image/*" multiple class="hidden">`), | |
_tmpl$3$a = /*#__PURE__*/template(`<label aria-label="Add attachments" for="document-upload">`), | |
_tmpl$4$5 = /*#__PURE__*/template(`<label> Document`), | |
_tmpl$5$3 = /*#__PURE__*/template(`<label> Photos & videos`); | |
const TextInputAddFileButton = props => { | |
return [(() => { | |
const _el$ = _tmpl$$w(); | |
_el$.addEventListener("change", e => { | |
if (!e.currentTarget.files) return; | |
props.onNewFiles(e.currentTarget.files); | |
}); | |
return _el$; | |
})(), (() => { | |
const _el$2 = _tmpl$2$j(); | |
_el$2.addEventListener("change", e => { | |
if (!e.currentTarget.files) return; | |
props.onNewFiles(e.currentTarget.files); | |
}); | |
return _el$2; | |
})(), createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return isMobile(); | |
}, | |
get children() { | |
const _el$3 = _tmpl$3$a(); | |
insert(_el$3, createComponent(PaperClipIcon, { | |
"class": "w-5" | |
})); | |
createRenderEffect(() => className(_el$3, clsx$1('filter data-[state=open]:backdrop-brightness-90 hover:backdrop-brightness-95 transition rounded-md p-2 focus:outline-none', props.class))); | |
return _el$3; | |
} | |
}), createComponent(Match, { | |
when: true, | |
get children() { | |
return createComponent(menu.Root, { | |
get children() { | |
return [createComponent(menu.Trigger, { | |
get ["class"]() { | |
return clsx$1('filter data-[state=open]:backdrop-brightness-90 hover:backdrop-brightness-95 transition rounded-md p-2 focus:outline-none', props.class); | |
}, | |
"aria-label": "Add attachments", | |
get children() { | |
return createComponent(PaperClipIcon, { | |
"class": "w-5" | |
}); | |
} | |
}), createComponent(menu.Positioner, { | |
get children() { | |
return createComponent(menu.Content, { | |
"class": "p-3 gap-2 focus:outline-none", | |
get children() { | |
return [createComponent(menu.Item, { | |
value: "document", | |
asChild: props => (() => { | |
const _el$4 = _tmpl$4$5(), | |
_el$5 = _el$4.firstChild; | |
spread(_el$4, mergeProps$2(props, { | |
"for": "document-upload", | |
"class": "p-2 filter hover:brightness-95 flex gap-3 items-center" | |
}), false, true); | |
insert(_el$4, createComponent(FileIcon, { | |
"class": "w-4" | |
}), _el$5); | |
return _el$4; | |
})() | |
}), createComponent(menu.Item, { | |
value: "photos", | |
asChild: props => (() => { | |
const _el$6 = _tmpl$5$3(), | |
_el$7 = _el$6.firstChild; | |
spread(_el$6, mergeProps$2(props, { | |
"for": "photos-upload", | |
"class": "p-2 filter hover:brightness-95 flex gap-3 items-center" | |
}), false, true); | |
insert(_el$6, createComponent(PictureIcon, { | |
"class": "w-4" | |
}), _el$7); | |
return _el$6; | |
})() | |
})]; | |
} | |
}); | |
} | |
})]; | |
} | |
}); | |
} | |
})]; | |
} | |
})]; | |
}; | |
const _tmpl$$v = /*#__PURE__*/template(`<img class="rounded-md object-cover w-[58px] h-[58px]">`), | |
_tmpl$2$i = /*#__PURE__*/template(`<div class="relative group flex-shrink-0"><button class="absolute -right-2 p-0.5 -top-2 rounded-full bg-gray-200 text-black border border-gray-400 opacity-1 sm:opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Remove attachment">`), | |
_tmpl$3$9 = /*#__PURE__*/template(`<div class="absolute w-full h-full inset-0 bg-black/20 rounded-md">`); | |
const SelectedFile = props => { | |
return (() => { | |
const _el$ = _tmpl$2$i(), | |
_el$3 = _el$.firstChild; | |
insert(_el$, createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return props.file.type.startsWith('image'); | |
}, | |
get children() { | |
const _el$2 = _tmpl$$v(); | |
createRenderEffect(_p$ => { | |
const _v$ = URL.createObjectURL(props.file), | |
_v$2 = props.file.name; | |
_v$ !== _p$._v$ && setAttribute(_el$2, "src", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$2, "alt", _p$._v$2 = _v$2); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined | |
}); | |
return _el$2; | |
} | |
}), createComponent(Match, { | |
when: true, | |
get children() { | |
return createComponent(FilePreview, { | |
get file() { | |
return props.file; | |
} | |
}); | |
} | |
})]; | |
} | |
}), _el$3); | |
_el$3.addEventListener("click", props.onRemoveClick); | |
insert(_el$3, createComponent(CloseIcon, { | |
"class": "w-4" | |
})); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return isDefined(props.uploadProgressPercent) && props.uploadProgressPercent !== 100; | |
}, | |
get children() { | |
return createComponent(UploadOverlay, { | |
get progressPercent() { | |
return props.uploadProgressPercent; | |
} | |
}); | |
} | |
}), null); | |
return _el$; | |
})(); | |
}; | |
const UploadOverlay = props => { | |
const [progressPercent, setProgressPercent] = createSignal(props.progressPercent ?? 0); | |
let interval; | |
createEffect(() => { | |
if (props.progressPercent === 20) { | |
const incrementProgress = () => { | |
if (progressPercent() < 100) { | |
setProgressPercent(prev => prev + (Math.floor(Math.random() * 10) + 1)); | |
} | |
}; | |
interval = setInterval(incrementProgress, 1000); | |
} | |
}); | |
onCleanup(() => { | |
clearInterval(interval); | |
}); | |
return (() => { | |
const _el$4 = _tmpl$3$9(); | |
insert(_el$4, createComponent(progress.Root, { | |
get value() { | |
return progressPercent(); | |
}, | |
"class": "flex items-center justify-center", | |
get children() { | |
return createComponent(progress.Circle, { | |
get children() { | |
return [createComponent(progress.CircleTrack, {}), createComponent(progress.CircleRange, {})]; | |
} | |
}); | |
} | |
})); | |
return _el$4; | |
})(); | |
}; | |
const sanitizeNewFile = ({ | |
newFile, | |
existingFiles, | |
params, | |
onError | |
}) => { | |
const sizeLimit = params.sizeLimit ?? getRuntimeVariable('NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE'); | |
if (sizeLimit && newFile.size > sizeLimit * 1024 * 1024) { | |
onError({ | |
title: 'File too large', | |
description: `${newFile.name} is larger than ${sizeLimit}MB` | |
}); | |
return; | |
} | |
if (existingFiles.length === 0) return newFile; | |
let fileName = newFile.name; | |
let counter = 1; | |
while (existingFiles.some(file => file.name === fileName)) { | |
const dotIndex = newFile.name.lastIndexOf('.'); | |
const extension = dotIndex !== -1 ? newFile.name.slice(dotIndex) : ''; | |
fileName = `${newFile.name.slice(0, dotIndex)}(${counter})${extension}`; | |
counter++; | |
} | |
return new File([newFile], fileName, { | |
type: newFile.type | |
}); | |
}; | |
const toaster = createToaster({ | |
placement: 'bottom-end', | |
gap: 24 | |
}); | |
const uploadFiles = async ({ | |
apiHost, | |
files, | |
onUploadProgress | |
}) => { | |
const urls = []; | |
let i = 0; | |
for (const { | |
input, | |
file | |
} of files) { | |
onUploadProgress && onUploadProgress({ | |
progress: i / files.length * 100, | |
fileIndex: i | |
}); | |
i += 1; | |
const { | |
data | |
} = await sendRequest({ | |
method: 'POST', | |
url: `${apiHost}/api/v2/generate-upload-url`, | |
body: { | |
fileName: input.fileName, | |
sessionId: input.sessionId, | |
fileType: file.type | |
} | |
}); | |
if (!data?.presignedUrl) continue;else { | |
const formData = new FormData(); | |
Object.entries(data.formData).forEach(([key, value]) => { | |
formData.append(key, value); | |
}); | |
formData.append('file', file); | |
const upload = await fetch(data.presignedUrl, { | |
method: 'POST', | |
body: formData | |
}); | |
if (!upload.ok) continue; | |
urls.push({ | |
url: data.fileUrl, | |
type: file.type | |
}); | |
} | |
} | |
return urls; | |
}; | |
const _tmpl$$u = /*#__PURE__*/template(`<div class="p-2 flex gap-2 border-gray-100 overflow-auto">`), | |
_tmpl$2$h = /*#__PURE__*/template(`<div><div><div>`); | |
const TextInput = props => { | |
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? ''); | |
const [selectedFiles, setSelectedFiles] = createSignal([]); | |
const [uploadProgress, setUploadProgress] = createSignal(undefined); | |
const [isDraggingOver, setIsDraggingOver] = createSignal(false); | |
let inputRef; | |
const handleInput = inputValue => setInputValue(inputValue); | |
const checkIfInputIsValid = () => inputRef?.value !== '' && inputRef?.reportValidity(); | |
const submit = async () => { | |
if (checkIfInputIsValid()) { | |
let attachments; | |
if (selectedFiles().length > 0) { | |
setUploadProgress(undefined); | |
const urls = await uploadFiles({ | |
apiHost: props.context.apiHost ?? guessApiHost({ | |
ignoreChatApiUrl: true | |
}), | |
files: selectedFiles().map(file => ({ | |
file: file, | |
input: { | |
sessionId: props.context.sessionId, | |
fileName: file.name | |
} | |
})), | |
onUploadProgress: setUploadProgress | |
}); | |
attachments = urls?.filter(isDefined); | |
} | |
props.onSubmit({ | |
value: inputRef?.value ?? inputValue(), | |
attachments | |
}); | |
} else inputRef?.focus(); | |
}; | |
const submitWhenEnter = e => { | |
if (props.block.options?.isLong) return; | |
if (e.key === 'Enter') submit(); | |
}; | |
const submitIfCtrlEnter = e => { | |
if (!props.block.options?.isLong) return; | |
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) submit(); | |
}; | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
window.addEventListener('message', processIncomingEvent); | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'setInputValue') setInputValue(data.value); | |
}; | |
const handleDragOver = e => { | |
e.preventDefault(); | |
setIsDraggingOver(true); | |
}; | |
const handleDragLeave = () => setIsDraggingOver(false); | |
const handleDropFile = e => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
if (!e.dataTransfer?.files) return; | |
onNewFiles(e.dataTransfer.files); | |
}; | |
const onNewFiles = files => { | |
const newFiles = Array.from(files).map(file => sanitizeNewFile({ | |
existingFiles: selectedFiles(), | |
newFile: file, | |
params: { | |
sizeLimit: getRuntimeVariable('NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE') | |
}, | |
onError: ({ | |
description, | |
title | |
}) => { | |
toaster.create({ | |
description, | |
title | |
}); | |
} | |
})).filter(isDefined); | |
if (newFiles.length === 0) return; | |
setSelectedFiles(selectedFiles => [...newFiles, ...selectedFiles]); | |
}; | |
const removeSelectedFile = index => { | |
setSelectedFiles(selectedFiles => selectedFiles.filter((_, i) => i !== index)); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$h(), | |
_el$2 = _el$.firstChild, | |
_el$4 = _el$2.firstChild; | |
_el$.addEventListener("dragleave", handleDragLeave); | |
_el$.addEventListener("dragover", handleDragOver); | |
_el$.addEventListener("drop", handleDropFile); | |
_el$.$$keydown = submitWhenEnter; | |
insert(_el$2, createComponent(Show, { | |
get when() { | |
return selectedFiles().length; | |
}, | |
get children() { | |
const _el$3 = _tmpl$$u(); | |
_el$3.style.setProperty("border-bottom-width", "1px"); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return selectedFiles(); | |
}, | |
children: (file, index) => createComponent(SelectedFile, { | |
file: file, | |
get uploadProgressPercent() { | |
return createMemo(() => !!uploadProgress())() ? createMemo(() => uploadProgress()?.fileIndex === index())() ? 20 : index() < (uploadProgress()?.fileIndex ?? 0) ? 100 : 0 : undefined; | |
}, | |
onRemoveClick: () => removeSelectedFile(index()) | |
}) | |
})); | |
return _el$3; | |
} | |
}), _el$4); | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!props.block.options?.isLong); | |
return () => _c$() ? createComponent(Textarea, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
onInput: handleInput, | |
onKeyDown: submitIfCtrlEnter, | |
get value() { | |
return inputValue(); | |
}, | |
get placeholder() { | |
return props.block.options?.labels?.placeholder ?? defaultTextInputOptions.labels.placeholder; | |
} | |
}) : createComponent(ShortTextInput, { | |
ref(r$) { | |
const _ref$2 = inputRef; | |
typeof _ref$2 === "function" ? _ref$2(r$) : inputRef = r$; | |
}, | |
onInput: handleInput, | |
get value() { | |
return inputValue(); | |
}, | |
get placeholder() { | |
return props.block.options?.labels?.placeholder ?? defaultTextInputOptions.labels.placeholder; | |
} | |
}); | |
})(), null); | |
insert(_el$4, createComponent(Show, { | |
get when() { | |
return (props.block.options?.attachments?.isEnabled ?? defaultTextInputOptions.attachments.isEnabled) && props.block.options?.attachments?.saveVariableId; | |
}, | |
get children() { | |
return createComponent(TextInputAddFileButton, { | |
onNewFiles: onNewFiles, | |
get ["class"]() { | |
return clsx$1(props.block.options?.isLong ? 'ml-2' : undefined); | |
} | |
}); | |
} | |
}), null); | |
insert(_el$, createComponent(SendButton, { | |
type: "button", | |
"on:click": submit, | |
get isDisabled() { | |
return Boolean(uploadProgress()); | |
}, | |
"class": "h-[56px]", | |
get children() { | |
return props.block.options?.labels?.button; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$ = clsx$1('typebot-input-form flex w-full gap-2 items-end', props.block.options?.isLong ? 'max-w-full' : 'max-w-[350px]'), | |
_v$2 = clsx$1('typebot-input flex-col w-full', isDraggingOver() && 'filter brightness-95'), | |
_v$3 = clsx$1('flex justify-between px-2', props.block.options?.isLong ? 'items-end' : 'items-center'); | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && className(_el$2, _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && className(_el$4, _p$._v$3 = _v$3); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["keydown"]); | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
const numberInputHelper = function (value) { | |
const bindDirective = function (el) { | |
createEffect(function () { | |
const v = value(); | |
if (v == null) { | |
el.value = v; | |
return; | |
} | |
const nodeV = el.value; | |
if (v === 0 && nodeV === '' || v != nodeV) { | |
el.value = v + ''; | |
} | |
}); | |
}; | |
const targetValue = function (el) { | |
if (el.validity.badInput) { | |
return value(); | |
} | |
if (el.value == '') { | |
return undefined; | |
} | |
return el.valueAsNumber; | |
}; | |
return [untrack(value), bindDirective, targetValue]; | |
}; | |
const defaultNumberInputOptions = { | |
labels: { | |
button: defaultButtonLabel, | |
placeholder: 'Type a number...' | |
} | |
}; | |
const _tmpl$$t = /*#__PURE__*/template(`<div class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"><div class="flex typebot-input w-full"><input class="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input" type="number">`); | |
const NumberInput = props => { | |
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? ''); | |
const [staticValue, bindValue, targetValue] = numberInputHelper(() => inputValue()); | |
let inputRef; | |
const checkIfInputIsValid = () => inputRef?.value !== '' && inputRef?.reportValidity(); | |
const submit = () => { | |
if (checkIfInputIsValid()) props.onSubmit({ | |
value: inputRef?.value ?? inputValue().toString() | |
});else inputRef?.focus(); | |
}; | |
const submitWhenEnter = e => { | |
if (e.key === 'Enter') submit(); | |
}; | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
window.addEventListener('message', processIncomingEvent); | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'setInputValue') setInputValue(data.value); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$t(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild; | |
_el$.$$keydown = submitWhenEnter; | |
_el$3.$$input = e => { | |
setInputValue(targetValue(e.currentTarget)); | |
}; | |
use(bindValue, _el$3, () => true); | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? use(_ref$, _el$3) : inputRef = _el$3; | |
_el$3.style.setProperty("font-size", "16px"); | |
_el$3.style.setProperty("appearance", "auto"); | |
_el$3.value = staticValue; | |
insert(_el$, createComponent(SendButton, { | |
type: "button", | |
"class": "h-[56px]", | |
"on:click": submit, | |
get children() { | |
return props.block.options?.labels?.button; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$ = props.block.options?.labels?.placeholder ?? defaultNumberInputOptions.labels.placeholder, | |
_v$2 = props.block.options?.min, | |
_v$3 = props.block.options?.max, | |
_v$4 = props.block.options?.step ?? 'any'; | |
_v$ !== _p$._v$ && setAttribute(_el$3, "placeholder", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$3, "min", _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && setAttribute(_el$3, "max", _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && setAttribute(_el$3, "step", _p$._v$4 = _v$4); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["keydown", "input"]); | |
const defaultEmailInputOptions = { | |
labels: { | |
button: defaultButtonLabel, | |
placeholder: 'Type your email...' | |
}, | |
retryMessageContent: "This email doesn't seem to be valid. Can you type it again?" | |
}; | |
const _tmpl$$s = /*#__PURE__*/template(`<div class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"><div class="flex typebot-input w-full">`); | |
const EmailInput = props => { | |
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? ''); | |
let inputRef; | |
const handleInput = inputValue => setInputValue(inputValue); | |
const checkIfInputIsValid = () => inputRef?.value !== '' && inputRef?.reportValidity(); | |
const submit = () => { | |
if (checkIfInputIsValid()) props.onSubmit({ | |
value: inputRef?.value ?? inputValue() | |
});else inputRef?.focus(); | |
}; | |
const submitWhenEnter = e => { | |
if (e.key === 'Enter') submit(); | |
}; | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
window.addEventListener('message', processIncomingEvent); | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'setInputValue') setInputValue(data.value); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$s(), | |
_el$2 = _el$.firstChild; | |
_el$.$$keydown = submitWhenEnter; | |
insert(_el$2, createComponent(ShortTextInput, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
get value() { | |
return inputValue(); | |
}, | |
get placeholder() { | |
return props.block.options?.labels?.placeholder ?? defaultEmailInputOptions.labels.placeholder; | |
}, | |
onInput: handleInput, | |
type: "email", | |
autocomplete: "email" | |
})); | |
insert(_el$, createComponent(SendButton, { | |
type: "button", | |
"class": "h-[56px]", | |
"on:click": submit, | |
get children() { | |
return props.block.options?.labels?.button; | |
} | |
}), null); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["keydown"]); | |
const defaultUrlInputOptions = { | |
labels: { | |
button: defaultButtonLabel, | |
placeholder: 'Type a URL...' | |
}, | |
retryMessageContent: "This URL doesn't seem to be valid. Can you type it again?" | |
}; | |
const _tmpl$$r = /*#__PURE__*/template(`<div class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"><div class="flex typebot-input w-full">`); | |
const UrlInput = props => { | |
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? ''); | |
let inputRef; | |
const handleInput = inputValue => { | |
setInputValue(inputValue); | |
}; | |
const checkIfInputIsValid = () => inputRef?.value !== '' && inputRef?.reportValidity(); | |
const submit = () => { | |
if (inputRef && !inputRef?.value.startsWith('http')) inputRef.value = `https://${inputRef.value}`; | |
if (checkIfInputIsValid()) props.onSubmit({ | |
value: inputRef?.value ?? inputValue() | |
});else inputRef?.focus(); | |
}; | |
const submitWhenEnter = e => { | |
if (e.key === 'Enter') submit(); | |
}; | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
window.addEventListener('message', processIncomingEvent); | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'setInputValue') setInputValue(data.value); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$r(), | |
_el$2 = _el$.firstChild; | |
_el$.$$keydown = submitWhenEnter; | |
insert(_el$2, createComponent(ShortTextInput, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
get value() { | |
return inputValue(); | |
}, | |
get placeholder() { | |
return props.block.options?.labels?.placeholder ?? defaultUrlInputOptions.labels.placeholder; | |
}, | |
onInput: handleInput, | |
type: "url", | |
autocomplete: "url" | |
})); | |
insert(_el$, createComponent(SendButton, { | |
type: "button", | |
"class": "h-[56px]", | |
"on:click": submit, | |
get children() { | |
return props.block.options?.labels?.button; | |
} | |
}), null); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["keydown"]); | |
const _tmpl$$q = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2px" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9">`); | |
const ChevronDownIcon = props => (() => { | |
const _el$ = _tmpl$$q(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
const phoneCountries = [{ | |
name: 'International', | |
flag: '🌐', | |
code: 'INT', | |
dial_code: null | |
}, { | |
name: 'Afghanistan', | |
flag: '🇦🇫', | |
code: 'AF', | |
dial_code: '+93' | |
}, { | |
name: 'Åland Islands', | |
flag: '🇦🇽', | |
code: 'AX', | |
dial_code: '+358' | |
}, { | |
name: 'Albania', | |
flag: '🇦🇱', | |
code: 'AL', | |
dial_code: '+355' | |
}, { | |
name: 'Algeria', | |
flag: '🇩🇿', | |
code: 'DZ', | |
dial_code: '+213' | |
}, { | |
name: 'American Samoa', | |
flag: '🇦🇸', | |
code: 'AS', | |
dial_code: '+1684' | |
}, { | |
name: 'Andorra', | |
flag: '🇦🇩', | |
code: 'AD', | |
dial_code: '+376' | |
}, { | |
name: 'Angola', | |
flag: '🇦🇴', | |
code: 'AO', | |
dial_code: '+244' | |
}, { | |
name: 'Anguilla', | |
flag: '🇦🇮', | |
code: 'AI', | |
dial_code: '+1264' | |
}, { | |
name: 'Antarctica', | |
flag: '🇦🇶', | |
code: 'AQ', | |
dial_code: '+672' | |
}, { | |
name: 'Antigua and Barbuda', | |
flag: '🇦🇬', | |
code: 'AG', | |
dial_code: '+1268' | |
}, { | |
name: 'Argentina', | |
flag: '🇦🇷', | |
code: 'AR', | |
dial_code: '+54' | |
}, { | |
name: 'Armenia', | |
flag: '🇦🇲', | |
code: 'AM', | |
dial_code: '+374' | |
}, { | |
name: 'Aruba', | |
flag: '🇦🇼', | |
code: 'AW', | |
dial_code: '+297' | |
}, { | |
name: 'Australia', | |
flag: '🇦🇺', | |
code: 'AU', | |
dial_code: '+61' | |
}, { | |
name: 'Austria', | |
flag: '🇦🇹', | |
code: 'AT', | |
dial_code: '+43' | |
}, { | |
name: 'Azerbaijan', | |
flag: '🇦🇿', | |
code: 'AZ', | |
dial_code: '+994' | |
}, { | |
name: 'Bahamas', | |
flag: '🇧🇸', | |
code: 'BS', | |
dial_code: '+1242' | |
}, { | |
name: 'Bahrain', | |
flag: '🇧🇭', | |
code: 'BH', | |
dial_code: '+973' | |
}, { | |
name: 'Bangladesh', | |
flag: '🇧🇩', | |
code: 'BD', | |
dial_code: '+880' | |
}, { | |
name: 'Barbados', | |
flag: '🇧🇧', | |
code: 'BB', | |
dial_code: '+1246' | |
}, { | |
name: 'Belarus', | |
flag: '🇧🇾', | |
code: 'BY', | |
dial_code: '+375' | |
}, { | |
name: 'Belgium', | |
flag: '🇧🇪', | |
code: 'BE', | |
dial_code: '+32' | |
}, { | |
name: 'Belize', | |
flag: '🇧🇿', | |
code: 'BZ', | |
dial_code: '+501' | |
}, { | |
name: 'Benin', | |
flag: '🇧🇯', | |
code: 'BJ', | |
dial_code: '+229' | |
}, { | |
name: 'Bermuda', | |
flag: '🇧🇲', | |
code: 'BM', | |
dial_code: '+1441' | |
}, { | |
name: 'Bhutan', | |
flag: '🇧🇹', | |
code: 'BT', | |
dial_code: '+975' | |
}, { | |
name: 'Bolivia, Plurinational State of bolivia', | |
flag: '🇧🇴', | |
code: 'BO', | |
dial_code: '+591' | |
}, { | |
name: 'Bosnia and Herzegovina', | |
flag: '🇧🇦', | |
code: 'BA', | |
dial_code: '+387' | |
}, { | |
name: 'Botswana', | |
flag: '🇧🇼', | |
code: 'BW', | |
dial_code: '+267' | |
}, { | |
name: 'Bouvet Island', | |
flag: '🇧🇻', | |
code: 'BV', | |
dial_code: '+47' | |
}, { | |
name: 'Brazil', | |
flag: '🇧🇷', | |
code: 'BR', | |
dial_code: '+55' | |
}, { | |
name: 'British Indian Ocean Territory', | |
flag: '🇮🇴', | |
code: 'IO', | |
dial_code: '+246' | |
}, { | |
name: 'Brunei Darussalam', | |
flag: '🇧🇳', | |
code: 'BN', | |
dial_code: '+673' | |
}, { | |
name: 'Bulgaria', | |
flag: '🇧🇬', | |
code: 'BG', | |
dial_code: '+359' | |
}, { | |
name: 'Burkina Faso', | |
flag: '🇧🇫', | |
code: 'BF', | |
dial_code: '+226' | |
}, { | |
name: 'Burundi', | |
flag: '🇧🇮', | |
code: 'BI', | |
dial_code: '+257' | |
}, { | |
name: 'Cambodia', | |
flag: '🇰🇭', | |
code: 'KH', | |
dial_code: '+855' | |
}, { | |
name: 'Cameroon', | |
flag: '🇨🇲', | |
code: 'CM', | |
dial_code: '+237' | |
}, { | |
name: 'Canada', | |
flag: '🇨🇦', | |
code: 'CA', | |
dial_code: '+1' | |
}, { | |
name: 'Cape Verde', | |
flag: '🇨🇻', | |
code: 'CV', | |
dial_code: '+238' | |
}, { | |
name: 'Cayman Islands', | |
flag: '🇰🇾', | |
code: 'KY', | |
dial_code: '+345' | |
}, { | |
name: 'Central African Republic', | |
flag: '🇨🇫', | |
code: 'CF', | |
dial_code: '+236' | |
}, { | |
name: 'Chad', | |
flag: '🇹🇩', | |
code: 'TD', | |
dial_code: '+235' | |
}, { | |
name: 'Chile', | |
flag: '🇨🇱', | |
code: 'CL', | |
dial_code: '+56' | |
}, { | |
name: 'China', | |
flag: '🇨🇳', | |
code: 'CN', | |
dial_code: '+86' | |
}, { | |
name: 'Christmas Island', | |
flag: '🇨🇽', | |
code: 'CX', | |
dial_code: '+61' | |
}, { | |
name: 'Cocos (Keeling) Islands', | |
flag: '🇨🇨', | |
code: 'CC', | |
dial_code: '+61' | |
}, { | |
name: 'Colombia', | |
flag: '🇨🇴', | |
code: 'CO', | |
dial_code: '+57' | |
}, { | |
name: 'Comoros', | |
flag: '🇰🇲', | |
code: 'KM', | |
dial_code: '+269' | |
}, { | |
name: 'Congo', | |
flag: '🇨🇬', | |
code: 'CG', | |
dial_code: '+242' | |
}, { | |
name: 'Congo, The Democratic Republic of the Congo', | |
flag: '🇨🇩', | |
code: 'CD', | |
dial_code: '+243' | |
}, { | |
name: 'Cook Islands', | |
flag: '🇨🇰', | |
code: 'CK', | |
dial_code: '+682' | |
}, { | |
name: 'Costa Rica', | |
flag: '🇨🇷', | |
code: 'CR', | |
dial_code: '+506' | |
}, { | |
name: "Cote d'Ivoire", | |
flag: '🇨🇮', | |
code: 'CI', | |
dial_code: '+225' | |
}, { | |
name: 'Croatia', | |
flag: '🇭🇷', | |
code: 'HR', | |
dial_code: '+385' | |
}, { | |
name: 'Cuba', | |
flag: '🇨🇺', | |
code: 'CU', | |
dial_code: '+53' | |
}, { | |
name: 'Cyprus', | |
flag: '🇨🇾', | |
code: 'CY', | |
dial_code: '+357' | |
}, { | |
name: 'Czech Republic', | |
flag: '🇨🇿', | |
code: 'CZ', | |
dial_code: '+420' | |
}, { | |
name: 'Denmark', | |
flag: '🇩🇰', | |
code: 'DK', | |
dial_code: '+45' | |
}, { | |
name: 'Djibouti', | |
flag: '🇩🇯', | |
code: 'DJ', | |
dial_code: '+253' | |
}, { | |
name: 'Dominica', | |
flag: '🇩🇲', | |
code: 'DM', | |
dial_code: '+1767' | |
}, { | |
name: 'Dominican Republic', | |
flag: '🇩🇴', | |
code: 'DO', | |
dial_code: '+1849' | |
}, { | |
name: 'Dominican Republic', | |
flag: '🇩🇴', | |
code: 'DO', | |
dial_code: '+1829' | |
}, { | |
name: 'Dominican Republic', | |
flag: '🇩🇴', | |
code: 'DO', | |
dial_code: '+1809' | |
}, { | |
name: 'Ecuador', | |
flag: '🇪🇨', | |
code: 'EC', | |
dial_code: '+593' | |
}, { | |
name: 'Egypt', | |
flag: '🇪🇬', | |
code: 'EG', | |
dial_code: '+20' | |
}, { | |
name: 'El Salvador', | |
flag: '🇸🇻', | |
code: 'SV', | |
dial_code: '+503' | |
}, { | |
name: 'Equatorial Guinea', | |
flag: '🇬🇶', | |
code: 'GQ', | |
dial_code: '+240' | |
}, { | |
name: 'Eritrea', | |
flag: '🇪🇷', | |
code: 'ER', | |
dial_code: '+291' | |
}, { | |
name: 'Estonia', | |
flag: '🇪🇪', | |
code: 'EE', | |
dial_code: '+372' | |
}, { | |
name: 'Ethiopia', | |
flag: '🇪🇹', | |
code: 'ET', | |
dial_code: '+251' | |
}, { | |
name: 'Falkland Islands (Malvinas)', | |
flag: '🇫🇰', | |
code: 'FK', | |
dial_code: '+500' | |
}, { | |
name: 'Faroe Islands', | |
flag: '🇫🇴', | |
code: 'FO', | |
dial_code: '+298' | |
}, { | |
name: 'Fiji', | |
flag: '🇫🇯', | |
code: 'FJ', | |
dial_code: '+679' | |
}, { | |
name: 'Finland', | |
flag: '🇫🇮', | |
code: 'FI', | |
dial_code: '+358' | |
}, { | |
name: 'France', | |
flag: '🇫🇷', | |
code: 'FR', | |
dial_code: '+33' | |
}, { | |
name: 'French Guiana', | |
flag: '🇬🇫', | |
code: 'GF', | |
dial_code: '+594' | |
}, { | |
name: 'French Polynesia', | |
flag: '🇵🇫', | |
code: 'PF', | |
dial_code: '+689' | |
}, { | |
name: 'French Southern Territories', | |
flag: '🇹🇫', | |
code: 'TF', | |
dial_code: '+262' | |
}, { | |
name: 'Gabon', | |
flag: '🇬🇦', | |
code: 'GA', | |
dial_code: '+241' | |
}, { | |
name: 'Gambia', | |
flag: '🇬🇲', | |
code: 'GM', | |
dial_code: '+220' | |
}, { | |
name: 'Georgia', | |
flag: '🇬🇪', | |
code: 'GE', | |
dial_code: '+995' | |
}, { | |
name: 'Germany', | |
flag: '🇩🇪', | |
code: 'DE', | |
dial_code: '+49' | |
}, { | |
name: 'Ghana', | |
flag: '🇬🇭', | |
code: 'GH', | |
dial_code: '+233' | |
}, { | |
name: 'Gibraltar', | |
flag: '🇬🇮', | |
code: 'GI', | |
dial_code: '+350' | |
}, { | |
name: 'Greece', | |
flag: '🇬🇷', | |
code: 'GR', | |
dial_code: '+30' | |
}, { | |
name: 'Greenland', | |
flag: '🇬🇱', | |
code: 'GL', | |
dial_code: '+299' | |
}, { | |
name: 'Grenada', | |
flag: '🇬🇩', | |
code: 'GD', | |
dial_code: '+1473' | |
}, { | |
name: 'Guadeloupe', | |
flag: '🇬🇵', | |
code: 'GP', | |
dial_code: '+590' | |
}, { | |
name: 'Guam', | |
flag: '🇬🇺', | |
code: 'GU', | |
dial_code: '+1671' | |
}, { | |
name: 'Guatemala', | |
flag: '🇬🇹', | |
code: 'GT', | |
dial_code: '+502' | |
}, { | |
name: 'Guernsey', | |
flag: '🇬🇬', | |
code: 'GG', | |
dial_code: '+44' | |
}, { | |
name: 'Guinea', | |
flag: '🇬🇳', | |
code: 'GN', | |
dial_code: '+224' | |
}, { | |
name: 'Guinea-Bissau', | |
flag: '🇬🇼', | |
code: 'GW', | |
dial_code: '+245' | |
}, { | |
name: 'Guyana', | |
flag: '🇬🇾', | |
code: 'GY', | |
dial_code: '+592' | |
}, { | |
name: 'Haiti', | |
flag: '🇭🇹', | |
code: 'HT', | |
dial_code: '+509' | |
}, { | |
name: 'Heard Island and Mcdonald Islands', | |
flag: '🇭🇲', | |
code: 'HM', | |
dial_code: '+672' | |
}, { | |
name: 'Holy See (Vatican City State)', | |
flag: '🇻🇦', | |
code: 'VA', | |
dial_code: '+379' | |
}, { | |
name: 'Honduras', | |
flag: '🇭🇳', | |
code: 'HN', | |
dial_code: '+504' | |
}, { | |
name: 'Hong Kong', | |
flag: '🇭🇰', | |
code: 'HK', | |
dial_code: '+852' | |
}, { | |
name: 'Hungary', | |
flag: '🇭🇺', | |
code: 'HU', | |
dial_code: '+36' | |
}, { | |
name: 'Iceland', | |
flag: '🇮🇸', | |
code: 'IS', | |
dial_code: '+354' | |
}, { | |
name: 'India', | |
flag: '🇮🇳', | |
code: 'IN', | |
dial_code: '+91' | |
}, { | |
name: 'Indonesia', | |
flag: '🇮🇩', | |
code: 'ID', | |
dial_code: '+62' | |
}, { | |
name: 'Iran, Islamic Republic of Persian Gulf', | |
flag: '🇮🇷', | |
code: 'IR', | |
dial_code: '+98' | |
}, { | |
name: 'Iraq', | |
flag: '🇮🇶', | |
code: 'IQ', | |
dial_code: '+964' | |
}, { | |
name: 'Ireland', | |
flag: '🇮🇪', | |
code: 'IE', | |
dial_code: '+353' | |
}, { | |
name: 'Isle of Man', | |
flag: '🇮🇲', | |
code: 'IM', | |
dial_code: '+44' | |
}, { | |
name: 'Israel', | |
flag: '🇮🇱', | |
code: 'IL', | |
dial_code: '+972' | |
}, { | |
name: 'Italy', | |
flag: '🇮🇹', | |
code: 'IT', | |
dial_code: '+39' | |
}, { | |
name: 'Jamaica', | |
flag: '🇯🇲', | |
code: 'JM', | |
dial_code: '+1876' | |
}, { | |
name: 'Japan', | |
flag: '🇯🇵', | |
code: 'JP', | |
dial_code: '+81' | |
}, { | |
name: 'Jersey', | |
flag: '🇯🇪', | |
code: 'JE', | |
dial_code: '+44' | |
}, { | |
name: 'Jordan', | |
flag: '🇯🇴', | |
code: 'JO', | |
dial_code: '+962' | |
}, { | |
name: 'Kazakhstan', | |
flag: '🇰🇿', | |
code: 'KZ', | |
dial_code: '+7' | |
}, { | |
name: 'Kenya', | |
flag: '🇰🇪', | |
code: 'KE', | |
dial_code: '+254' | |
}, { | |
name: 'Kiribati', | |
flag: '🇰🇮', | |
code: 'KI', | |
dial_code: '+686' | |
}, { | |
name: "Korea, Democratic People's Republic of Korea", | |
flag: '🇰🇵', | |
code: 'KP', | |
dial_code: '+850' | |
}, { | |
name: 'Korea, Republic of South Korea', | |
flag: '🇰🇷', | |
code: 'KR', | |
dial_code: '+82' | |
}, { | |
name: 'Kosovo', | |
flag: '🇽🇰', | |
code: 'XK', | |
dial_code: '+383' | |
}, { | |
name: 'Kuwait', | |
flag: '🇰🇼', | |
code: 'KW', | |
dial_code: '+965' | |
}, { | |
name: 'Kyrgyzstan', | |
flag: '🇰🇬', | |
code: 'KG', | |
dial_code: '+996' | |
}, { | |
name: 'Laos', | |
flag: '🇱🇦', | |
code: 'LA', | |
dial_code: '+856' | |
}, { | |
name: 'Latvia', | |
flag: '🇱🇻', | |
code: 'LV', | |
dial_code: '+371' | |
}, { | |
name: 'Lebanon', | |
flag: '🇱🇧', | |
code: 'LB', | |
dial_code: '+961' | |
}, { | |
name: 'Lesotho', | |
flag: '🇱🇸', | |
code: 'LS', | |
dial_code: '+266' | |
}, { | |
name: 'Liberia', | |
flag: '🇱🇷', | |
code: 'LR', | |
dial_code: '+231' | |
}, { | |
name: 'Libyan Arab Jamahiriya', | |
flag: '🇱🇾', | |
code: 'LY', | |
dial_code: '+218' | |
}, { | |
name: 'Liechtenstein', | |
flag: '🇱🇮', | |
code: 'LI', | |
dial_code: '+423' | |
}, { | |
name: 'Lithuania', | |
flag: '🇱🇹', | |
code: 'LT', | |
dial_code: '+370' | |
}, { | |
name: 'Luxembourg', | |
flag: '🇱🇺', | |
code: 'LU', | |
dial_code: '+352' | |
}, { | |
name: 'Macao', | |
flag: '🇲🇴', | |
code: 'MO', | |
dial_code: '+853' | |
}, { | |
name: 'Macedonia', | |
flag: '🇲🇰', | |
code: 'MK', | |
dial_code: '+389' | |
}, { | |
name: 'Madagascar', | |
flag: '🇲🇬', | |
code: 'MG', | |
dial_code: '+261' | |
}, { | |
name: 'Malawi', | |
flag: '🇲🇼', | |
code: 'MW', | |
dial_code: '+265' | |
}, { | |
name: 'Malaysia', | |
flag: '🇲🇾', | |
code: 'MY', | |
dial_code: '+60' | |
}, { | |
name: 'Maldives', | |
flag: '🇲🇻', | |
code: 'MV', | |
dial_code: '+960' | |
}, { | |
name: 'Mali', | |
flag: '🇲🇱', | |
code: 'ML', | |
dial_code: '+223' | |
}, { | |
name: 'Malta', | |
flag: '🇲🇹', | |
code: 'MT', | |
dial_code: '+356' | |
}, { | |
name: 'Marshall Islands', | |
flag: '🇲🇭', | |
code: 'MH', | |
dial_code: '+692' | |
}, { | |
name: 'Martinique', | |
flag: '🇲🇶', | |
code: 'MQ', | |
dial_code: '+596' | |
}, { | |
name: 'Mauritania', | |
flag: '🇲🇷', | |
code: 'MR', | |
dial_code: '+222' | |
}, { | |
name: 'Mauritius', | |
flag: '🇲🇺', | |
code: 'MU', | |
dial_code: '+230' | |
}, { | |
name: 'Mayotte', | |
flag: '🇾🇹', | |
code: 'YT', | |
dial_code: '+262' | |
}, { | |
name: 'Mexico', | |
flag: '🇲🇽', | |
code: 'MX', | |
dial_code: '+52' | |
}, { | |
name: 'Micronesia, Federated States of Micronesia', | |
flag: '🇫🇲', | |
code: 'FM', | |
dial_code: '+691' | |
}, { | |
name: 'Moldova', | |
flag: '🇲🇩', | |
code: 'MD', | |
dial_code: '+373' | |
}, { | |
name: 'Monaco', | |
flag: '🇲🇨', | |
code: 'MC', | |
dial_code: '+377' | |
}, { | |
name: 'Mongolia', | |
flag: '🇲🇳', | |
code: 'MN', | |
dial_code: '+976' | |
}, { | |
name: 'Montenegro', | |
flag: '🇲🇪', | |
code: 'ME', | |
dial_code: '+382' | |
}, { | |
name: 'Montserrat', | |
flag: '🇲🇸', | |
code: 'MS', | |
dial_code: '+1664' | |
}, { | |
name: 'Morocco', | |
flag: '🇲🇦', | |
code: 'MA', | |
dial_code: '+212' | |
}, { | |
name: 'Mozambique', | |
flag: '🇲🇿', | |
code: 'MZ', | |
dial_code: '+258' | |
}, { | |
name: 'Myanmar', | |
flag: '🇲🇲', | |
code: 'MM', | |
dial_code: '+95' | |
}, { | |
name: 'Namibia', | |
flag: '🇳🇦', | |
code: 'NA', | |
dial_code: '+264' | |
}, { | |
name: 'Nauru', | |
flag: '🇳🇷', | |
code: 'NR', | |
dial_code: '+674' | |
}, { | |
name: 'Nepal', | |
flag: '🇳🇵', | |
code: 'NP', | |
dial_code: '+977' | |
}, { | |
name: 'Netherlands', | |
flag: '🇳🇱', | |
code: 'NL', | |
dial_code: '+31' | |
}, { | |
name: 'Netherlands Antilles', | |
flag: '', | |
code: 'AN', | |
dial_code: '+599' | |
}, { | |
name: 'New Caledonia', | |
flag: '🇳🇨', | |
code: 'NC', | |
dial_code: '+687' | |
}, { | |
name: 'New Zealand', | |
flag: '🇳🇿', | |
code: 'NZ', | |
dial_code: '+64' | |
}, { | |
name: 'Nicaragua', | |
flag: '🇳🇮', | |
code: 'NI', | |
dial_code: '+505' | |
}, { | |
name: 'Niger', | |
flag: '🇳🇪', | |
code: 'NE', | |
dial_code: '+227' | |
}, { | |
name: 'Nigeria', | |
flag: '🇳🇬', | |
code: 'NG', | |
dial_code: '+234' | |
}, { | |
name: 'Niue', | |
flag: '🇳🇺', | |
code: 'NU', | |
dial_code: '+683' | |
}, { | |
name: 'Norfolk Island', | |
flag: '🇳🇫', | |
code: 'NF', | |
dial_code: '+672' | |
}, { | |
name: 'Northern Mariana Islands', | |
flag: '🇲🇵', | |
code: 'MP', | |
dial_code: '+1670' | |
}, { | |
name: 'Norway', | |
flag: '🇳🇴', | |
code: 'NO', | |
dial_code: '+47' | |
}, { | |
name: 'Oman', | |
flag: '🇴🇲', | |
code: 'OM', | |
dial_code: '+968' | |
}, { | |
name: 'Pakistan', | |
flag: '🇵🇰', | |
code: 'PK', | |
dial_code: '+92' | |
}, { | |
name: 'Palau', | |
flag: '🇵🇼', | |
code: 'PW', | |
dial_code: '+680' | |
}, { | |
name: 'Palestinian Territory, Occupied', | |
flag: '🇵🇸', | |
code: 'PS', | |
dial_code: '+970' | |
}, { | |
name: 'Panama', | |
flag: '🇵🇦', | |
code: 'PA', | |
dial_code: '+507' | |
}, { | |
name: 'Papua New Guinea', | |
flag: '🇵🇬', | |
code: 'PG', | |
dial_code: '+675' | |
}, { | |
name: 'Paraguay', | |
flag: '🇵🇾', | |
code: 'PY', | |
dial_code: '+595' | |
}, { | |
name: 'Peru', | |
flag: '🇵🇪', | |
code: 'PE', | |
dial_code: '+51' | |
}, { | |
name: 'Philippines', | |
flag: '🇵🇭', | |
code: 'PH', | |
dial_code: '+63' | |
}, { | |
name: 'Pitcairn', | |
flag: '🇵🇳', | |
code: 'PN', | |
dial_code: '+64' | |
}, { | |
name: 'Poland', | |
flag: '🇵🇱', | |
code: 'PL', | |
dial_code: '+48' | |
}, { | |
name: 'Portugal', | |
flag: '🇵🇹', | |
code: 'PT', | |
dial_code: '+351' | |
}, { | |
name: 'Puerto Rico', | |
flag: '🇵🇷', | |
code: 'PR', | |
dial_code: '+1939' | |
}, { | |
name: 'Qatar', | |
flag: '🇶🇦', | |
code: 'QA', | |
dial_code: '+974' | |
}, { | |
name: 'Romania', | |
flag: '🇷🇴', | |
code: 'RO', | |
dial_code: '+40' | |
}, { | |
name: 'Russia', | |
flag: '🇷🇺', | |
code: 'RU', | |
dial_code: '+7' | |
}, { | |
name: 'Rwanda', | |
flag: '🇷🇼', | |
code: 'RW', | |
dial_code: '+250' | |
}, { | |
name: 'Reunion', | |
flag: '🇷🇪', | |
code: 'RE', | |
dial_code: '+262' | |
}, { | |
name: 'Saint Barthelemy', | |
flag: '🇧🇱', | |
code: 'BL', | |
dial_code: '+590' | |
}, { | |
name: 'Saint Helena, Ascension and Tristan Da Cunha', | |
flag: '🇸🇭', | |
code: 'SH', | |
dial_code: '+290' | |
}, { | |
name: 'Saint Kitts and Nevis', | |
flag: '🇰🇳', | |
code: 'KN', | |
dial_code: '+1869' | |
}, { | |
name: 'Saint Lucia', | |
flag: '🇱🇨', | |
code: 'LC', | |
dial_code: '+1758' | |
}, { | |
name: 'Saint Martin', | |
flag: '🇲🇫', | |
code: 'MF', | |
dial_code: '+590' | |
}, { | |
name: 'Saint Pierre and Miquelon', | |
flag: '🇵🇲', | |
code: 'PM', | |
dial_code: '+508' | |
}, { | |
name: 'Saint Vincent and the Grenadines', | |
flag: '🇻🇨', | |
code: 'VC', | |
dial_code: '+1784' | |
}, { | |
name: 'Samoa', | |
flag: '🇼🇸', | |
code: 'WS', | |
dial_code: '+685' | |
}, { | |
name: 'San Marino', | |
flag: '🇸🇲', | |
code: 'SM', | |
dial_code: '+378' | |
}, { | |
name: 'Sao Tome and Principe', | |
flag: '🇸🇹', | |
code: 'ST', | |
dial_code: '+239' | |
}, { | |
name: 'Saudi Arabia', | |
flag: '🇸🇦', | |
code: 'SA', | |
dial_code: '+966' | |
}, { | |
name: 'Senegal', | |
flag: '🇸🇳', | |
code: 'SN', | |
dial_code: '+221' | |
}, { | |
name: 'Serbia', | |
flag: '🇷🇸', | |
code: 'RS', | |
dial_code: '+381' | |
}, { | |
name: 'Seychelles', | |
flag: '🇸🇨', | |
code: 'SC', | |
dial_code: '+248' | |
}, { | |
name: 'Sierra Leone', | |
flag: '🇸🇱', | |
code: 'SL', | |
dial_code: '+232' | |
}, { | |
name: 'Singapore', | |
flag: '🇸🇬', | |
code: 'SG', | |
dial_code: '+65' | |
}, { | |
name: 'Slovakia', | |
flag: '🇸🇰', | |
code: 'SK', | |
dial_code: '+421' | |
}, { | |
name: 'Slovenia', | |
flag: '🇸🇮', | |
code: 'SI', | |
dial_code: '+386' | |
}, { | |
name: 'Solomon Islands', | |
flag: '🇸🇧', | |
code: 'SB', | |
dial_code: '+677' | |
}, { | |
name: 'Somalia', | |
flag: '🇸🇴', | |
code: 'SO', | |
dial_code: '+252' | |
}, { | |
name: 'South Africa', | |
flag: '🇿🇦', | |
code: 'ZA', | |
dial_code: '+27' | |
}, { | |
name: 'South Sudan', | |
flag: '🇸🇸', | |
code: 'SS', | |
dial_code: '+211' | |
}, { | |
name: 'South Georgia and the South Sandwich Islands', | |
flag: '🇬🇸', | |
code: 'GS', | |
dial_code: '+500' | |
}, { | |
name: 'Spain', | |
flag: '🇪🇸', | |
code: 'ES', | |
dial_code: '+34' | |
}, { | |
name: 'Sri Lanka', | |
flag: '🇱🇰', | |
code: 'LK', | |
dial_code: '+94' | |
}, { | |
name: 'Sudan', | |
flag: '🇸🇩', | |
code: 'SD', | |
dial_code: '+249' | |
}, { | |
name: 'Suriname', | |
flag: '🇸🇷', | |
code: 'SR', | |
dial_code: '+597' | |
}, { | |
name: 'Svalbard and Jan Mayen', | |
flag: '🇸🇯', | |
code: 'SJ', | |
dial_code: '+47' | |
}, { | |
name: 'Swaziland', | |
flag: '🇸🇿', | |
code: 'SZ', | |
dial_code: '+268' | |
}, { | |
name: 'Sweden', | |
flag: '🇸🇪', | |
code: 'SE', | |
dial_code: '+46' | |
}, { | |
name: 'Switzerland', | |
flag: '🇨🇭', | |
code: 'CH', | |
dial_code: '+41' | |
}, { | |
name: 'Syrian Arab Republic', | |
flag: '🇸🇾', | |
code: 'SY', | |
dial_code: '+963' | |
}, { | |
name: 'Taiwan', | |
flag: '🇹🇼', | |
code: 'TW', | |
dial_code: '+886' | |
}, { | |
name: 'Tajikistan', | |
flag: '🇹🇯', | |
code: 'TJ', | |
dial_code: '+992' | |
}, { | |
name: 'Tanzania, United Republic of Tanzania', | |
flag: '🇹🇿', | |
code: 'TZ', | |
dial_code: '+255' | |
}, { | |
name: 'Thailand', | |
flag: '🇹🇭', | |
code: 'TH', | |
dial_code: '+66' | |
}, { | |
name: 'Timor-Leste', | |
flag: '🇹🇱', | |
code: 'TL', | |
dial_code: '+670' | |
}, { | |
name: 'Togo', | |
flag: '🇹🇬', | |
code: 'TG', | |
dial_code: '+228' | |
}, { | |
name: 'Tokelau', | |
flag: '🇹🇰', | |
code: 'TK', | |
dial_code: '+690' | |
}, { | |
name: 'Tonga', | |
flag: '🇹🇴', | |
code: 'TO', | |
dial_code: '+676' | |
}, { | |
name: 'Trinidad and Tobago', | |
flag: '🇹🇹', | |
code: 'TT', | |
dial_code: '+1868' | |
}, { | |
name: 'Tunisia', | |
flag: '🇹🇳', | |
code: 'TN', | |
dial_code: '+216' | |
}, { | |
name: 'Turkey', | |
flag: '🇹🇷', | |
code: 'TR', | |
dial_code: '+90' | |
}, { | |
name: 'Turkmenistan', | |
flag: '🇹🇲', | |
code: 'TM', | |
dial_code: '+993' | |
}, { | |
name: 'Turks and Caicos Islands', | |
flag: '🇹🇨', | |
code: 'TC', | |
dial_code: '+1649' | |
}, { | |
name: 'Tuvalu', | |
flag: '🇹🇻', | |
code: 'TV', | |
dial_code: '+688' | |
}, { | |
name: 'Uganda', | |
flag: '🇺🇬', | |
code: 'UG', | |
dial_code: '+256' | |
}, { | |
name: 'Ukraine', | |
flag: '🇺🇦', | |
code: 'UA', | |
dial_code: '+380' | |
}, { | |
name: 'United Arab Emirates', | |
flag: '🇦🇪', | |
code: 'AE', | |
dial_code: '+971' | |
}, { | |
name: 'United Kingdom', | |
flag: '🇬🇧', | |
code: 'GB', | |
dial_code: '+44' | |
}, { | |
name: 'United States', | |
flag: '🇺🇸', | |
code: 'US', | |
dial_code: '+1' | |
}, { | |
name: 'Uruguay', | |
flag: '🇺🇾', | |
code: 'UY', | |
dial_code: '+598' | |
}, { | |
name: 'Uzbekistan', | |
flag: '🇺🇿', | |
code: 'UZ', | |
dial_code: '+998' | |
}, { | |
name: 'Vanuatu', | |
flag: '🇻🇺', | |
code: 'VU', | |
dial_code: '+678' | |
}, { | |
name: 'Venezuela, Bolivarian Republic of Venezuela', | |
flag: '🇻🇪', | |
code: 'VE', | |
dial_code: '+58' | |
}, { | |
name: 'Vietnam', | |
flag: '🇻🇳', | |
code: 'VN', | |
dial_code: '+84' | |
}, { | |
name: 'Virgin Islands, British', | |
flag: '🇻🇬', | |
code: 'VG', | |
dial_code: '+1284' | |
}, { | |
name: 'Virgin Islands, U.S.', | |
flag: '🇻🇮', | |
code: 'VI', | |
dial_code: '+1340' | |
}, { | |
name: 'Wallis and Futuna', | |
flag: '🇼🇫', | |
code: 'WF', | |
dial_code: '+681' | |
}, { | |
name: 'Yemen', | |
flag: '🇾🇪', | |
code: 'YE', | |
dial_code: '+967' | |
}, { | |
name: 'Zambia', | |
flag: '🇿🇲', | |
code: 'ZM', | |
dial_code: '+260' | |
}, { | |
name: 'Zimbabwe', | |
flag: '🇿🇼', | |
code: 'ZW', | |
dial_code: '+263' | |
}]; | |
const defaultPhoneInputOptions = { | |
labels: { | |
button: defaultButtonLabel, | |
placeholder: 'Type your phone number...' | |
}, | |
retryMessageContent: "This phone number doesn't seem to be valid. Can you type it again?" | |
}; | |
const _tmpl$$p = /*#__PURE__*/template(`<div class="typebot-input-form flex w-full gap-2 items-end max-w-[350px]"><div class="flex typebot-input w-full"><div class="relative typebot-country-select flex justify-center items-center"><div class="pl-2 pr-1 flex items-center gap-2"><span></span></div><select class="absolute top-0 left-0 w-full h-full cursor-pointer opacity-0">`), | |
_tmpl$2$g = /*#__PURE__*/template(`<option> `); | |
const PhoneInput = props => { | |
const [selectedCountryCode, setSelectedCountryCode] = createSignal(isEmpty$2(props.defaultCountryCode) ? 'INT' : props.defaultCountryCode); | |
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? ''); | |
let inputRef; | |
const handleInput = inputValue => { | |
setInputValue(inputValue); | |
if ((inputValue === '' || inputValue === '+') && selectedCountryCode() !== 'INT') setSelectedCountryCode('INT'); | |
const matchedCountry = inputValue?.startsWith('+') && inputValue.length > 2 && phoneCountries.reduce((matchedCountry, country) => { | |
if (!country?.dial_code || matchedCountry !== null && !matchedCountry.dial_code) { | |
return matchedCountry; | |
} | |
if (inputValue?.startsWith(country.dial_code) && country.dial_code.length > (matchedCountry?.dial_code.length ?? 0)) { | |
return country; | |
} | |
return matchedCountry; | |
}, null); | |
if (matchedCountry) setSelectedCountryCode(matchedCountry.code); | |
}; | |
const checkIfInputIsValid = () => inputRef?.value !== '' && inputRef?.reportValidity(); | |
const submit = () => { | |
const selectedCountryDialCode = phoneCountries.find(country => country.code === selectedCountryCode())?.dial_code; | |
if (checkIfInputIsValid()) { | |
const val = inputRef?.value ?? inputValue(); | |
props.onSubmit({ | |
value: val.startsWith('+') ? val : `${selectedCountryDialCode ?? ''}${val}` | |
}); | |
} else inputRef?.focus(); | |
}; | |
const submitWhenEnter = e => { | |
if (e.key === 'Enter') submit(); | |
}; | |
const selectNewCountryCode = event => { | |
const code = event.currentTarget.value; | |
setSelectedCountryCode(code); | |
const dial_code = phoneCountries.find(country => country.code === code)?.dial_code; | |
if (inputValue() === '' && dial_code) setInputValue(dial_code); | |
inputRef?.focus(); | |
}; | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
window.addEventListener('message', processIncomingEvent); | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'setInputValue') setInputValue(data.value); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$p(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.firstChild, | |
_el$6 = _el$4.nextSibling; | |
_el$.$$keydown = submitWhenEnter; | |
insert(_el$5, () => phoneCountries.find(country => selectedCountryCode() === country.code)?.flag); | |
insert(_el$4, createComponent(ChevronDownIcon, { | |
"class": "w-3" | |
}), null); | |
_el$6.addEventListener("change", selectNewCountryCode); | |
insert(_el$6, createComponent(For, { | |
each: phoneCountries, | |
children: country => (() => { | |
const _el$7 = _tmpl$2$g(), | |
_el$8 = _el$7.firstChild; | |
insert(_el$7, () => country.name, _el$8); | |
insert(_el$7, () => country.dial_code ? `(${country.dial_code})` : '', null); | |
createRenderEffect(() => _el$7.selected = country.code === selectedCountryCode()); | |
createRenderEffect(() => _el$7.value = country.code); | |
return _el$7; | |
})() | |
})); | |
insert(_el$2, createComponent(ShortTextInput, { | |
type: "tel", | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
get value() { | |
return inputValue(); | |
}, | |
onInput: handleInput, | |
get placeholder() { | |
return props.labels?.placeholder ?? defaultPhoneInputOptions.labels.placeholder; | |
}, | |
get autofocus() { | |
return !isMobile(); | |
} | |
}), null); | |
insert(_el$, createComponent(SendButton, { | |
type: "button", | |
"class": "h-[56px]", | |
"on:click": submit, | |
get children() { | |
return props.labels?.button; | |
} | |
}), null); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["keydown"]); | |
const defaultDateInputOptions = { | |
hasTime: false, | |
isRange: false, | |
labels: { | |
button: defaultButtonLabel, | |
from: 'From:', | |
to: 'To:' | |
}, | |
format: 'dd/MM/yyyy', | |
formatWithTime: 'dd/MM/yyyy HH:mm' | |
}; | |
const _tmpl$$o = /*#__PURE__*/template(`<div class="typebot-input-form flex gap-2 items-end"><form><div class="flex flex-col"><div><input class="focus:outline-none flex-1 w-full text-input typebot-date-input" data-testid="from-date">`), | |
_tmpl$2$f = /*#__PURE__*/template(`<p class="font-semibold">`), | |
_tmpl$3$8 = /*#__PURE__*/template(`<div class="flex items-center p-4"><input class="focus:outline-none flex-1 w-full text-input ml-2 typebot-date-input" data-testid="to-date">`); | |
const DateForm = props => { | |
const [inputValues, setInputValues] = createSignal(parseDefaultValue(props.defaultValue ?? '')); | |
const submit = () => { | |
if (inputValues().from === '' && inputValues().to === '') return; | |
props.onSubmit({ | |
value: `${inputValues().from}${props.options?.isRange ? ` to ${inputValues().to}` : ''}` | |
}); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$o(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.firstChild; | |
_el$2.addEventListener("submit", e => { | |
e.preventDefault(); | |
submit(); | |
}); | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!props.options?.isRange); | |
return () => _c$() && (() => { | |
const _el$6 = _tmpl$2$f(); | |
insert(_el$6, () => props.options.labels?.from ?? defaultDateInputOptions.labels.from); | |
return _el$6; | |
})(); | |
})(), _el$5); | |
_el$5.addEventListener("change", e => setInputValues({ | |
...inputValues(), | |
from: e.currentTarget.value | |
})); | |
_el$5.style.setProperty("min-height", "32px"); | |
_el$5.style.setProperty("min-width", "100px"); | |
_el$5.style.setProperty("font-size", "16px"); | |
insert(_el$3, (() => { | |
const _c$2 = createMemo(() => !!props.options?.isRange); | |
return () => _c$2() && (() => { | |
const _el$7 = _tmpl$3$8(), | |
_el$8 = _el$7.firstChild; | |
insert(_el$7, (() => { | |
const _c$3 = createMemo(() => !!props.options.isRange); | |
return () => _c$3() && (() => { | |
const _el$9 = _tmpl$2$f(); | |
insert(_el$9, () => props.options.labels?.to ?? defaultDateInputOptions.labels.to); | |
return _el$9; | |
})(); | |
})(), _el$8); | |
_el$8.addEventListener("change", e => setInputValues({ | |
...inputValues(), | |
to: e.currentTarget.value | |
})); | |
_el$8.style.setProperty("min-height", "32px"); | |
_el$8.style.setProperty("min-width", "100px"); | |
_el$8.style.setProperty("font-size", "16px"); | |
createRenderEffect(_p$ => { | |
const _v$6 = props.options.hasTime ? 'datetime-local' : 'date', | |
_v$7 = props.options?.min, | |
_v$8 = props.options?.max; | |
_v$6 !== _p$._v$6 && setAttribute(_el$8, "type", _p$._v$6 = _v$6); | |
_v$7 !== _p$._v$7 && setAttribute(_el$8, "min", _p$._v$7 = _v$7); | |
_v$8 !== _p$._v$8 && setAttribute(_el$8, "max", _p$._v$8 = _v$8); | |
return _p$; | |
}, { | |
_v$6: undefined, | |
_v$7: undefined, | |
_v$8: undefined | |
}); | |
createRenderEffect(() => _el$8.value = inputValues().to); | |
return _el$7; | |
})(); | |
})(), null); | |
insert(_el$, createComponent(SendButton, { | |
"class": "h-[56px]", | |
"on:click": submit, | |
get children() { | |
return props.options?.labels?.button; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$ = clsx$1('flex typebot-input', props.options?.isRange ? 'items-end' : 'items-center'), | |
_v$2 = 'flex items-center p-4 ' + (props.options?.isRange ? 'pb-0 gap-2' : ''), | |
_v$3 = props.options?.hasTime ? 'datetime-local' : 'date', | |
_v$4 = props.options?.min, | |
_v$5 = props.options?.max; | |
_v$ !== _p$._v$ && className(_el$2, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && className(_el$4, _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && setAttribute(_el$5, "type", _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && setAttribute(_el$5, "min", _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && setAttribute(_el$5, "max", _p$._v$5 = _v$5); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined | |
}); | |
createRenderEffect(() => _el$5.value = inputValues().from); | |
return _el$; | |
})(); | |
}; | |
const parseDefaultValue = defaultValue => { | |
if (!defaultValue.includes('to')) return { | |
from: defaultValue, | |
to: '' | |
}; | |
const [from, to] = defaultValue.split(' to '); | |
return { | |
from, | |
to | |
}; | |
}; | |
const defaultRatingInputOptions = { | |
buttonType: 'Numbers', | |
length: 10, | |
labels: { | |
button: defaultButtonLabel | |
}, | |
startsAt: 0, | |
customIcon: { | |
isEnabled: false | |
}, | |
isOneClickSubmitEnabled: false | |
}; | |
const _tmpl$$n = /*#__PURE__*/template(`<form class="flex flex-col gap-2"><div class="flex flex-wrap justify-center gap-2"></div><div class="flex justify-end">`), | |
_tmpl$2$e = /*#__PURE__*/template(`<span class="text-sm w-full rating-label">`), | |
_tmpl$3$7 = /*#__PURE__*/template(`<span class="text-sm w-full text-right pr-2 rating-label">`), | |
_tmpl$4$4 = /*#__PURE__*/template(`<div role="checkbox">`), | |
_tmpl$5$2 = /*#__PURE__*/template(`<div>`); | |
const RatingForm = props => { | |
const [rating, setRating] = createSignal(props.defaultValue ? Number(props.defaultValue) : undefined); | |
const handleSubmit = e => { | |
e.preventDefault(); | |
const selectedRating = rating(); | |
if (isNotDefined(selectedRating)) return; | |
props.onSubmit({ | |
value: selectedRating.toString() | |
}); | |
}; | |
const handleClick = rating => { | |
if (props.block.options?.isOneClickSubmitEnabled) props.onSubmit({ | |
value: rating.toString() | |
}); | |
setRating(rating); | |
}; | |
return (() => { | |
const _el$ = _tmpl$$n(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.nextSibling; | |
_el$.addEventListener("submit", handleSubmit); | |
insert(_el$, (() => { | |
const _c$ = createMemo(() => !!props.block.options?.labels?.left); | |
return () => _c$() && (() => { | |
const _el$4 = _tmpl$2$e(); | |
insert(_el$4, () => props.block.options.labels.left); | |
return _el$4; | |
})(); | |
})(), _el$2); | |
insert(_el$2, createComponent(For, { | |
get each() { | |
return Array.from(Array((props.block.options?.length ?? defaultRatingInputOptions.length) + ((props.block.options?.buttonType ?? defaultRatingInputOptions.buttonType) === 'Numbers' ? -((props.block.options?.startsAt ?? defaultRatingInputOptions.startsAt) - 1) : 0))); | |
}, | |
children: (_, idx) => createComponent(RatingButton, mergeProps$2(() => props.block.options, { | |
get rating() { | |
return rating(); | |
}, | |
get idx() { | |
return idx() + ((props.block.options?.buttonType ?? defaultRatingInputOptions.buttonType) === 'Numbers' ? props.block.options?.startsAt ?? defaultRatingInputOptions.startsAt : 1); | |
}, | |
onClick: handleClick | |
})) | |
})); | |
insert(_el$, (() => { | |
const _c$2 = createMemo(() => !!props.block.options?.labels?.right); | |
return () => _c$2() && (() => { | |
const _el$5 = _tmpl$3$7(); | |
insert(_el$5, () => props.block.options.labels.right); | |
return _el$5; | |
})(); | |
})(), _el$3); | |
insert(_el$3, (() => { | |
const _c$3 = createMemo(() => !!isDefined(rating())); | |
return () => _c$3() && createComponent(SendButton, { | |
disableIcon: true, | |
get children() { | |
return props.block.options?.labels?.button ?? defaultRatingInputOptions.labels.button; | |
} | |
}); | |
})()); | |
return _el$; | |
})(); | |
}; | |
const RatingButton = props => { | |
const handleClick = e => { | |
e.preventDefault(); | |
props.onClick(props.idx); | |
}; | |
return createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return (props.buttonType ?? defaultRatingInputOptions.buttonType) === 'Numbers'; | |
}, | |
get children() { | |
return [createComponent(Show, { | |
get when() { | |
return props.isOneClickSubmitEnabled; | |
}, | |
get children() { | |
return createComponent(Button, { | |
"on:click": handleClick, | |
get children() { | |
return props.idx; | |
} | |
}); | |
} | |
}), createComponent(Show, { | |
get when() { | |
return !props.isOneClickSubmitEnabled; | |
}, | |
get children() { | |
const _el$6 = _tmpl$4$4(); | |
_el$6.addEventListener("click", handleClick); | |
insert(_el$6, () => props.idx); | |
createRenderEffect(_p$ => { | |
const _v$ = isDefined(props.rating) && props.idx <= props.rating, | |
_v$2 = 'py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable' + (isDefined(props.rating) && props.idx <= props.rating ? ' selected' : ''); | |
_v$ !== _p$._v$ && setAttribute(_el$6, "aria-checked", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && className(_el$6, _p$._v$2 = _v$2); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined | |
}); | |
return _el$6; | |
} | |
})]; | |
} | |
}), createComponent(Match, { | |
get when() { | |
return (props.buttonType ?? defaultRatingInputOptions.buttonType) !== 'Numbers'; | |
}, | |
get children() { | |
const _el$7 = _tmpl$5$2(); | |
_el$7.addEventListener("click", () => props.onClick(props.idx)); | |
createRenderEffect(_p$ => { | |
const _v$3 = 'flex justify-center items-center rating-icon-container cursor-pointer ' + (isDefined(props.rating) && props.idx <= props.rating ? 'selected' : ''), | |
_v$4 = props.customIcon?.isEnabled && !isEmpty$2(props.customIcon.svg) ? props.customIcon.svg : defaultIcon; | |
_v$3 !== _p$._v$3 && className(_el$7, _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && (_el$7.innerHTML = _p$._v$4 = _v$4); | |
return _p$; | |
}, { | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$7; | |
} | |
})]; | |
} | |
}); | |
}; | |
const defaultIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>`; | |
const defaultFileInputOptions = { | |
isRequired: true, | |
isMultipleAllowed: false, | |
visibility: 'Auto', | |
labels: { | |
placeholder: `<strong> | |
Click to upload | |
</strong> or drag and drop<br> | |
(size limit: 10MB)`, | |
button: 'Upload', | |
clear: 'Clear', | |
skip: 'Skip', | |
success: { | |
single: 'File uploaded', | |
multiple: '{total} files uploaded' | |
} | |
} | |
}; | |
const _tmpl$$m = /*#__PURE__*/template(`<div class="w-full bg-gray-200 rounded-full h-2.5"><div class="upload-progress-bar h-2.5 rounded-full">`), | |
_tmpl$2$d = /*#__PURE__*/template(`<div class="p-4 flex gap-2 border-gray-200 border overflow-auto bg-white rounded-md w-full">`), | |
_tmpl$3$6 = /*#__PURE__*/template(`<div class="flex flex-col justify-center items-center gap-4 max-w-[90%]"><p class="text-sm text-gray-500 text-center">`), | |
_tmpl$4$3 = /*#__PURE__*/template(`<input id="dropzone-file" type="file" class="hidden">`), | |
_tmpl$5$1 = /*#__PURE__*/template(`<div class="flex justify-end">`), | |
_tmpl$6$1 = /*#__PURE__*/template(`<div class="flex justify-end"><div class="flex gap-2">`), | |
_tmpl$7$1 = /*#__PURE__*/template(`<form class="flex flex-col w-full gap-2"><label for="dropzone-file">`), | |
_tmpl$8 = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-500"><polyline points="16 16 12 12 8 16"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline points="16 16 12 12 8 16">`); | |
const FileUploadForm = props => { | |
const [selectedFiles, setSelectedFiles] = createSignal([]); | |
const [isUploading, setIsUploading] = createSignal(false); | |
const [uploadProgressPercent, setUploadProgressPercent] = createSignal(0); | |
const [isDraggingOver, setIsDraggingOver] = createSignal(false); | |
const onNewFiles = files => { | |
const newFiles = Array.from(files).map(file => sanitizeNewFile({ | |
existingFiles: selectedFiles(), | |
newFile: file, | |
params: { | |
sizeLimit: props.block.options && 'sizeLimit' in props.block.options ? props.block.options.sizeLimit : undefined | |
}, | |
onError: ({ | |
description, | |
title | |
}) => toaster.create({ | |
title, | |
description | |
}) | |
})).filter(isDefined); | |
if (newFiles.length === 0) return; | |
if (!props.block.options?.isMultipleAllowed) return startSingleFileUpload(newFiles[0]); | |
setSelectedFiles([...selectedFiles(), ...newFiles]); | |
}; | |
const handleSubmit = async e => { | |
e.preventDefault(); | |
if (selectedFiles().length === 0) return; | |
startFilesUpload(selectedFiles()); | |
}; | |
const startSingleFileUpload = async file => { | |
setIsUploading(true); | |
const urls = await uploadFiles({ | |
apiHost: props.context.apiHost ?? guessApiHost({ | |
ignoreChatApiUrl: true | |
}), | |
files: [{ | |
file, | |
input: { | |
sessionId: props.context.sessionId, | |
fileName: file.name | |
} | |
}] | |
}); | |
setIsUploading(false); | |
if (urls.length && urls[0]) return props.onSubmit({ | |
label: props.block.options?.labels?.success?.single ?? defaultFileInputOptions.labels.success.single, | |
value: urls[0] ? encodeUrl(urls[0].url) : '', | |
attachments: [{ | |
type: file.type, | |
url: urls[0].url | |
}] | |
}); | |
toaster.create({ | |
description: 'An error occured while uploading the file' | |
}); | |
}; | |
const startFilesUpload = async files => { | |
setIsUploading(true); | |
const urls = await uploadFiles({ | |
apiHost: props.context.apiHost ?? guessApiHost({ | |
ignoreChatApiUrl: true | |
}), | |
files: files.map(file => ({ | |
file: file, | |
input: { | |
sessionId: props.context.sessionId, | |
fileName: file.name | |
} | |
})), | |
onUploadProgress: setUploadProgressPercent | |
}); | |
setIsUploading(false); | |
setUploadProgressPercent(0); | |
if (urls.length !== files.length) return toaster.create({ | |
description: 'An error occured while uploading the files' | |
}); | |
props.onSubmit({ | |
label: urls.length > 1 ? (props.block.options?.labels?.success?.multiple ?? defaultFileInputOptions.labels.success.multiple).replaceAll('{total}', urls.length.toString()) : props.block.options?.labels?.success?.single ?? defaultFileInputOptions.labels.success.single, | |
value: urls.filter(isDefined).map(({ | |
url | |
}) => encodeUrl(url)).join(', '), | |
attachments: urls.filter(isDefined) | |
}); | |
}; | |
const handleDragOver = e => { | |
e.preventDefault(); | |
setIsDraggingOver(true); | |
}; | |
const handleDragLeave = () => setIsDraggingOver(false); | |
const handleDropFile = e => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
if (!e.dataTransfer?.files) return; | |
onNewFiles(e.dataTransfer.files); | |
}; | |
const clearFiles = () => setSelectedFiles([]); | |
const skip = () => props.onSkip(props.block.options?.labels?.skip ?? defaultFileInputOptions.labels.skip); | |
const removeSelectedFile = index => { | |
setSelectedFiles(selectedFiles => selectedFiles.filter((_, i) => i !== index)); | |
}; | |
return (() => { | |
const _el$ = _tmpl$7$1(), | |
_el$2 = _el$.firstChild; | |
_el$.addEventListener("submit", handleSubmit); | |
_el$2.addEventListener("drop", handleDropFile); | |
_el$2.addEventListener("dragleave", handleDragLeave); | |
_el$2.addEventListener("dragover", handleDragOver); | |
insert(_el$2, createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return isUploading(); | |
}, | |
get children() { | |
return createComponent(Show, { | |
get when() { | |
return selectedFiles().length > 1; | |
}, | |
get fallback() { | |
return createComponent(Spinner, {}); | |
}, | |
get children() { | |
const _el$3 = _tmpl$$m(), | |
_el$4 = _el$3.firstChild; | |
_el$4.style.setProperty("transition", "width 150ms cubic-bezier(0.4, 0, 0.2, 1)"); | |
createRenderEffect(() => `${uploadProgressPercent() > 0 ? uploadProgressPercent : 10}%` != null ? _el$4.style.setProperty("width", `${uploadProgressPercent() > 0 ? uploadProgressPercent : 10}%`) : _el$4.style.removeProperty("width")); | |
return _el$3; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return !isUploading(); | |
}, | |
get children() { | |
return [(() => { | |
const _el$5 = _tmpl$3$6(), | |
_el$7 = _el$5.firstChild; | |
insert(_el$5, createComponent(Show, { | |
get when() { | |
return selectedFiles().length; | |
}, | |
get fallback() { | |
return createComponent(UploadIcon, {}); | |
}, | |
get children() { | |
const _el$6 = _tmpl$2$d(); | |
_el$6.addEventListener("click", e => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
}); | |
insert(_el$6, createComponent(For, { | |
get each() { | |
return selectedFiles(); | |
}, | |
children: (file, index) => createComponent(SelectedFile, { | |
file: file, | |
onRemoveClick: () => removeSelectedFile(index()) | |
}) | |
})); | |
return _el$6; | |
} | |
}), _el$7); | |
createRenderEffect(() => _el$7.innerHTML = props.block.options?.labels?.placeholder ?? defaultFileInputOptions.labels.placeholder); | |
return _el$5; | |
})(), (() => { | |
const _el$8 = _tmpl$4$3(); | |
_el$8.addEventListener("change", e => { | |
if (!e.currentTarget.files) return; | |
onNewFiles(e.currentTarget.files); | |
}); | |
createRenderEffect(() => _el$8.multiple = props.block.options?.isMultipleAllowed ?? defaultFileInputOptions.isMultipleAllowed); | |
return _el$8; | |
})()]; | |
} | |
})]; | |
} | |
})); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return selectedFiles().length === 0 && props.block.options?.isRequired === false; | |
}, | |
get children() { | |
const _el$9 = _tmpl$5$1(); | |
insert(_el$9, createComponent(Button, { | |
"on:click": skip, | |
get children() { | |
return props.block.options?.labels?.skip ?? defaultFileInputOptions.labels.skip; | |
} | |
})); | |
return _el$9; | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return createMemo(() => !!(props.block.options?.isMultipleAllowed && selectedFiles().length > 0))() && !isUploading(); | |
}, | |
get children() { | |
const _el$10 = _tmpl$6$1(), | |
_el$11 = _el$10.firstChild; | |
insert(_el$11, createComponent(Show, { | |
get when() { | |
return selectedFiles().length; | |
}, | |
get children() { | |
return createComponent(Button, { | |
variant: "secondary", | |
"on:click": clearFiles, | |
get children() { | |
return props.block.options?.labels?.clear ?? defaultFileInputOptions.labels.clear; | |
} | |
}); | |
} | |
}), null); | |
insert(_el$11, createComponent(SendButton, { | |
type: "submit", | |
disableIcon: true, | |
get children() { | |
return createMemo(() => (props.block.options?.labels?.button ?? defaultFileInputOptions.labels.button) === defaultFileInputOptions.labels.button)() ? `Upload ${selectedFiles().length} file${selectedFiles().length > 1 ? 's' : ''}` : props.block.options?.labels?.button; | |
} | |
}), null); | |
return _el$10; | |
} | |
}), null); | |
createRenderEffect(() => className(_el$2, 'typebot-upload-input py-6 flex flex-col justify-center items-center w-full bg-gray-50 border-2 border-gray-300 border-dashed cursor-pointer hover:bg-gray-100 px-8 ' + (isDraggingOver() ? 'dragging-over' : ''))); | |
return _el$; | |
})(); | |
}; | |
const UploadIcon = () => _tmpl$8(); | |
const encodeUrl = url => { | |
const fileName = url.split('/').pop(); | |
if (!fileName) return url; | |
const encodedFileName = encodeURIComponent(fileName); | |
return url.replace(fileName, encodedFileName); | |
}; | |
const loadStripe = publishableKey => new Promise(resolve => { | |
if (window.Stripe) return resolve(window.Stripe(publishableKey)); | |
const script = document.createElement('script'); | |
script.src = 'https://js.stripe.com/v3'; | |
document.body.appendChild(script); | |
script.onload = () => { | |
if (!window.Stripe) throw new Error('Stripe.js failed to load.'); | |
resolve(window.Stripe(publishableKey)); | |
}; | |
}); | |
let PaymentProvider = /*#__PURE__*/function (PaymentProvider) { | |
PaymentProvider["STRIPE"] = "Stripe"; | |
return PaymentProvider; | |
}({}); | |
const defaultPaymentInputOptions = { | |
provider: PaymentProvider.STRIPE, | |
labels: { | |
button: 'Pay', | |
success: 'Success' | |
}, | |
retryMessageContent: 'Payment failed. Please, try again.', | |
currency: 'USD' | |
}; | |
const _tmpl$$l = /*#__PURE__*/template(`<div class="typebot-input-error-message mt-4 text-center animate-fade-in">`), | |
_tmpl$2$c = /*#__PURE__*/template(`<form id="payment-form" class="flex flex-col p-4 typebot-input w-full items-center"><slot name="stripe-payment-form">`); | |
const slotName = 'stripe-payment-form'; | |
let paymentElementSlot; | |
let stripe = null; | |
let elements = null; | |
const StripePaymentForm = props => { | |
const [message, setMessage] = createSignal(); | |
const [isMounted, setIsMounted] = createSignal(false); | |
const [isLoading, setIsLoading] = createSignal(false); | |
onMount(async () => { | |
initShadowMountPoint(paymentElementSlot); | |
if (!props.options?.publicKey) return setMessage('Missing Stripe public key'); | |
stripe = await loadStripe(props.options?.publicKey); | |
if (!stripe) return; | |
elements = stripe.elements({ | |
appearance: { | |
theme: 'stripe', | |
variables: { | |
colorPrimary: getComputedStyle(paymentElementSlot).getPropertyValue('--typebot-button-bg-color') | |
} | |
}, | |
clientSecret: props.options.paymentIntentSecret | |
}); | |
const paymentElement = elements.create('payment', { | |
layout: 'tabs' | |
}); | |
paymentElement.mount('#payment-element'); | |
setTimeout(() => { | |
setIsMounted(true); | |
props.onTransitionEnd(); | |
}, 1000); | |
}); | |
const handleSubmit = async event => { | |
event.preventDefault(); | |
if (!stripe || !elements) return; | |
setIsLoading(true); | |
setPaymentInProgressInStorage({ | |
sessionId: props.context.sessionId, | |
resultId: props.context.resultId, | |
typebot: props.context.typebot | |
}); | |
const { | |
postalCode, | |
...address | |
} = props.options?.additionalInformation?.address ?? {}; | |
const { | |
error, | |
paymentIntent | |
} = await stripe.confirmPayment({ | |
elements, | |
confirmParams: { | |
return_url: window.location.href, | |
payment_method_data: { | |
billing_details: { | |
name: props.options?.additionalInformation?.name, | |
email: props.options?.additionalInformation?.email, | |
phone: props.options?.additionalInformation?.phoneNumber, | |
address: { | |
...address, | |
postal_code: postalCode | |
} | |
} | |
} | |
}, | |
redirect: 'if_required' | |
}); | |
removePaymentInProgressFromStorage(); | |
setIsLoading(false); | |
if (error?.type === 'validation_error') return; | |
if (error?.type === 'card_error') return setMessage(error.message); | |
if (!error && paymentIntent.status === 'succeeded') return props.onSuccess(); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$c(), | |
_el$2 = _el$.firstChild; | |
_el$.addEventListener("submit", handleSubmit); | |
const _ref$ = paymentElementSlot; | |
typeof _ref$ === "function" ? use(_ref$, _el$2) : paymentElementSlot = _el$2; | |
_el$2._$owner = getOwner(); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return isMounted(); | |
}, | |
get children() { | |
return createComponent(SendButton, { | |
get isLoading() { | |
return isLoading(); | |
}, | |
"class": "mt-4 w-full max-w-lg animate-fade-in", | |
disableIcon: true, | |
get children() { | |
return [createMemo(() => props.options?.labels?.button ?? defaultPaymentInputOptions.labels.button), ' ', createMemo(() => props.options?.amountLabel)]; | |
} | |
}); | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return message(); | |
}, | |
get children() { | |
const _el$3 = _tmpl$$l(); | |
insert(_el$3, message); | |
return _el$3; | |
} | |
}), null); | |
return _el$; | |
})(); | |
}; | |
const initShadowMountPoint = element => { | |
const rootNode = element.getRootNode(); | |
const host = rootNode.host; | |
const slotPlaceholder = document.createElement('div'); | |
slotPlaceholder.style.width = '100%'; | |
slotPlaceholder.slot = slotName; | |
host.appendChild(slotPlaceholder); | |
const paymentElementContainer = document.createElement('div'); | |
paymentElementContainer.id = 'payment-element'; | |
slotPlaceholder.appendChild(paymentElementContainer); | |
}; | |
const PaymentForm = props => createComponent(StripePaymentForm, { | |
get onSuccess() { | |
return props.onSuccess; | |
}, | |
get options() { | |
return props.options; | |
}, | |
get context() { | |
return props.context; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
const _tmpl$$k = /*#__PURE__*/template(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3px" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12">`); | |
const CheckIcon = props => (() => { | |
const _el$ = _tmpl$$k(); | |
spread(_el$, props, true, true); | |
return _el$; | |
})(); | |
const _tmpl$$j = /*#__PURE__*/template(`<div>`); | |
const Checkbox = props => { | |
return (() => { | |
const _el$ = _tmpl$$j(); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.isChecked; | |
}, | |
get children() { | |
return createComponent(CheckIcon, {}); | |
} | |
})); | |
createRenderEffect(() => className(_el$, 'w-4 h-4 typebot-checkbox' + (props.isChecked ? ' checked' : '') + (props.class ? ` ${props.class}` : ''))); | |
return _el$; | |
})(); | |
}; | |
const _tmpl$$i = /*#__PURE__*/template(`<button class="w-5 h-5">`), | |
_tmpl$2$b = /*#__PURE__*/template(`<div class="flex justify-between items-center gap-2 w-full pr-4"><input class="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input" type="text">`); | |
const SearchInput = props => { | |
const [value, setValue] = createSignal(''); | |
const [local, others] = splitProps(props, ['onInput', 'ref']); | |
const changeValue = value => { | |
setValue(value); | |
local.onInput(value); | |
}; | |
const clearValue = () => { | |
setValue(''); | |
props.onClear(); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$b(), | |
_el$2 = _el$.firstChild; | |
_el$2.$$input = e => changeValue(e.currentTarget.value); | |
const _ref$ = props.ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$2) : props.ref = _el$2; | |
_el$2.style.setProperty("font-size", "16px"); | |
spread(_el$2, mergeProps$2({ | |
get value() { | |
return value(); | |
} | |
}, others), false, false); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return value().length > 0; | |
}, | |
get children() { | |
const _el$3 = _tmpl$$i(); | |
_el$3.addEventListener("click", clearValue); | |
insert(_el$3, createComponent(CloseIcon, {})); | |
return _el$3; | |
} | |
}), null); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["input"]); | |
const defaultChoiceInputOptions = { | |
buttonLabel: defaultButtonLabel, | |
searchInputPlaceholder: 'Filter the options...', | |
isMultipleChoice: false, | |
isSearchable: false | |
}; | |
const _tmpl$$h = /*#__PURE__*/template(`<div class="flex items-end typebot-input w-full">`), | |
_tmpl$2$a = /*#__PURE__*/template(`<form class="flex flex-col items-end gap-2 w-full"><div>`), | |
_tmpl$3$5 = /*#__PURE__*/template(`<span><div role="checkbox"><div class="flex items-center gap-2"><span>`), | |
_tmpl$4$2 = /*#__PURE__*/template(`<span><div role="checkbox" aria-checked class="w-full py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable selected"><div class="flex items-center gap-2"><span>`); | |
const MultipleChoicesForm = props => { | |
let inputRef; | |
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems); | |
const [selectedItemIds, setSelectedItemIds] = createSignal([]); | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
}); | |
const handleClick = itemId => { | |
toggleSelectedItemId(itemId); | |
}; | |
const toggleSelectedItemId = itemId => { | |
const existingIndex = selectedItemIds().indexOf(itemId); | |
if (existingIndex !== -1) { | |
setSelectedItemIds(selectedItemIds => selectedItemIds.filter(selectedItemId => selectedItemId !== itemId)); | |
} else { | |
setSelectedItemIds(selectedIndices => [...selectedIndices, itemId]); | |
} | |
}; | |
const handleSubmit = () => props.onSubmit({ | |
value: selectedItemIds().map(selectedItemId => props.defaultItems.find(item => item.id === selectedItemId)?.content).join(', ') | |
}); | |
const filterItems = inputValue => { | |
setFilteredItems(props.defaultItems.filter(item => item.content?.toLowerCase().includes((inputValue ?? '').toLowerCase()))); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$a(), | |
_el$3 = _el$.firstChild; | |
_el$.addEventListener("submit", handleSubmit); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.options?.isSearchable; | |
}, | |
get children() { | |
const _el$2 = _tmpl$$h(); | |
insert(_el$2, createComponent(SearchInput, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
onInput: filterItems, | |
get placeholder() { | |
return props.options?.searchInputPlaceholder ?? defaultChoiceInputOptions.searchInputPlaceholder; | |
}, | |
onClear: () => setFilteredItems(props.defaultItems) | |
})); | |
return _el$2; | |
} | |
}), _el$3); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return filteredItems(); | |
}, | |
children: item => (() => { | |
const _el$4 = _tmpl$3$5(), | |
_el$5 = _el$4.firstChild, | |
_el$6 = _el$5.firstChild, | |
_el$7 = _el$6.firstChild; | |
_el$5.addEventListener("click", () => handleClick(item.id)); | |
insert(_el$6, createComponent(Checkbox, { | |
get isChecked() { | |
return selectedItemIds().some(selectedItemId => selectedItemId === item.id); | |
}, | |
"class": "flex-shrink-0" | |
}), _el$7); | |
insert(_el$7, () => item.content); | |
createRenderEffect(_p$ => { | |
const _v$ = 'relative' + (isMobile() ? ' w-full' : ''), | |
_v$2 = selectedItemIds().some(selectedItemId => selectedItemId === item.id), | |
_v$3 = 'w-full py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable' + (selectedItemIds().some(selectedItemId => selectedItemId === item.id) ? ' selected' : ''), | |
_v$4 = item.id; | |
_v$ !== _p$._v$ && className(_el$4, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && setAttribute(_el$5, "aria-checked", _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && className(_el$5, _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && setAttribute(_el$5, "data-itemid", _p$._v$4 = _v$4); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$4; | |
})() | |
}), null); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return selectedItemIds().filter(selectedItemId => filteredItems().every(item => item.id !== selectedItemId)); | |
}, | |
children: selectedItemId => (() => { | |
const _el$8 = _tmpl$4$2(), | |
_el$9 = _el$8.firstChild, | |
_el$10 = _el$9.firstChild, | |
_el$11 = _el$10.firstChild; | |
_el$9.addEventListener("click", () => handleClick(selectedItemId)); | |
setAttribute(_el$9, "data-itemid", selectedItemId); | |
insert(_el$10, createComponent(Checkbox, { | |
isChecked: true | |
}), _el$11); | |
insert(_el$11, () => props.defaultItems.find(item => item.id === selectedItemId)?.content); | |
createRenderEffect(() => className(_el$8, 'relative' + (isMobile() ? ' w-full' : ''))); | |
return _el$8; | |
})() | |
}), null); | |
insert(_el$, (() => { | |
const _c$ = createMemo(() => selectedItemIds().length > 0); | |
return () => _c$() && createComponent(SendButton, { | |
disableIcon: true, | |
get children() { | |
return props.options?.buttonLabel ?? defaultChoiceInputOptions.buttonLabel; | |
} | |
}); | |
})(), null); | |
createRenderEffect(() => className(_el$3, 'flex flex-wrap justify-end gap-2' + (props.options?.isSearchable ? ' overflow-y-scroll max-h-80 rounded-md' : ''))); | |
return _el$; | |
})(); | |
}; | |
const _tmpl$$g = /*#__PURE__*/template(`<div class="flex items-end typebot-input w-full">`), | |
_tmpl$2$9 = /*#__PURE__*/template(`<div class="flex flex-col gap-2 w-full"><div>`), | |
_tmpl$3$4 = /*#__PURE__*/template(`<span>`), | |
_tmpl$4$1 = /*#__PURE__*/template(`<span class="flex h-3 w-3 absolute top-0 right-0 -mt-1 -mr-1 ping"><span class="animate-ping absolute inline-flex h-full w-full rounded-full brightness-200 opacity-75"></span><span class="relative inline-flex rounded-full h-3 w-3 brightness-150">`); | |
const Buttons = props => { | |
let inputRef; | |
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems); | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
}); | |
const handleClick = itemIndex => props.onSubmit({ | |
value: filteredItems()[itemIndex].content ?? '' | |
}); | |
const filterItems = inputValue => { | |
setFilteredItems(props.defaultItems.filter(item => item.content?.toLowerCase().includes((inputValue ?? '').toLowerCase()))); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$9(), | |
_el$3 = _el$.firstChild; | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.options?.isSearchable; | |
}, | |
get children() { | |
const _el$2 = _tmpl$$g(); | |
insert(_el$2, createComponent(SearchInput, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
onInput: filterItems, | |
get placeholder() { | |
return props.options?.searchInputPlaceholder ?? defaultChoiceInputOptions.searchInputPlaceholder; | |
}, | |
onClear: () => setFilteredItems(props.defaultItems) | |
})); | |
return _el$2; | |
} | |
}), _el$3); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return filteredItems(); | |
}, | |
children: (item, index) => (() => { | |
const _el$4 = _tmpl$3$4(); | |
insert(_el$4, createComponent(Button, { | |
"on:click": () => handleClick(index()), | |
get ["data-itemid"]() { | |
return item.id; | |
}, | |
"class": "w-full", | |
get children() { | |
return item.content; | |
} | |
}), null); | |
insert(_el$4, (() => { | |
const _c$ = createMemo(() => !!(props.chunkIndex === 0 && props.defaultItems.length === 1)); | |
return () => _c$() && _tmpl$4$1(); | |
})(), null); | |
createRenderEffect(() => className(_el$4, 'relative' + (isMobile() ? ' w-full' : ''))); | |
return _el$4; | |
})() | |
})); | |
createRenderEffect(() => className(_el$3, 'flex flex-wrap justify-end gap-2' + (props.options?.isSearchable ? ' overflow-y-scroll max-h-80 rounded-md' : ''))); | |
return _el$; | |
})(); | |
}; | |
const _tmpl$$f = /*#__PURE__*/template(`<div class="flex items-end typebot-input w-full">`), | |
_tmpl$2$8 = /*#__PURE__*/template(`<div class="flex flex-col gap-2 w-full"><div>`), | |
_tmpl$3$3 = /*#__PURE__*/template(`<button><img fetchpriority="high" class="m-auto"><div><span class="font-semibold"></span><span class="text-sm whitespace-pre-wrap text-left">`); | |
const SinglePictureChoice = props => { | |
let inputRef; | |
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems); | |
const [totalLoadedImages, setTotalLoadedImages] = createSignal(0); | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
}); | |
const handleClick = itemIndex => { | |
const item = filteredItems()[itemIndex]; | |
return props.onSubmit({ | |
label: isNotEmpty(item.title) ? item.title : item.pictureSrc ?? item.id, | |
value: item.id | |
}); | |
}; | |
const filterItems = inputValue => { | |
setFilteredItems(props.defaultItems.filter(item => item.title?.toLowerCase().includes((inputValue ?? '').toLowerCase()) || item.description?.toLowerCase().includes((inputValue ?? '').toLowerCase()))); | |
}; | |
createEffect(() => { | |
if (totalLoadedImages() === props.defaultItems.filter(item => isDefined(item.pictureSrc)).length) props.onTransitionEnd(); | |
}); | |
const onImageLoad = () => { | |
setTotalLoadedImages(acc => acc + 1); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$8(), | |
_el$3 = _el$.firstChild; | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.options?.isSearchable; | |
}, | |
get children() { | |
const _el$2 = _tmpl$$f(); | |
insert(_el$2, createComponent(SearchInput, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
onInput: filterItems, | |
get placeholder() { | |
return props.options?.searchInputPlaceholder ?? ''; | |
}, | |
onClear: () => setFilteredItems(props.defaultItems) | |
})); | |
return _el$2; | |
} | |
}), _el$3); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return filteredItems(); | |
}, | |
children: (item, index) => (() => { | |
const _el$4 = _tmpl$3$3(), | |
_el$5 = _el$4.firstChild, | |
_el$6 = _el$5.nextSibling, | |
_el$7 = _el$6.firstChild, | |
_el$8 = _el$7.nextSibling; | |
_el$4.addEventListener("click", () => handleClick(index())); | |
_el$5.addEventListener("load", onImageLoad); | |
insert(_el$7, () => item.title); | |
insert(_el$8, () => item.description); | |
createRenderEffect(_p$ => { | |
const _v$ = item.id, | |
_v$2 = 'flex flex-col typebot-picture-button focus:outline-none filter hover:brightness-90 active:brightness-75 justify-between ' + (isSvgSrc(item.pictureSrc) ? 'has-svg' : ''), | |
_v$3 = item.pictureSrc, | |
_v$4 = item.title ?? `Picture ${index() + 1}`, | |
_v$5 = `Picture choice ${index() + 1}`, | |
_v$6 = 'flex flex-col gap-1 py-2 flex-shrink-0 px-4 w-full' + (item.description ? ' items-start' : ''); | |
_v$ !== _p$._v$ && setAttribute(_el$4, "data-itemid", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && className(_el$4, _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && setAttribute(_el$5, "src", _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && setAttribute(_el$5, "alt", _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && setAttribute(_el$5, "elementtiming", _p$._v$5 = _v$5); | |
_v$6 !== _p$._v$6 && className(_el$6, _p$._v$6 = _v$6); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined | |
}); | |
return _el$4; | |
})() | |
})); | |
createRenderEffect(() => className(_el$3, 'gap-2 flex flex-wrap justify-end' + (props.options?.isSearchable ? ' overflow-y-scroll max-h-[464px] rounded-md' : ''))); | |
return _el$; | |
})(); | |
}; | |
const defaultPictureChoiceOptions = { | |
buttonLabel: defaultButtonLabel, | |
searchInputPlaceholder: 'Filter the options...', | |
isMultipleChoice: false, | |
isSearchable: false, | |
dynamicItems: { | |
isEnabled: false | |
} | |
}; | |
const _tmpl$$e = /*#__PURE__*/template(`<div class="flex items-end typebot-input w-full">`), | |
_tmpl$2$7 = /*#__PURE__*/template(`<form class="flex flex-col gap-2 w-full items-end"><div>`), | |
_tmpl$3$2 = /*#__PURE__*/template(`<span class="font-semibold">`), | |
_tmpl$4 = /*#__PURE__*/template(`<span class="text-sm whitespace-pre-wrap text-left">`), | |
_tmpl$5 = /*#__PURE__*/template(`<div class="flex flex-col gap-1 ">`), | |
_tmpl$6 = /*#__PURE__*/template(`<div role="checkbox"><img fetchpriority="high" class="m-auto"><div>`), | |
_tmpl$7 = /*#__PURE__*/template(`<div role="checkbox" aria-checked class="flex flex-col focus:outline-none cursor-pointer select-none typebot-selectable-picture selected"><img fetchpriority="high"><div>`); | |
const MultiplePictureChoice = props => { | |
let inputRef; | |
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems); | |
const [selectedItemIds, setSelectedItemIds] = createSignal([]); | |
const [totalLoadedImages, setTotalLoadedImages] = createSignal(0); | |
onMount(() => { | |
if (!isMobile() && inputRef) inputRef.focus({ | |
preventScroll: true | |
}); | |
}); | |
const handleClick = itemId => { | |
toggleSelectedItemId(itemId); | |
}; | |
const toggleSelectedItemId = itemId => { | |
const existingIndex = selectedItemIds().indexOf(itemId); | |
if (existingIndex !== -1) { | |
setSelectedItemIds(selectedItemIds => selectedItemIds.filter(selectedItemId => selectedItemId !== itemId)); | |
} else { | |
setSelectedItemIds(selectedIndices => [...selectedIndices, itemId]); | |
} | |
}; | |
const handleSubmit = () => props.onSubmit({ | |
value: selectedItemIds().map(selectedItemId => { | |
const item = props.defaultItems.find(item => item.id === selectedItemId); | |
return isNotEmpty(item?.title) ? item.title : item?.pictureSrc; | |
}).join(', ') | |
}); | |
const filterItems = inputValue => { | |
setFilteredItems(props.defaultItems.filter(item => item.title?.toLowerCase().includes((inputValue ?? '').toLowerCase()) || item.description?.toLowerCase().includes((inputValue ?? '').toLowerCase()))); | |
}; | |
createEffect(() => { | |
if (totalLoadedImages() === props.defaultItems.filter(item => isDefined(item.pictureSrc)).length) props.onTransitionEnd(); | |
}); | |
const onImageLoad = () => { | |
setTotalLoadedImages(acc => acc + 1); | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$7(), | |
_el$3 = _el$.firstChild; | |
_el$.addEventListener("submit", handleSubmit); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.options?.isSearchable; | |
}, | |
get children() { | |
const _el$2 = _tmpl$$e(); | |
insert(_el$2, createComponent(SearchInput, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
onInput: filterItems, | |
get placeholder() { | |
return props.options?.searchInputPlaceholder ?? defaultPictureChoiceOptions.searchInputPlaceholder; | |
}, | |
onClear: () => setFilteredItems(props.defaultItems) | |
})); | |
return _el$2; | |
} | |
}), _el$3); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return filteredItems(); | |
}, | |
children: (item, index) => (() => { | |
const _el$4 = _tmpl$6(), | |
_el$5 = _el$4.firstChild, | |
_el$6 = _el$5.nextSibling; | |
_el$4.addEventListener("click", () => handleClick(item.id)); | |
_el$5.addEventListener("load", onImageLoad); | |
insert(_el$6, createComponent(Checkbox, { | |
get isChecked() { | |
return selectedItemIds().some(selectedItemId => selectedItemId === item.id); | |
}, | |
get ["class"]() { | |
return 'flex-shrink-0' + (item.title || item.description ? ' mt-1' : undefined); | |
} | |
}), null); | |
insert(_el$6, createComponent(Show, { | |
get when() { | |
return item.title || item.description; | |
}, | |
get children() { | |
const _el$7 = _tmpl$5(); | |
insert(_el$7, createComponent(Show, { | |
get when() { | |
return item.title; | |
}, | |
get children() { | |
const _el$8 = _tmpl$3$2(); | |
insert(_el$8, () => item.title); | |
return _el$8; | |
} | |
}), null); | |
insert(_el$7, createComponent(Show, { | |
get when() { | |
return item.description; | |
}, | |
get children() { | |
const _el$9 = _tmpl$4(); | |
insert(_el$9, () => item.description); | |
return _el$9; | |
} | |
}), null); | |
return _el$7; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$ = selectedItemIds().some(selectedItemId => selectedItemId === item.id), | |
_v$2 = 'flex flex-col focus:outline-none cursor-pointer select-none typebot-selectable-picture' + (selectedItemIds().some(selectedItemId => selectedItemId === item.id) ? ' selected' : '') + (isSvgSrc(item.pictureSrc) ? ' has-svg' : ''), | |
_v$3 = item.id, | |
_v$4 = item.pictureSrc, | |
_v$5 = item.title ?? `Picture ${index() + 1}`, | |
_v$6 = `Picture choice ${index() + 1}`, | |
_v$7 = 'flex gap-3 py-2 flex-shrink-0' + (isEmpty$2(item.title) && isEmpty$2(item.description) ? ' justify-center' : ' px-3'); | |
_v$ !== _p$._v$ && setAttribute(_el$4, "aria-checked", _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && className(_el$4, _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && setAttribute(_el$4, "data-itemid", _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && setAttribute(_el$5, "src", _p$._v$4 = _v$4); | |
_v$5 !== _p$._v$5 && setAttribute(_el$5, "alt", _p$._v$5 = _v$5); | |
_v$6 !== _p$._v$6 && setAttribute(_el$5, "elementtiming", _p$._v$6 = _v$6); | |
_v$7 !== _p$._v$7 && className(_el$6, _p$._v$7 = _v$7); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined, | |
_v$7: undefined | |
}); | |
return _el$4; | |
})() | |
}), null); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return selectedItemIds().filter(selectedItemId => filteredItems().every(item => item.id !== selectedItemId)).map(selectedItemId => props.defaultItems.find(item => item.id === selectedItemId)).filter(isDefined); | |
}, | |
children: (selectedItem, index) => (() => { | |
const _el$10 = _tmpl$7(), | |
_el$11 = _el$10.firstChild, | |
_el$12 = _el$11.nextSibling; | |
_el$10.addEventListener("click", () => handleClick(selectedItem.id)); | |
insert(_el$12, createComponent(Checkbox, { | |
get isChecked() { | |
return selectedItemIds().some(selectedItemId => selectedItemId === selectedItem.id); | |
}, | |
get ["class"]() { | |
return 'flex-shrink-0' + (selectedItem.title || selectedItem.description ? ' mt-1' : undefined); | |
} | |
}), null); | |
insert(_el$12, createComponent(Show, { | |
get when() { | |
return selectedItem.title || selectedItem.description; | |
}, | |
get children() { | |
const _el$13 = _tmpl$5(); | |
insert(_el$13, createComponent(Show, { | |
get when() { | |
return selectedItem.title; | |
}, | |
get children() { | |
const _el$14 = _tmpl$3$2(); | |
insert(_el$14, () => selectedItem.title); | |
return _el$14; | |
} | |
}), null); | |
insert(_el$13, createComponent(Show, { | |
get when() { | |
return selectedItem.description; | |
}, | |
get children() { | |
const _el$15 = _tmpl$4(); | |
insert(_el$15, () => selectedItem.description); | |
return _el$15; | |
} | |
}), null); | |
return _el$13; | |
} | |
}), null); | |
createRenderEffect(_p$ => { | |
const _v$8 = selectedItem.id, | |
_v$9 = props.defaultItems.find(item => item.id === selectedItem.id)?.pictureSrc, | |
_v$10 = selectedItem.title ?? `Selected picture ${index() + 1}`, | |
_v$11 = `Selected picture choice ${index() + 1}`, | |
_v$12 = 'flex gap-3 py-2 flex-shrink-0' + (isEmpty$2(selectedItem.title) && isEmpty$2(selectedItem.description) ? ' justify-center' : ' pl-4'); | |
_v$8 !== _p$._v$8 && setAttribute(_el$10, "data-itemid", _p$._v$8 = _v$8); | |
_v$9 !== _p$._v$9 && setAttribute(_el$11, "src", _p$._v$9 = _v$9); | |
_v$10 !== _p$._v$10 && setAttribute(_el$11, "alt", _p$._v$10 = _v$10); | |
_v$11 !== _p$._v$11 && setAttribute(_el$11, "elementtiming", _p$._v$11 = _v$11); | |
_v$12 !== _p$._v$12 && className(_el$12, _p$._v$12 = _v$12); | |
return _p$; | |
}, { | |
_v$8: undefined, | |
_v$9: undefined, | |
_v$10: undefined, | |
_v$11: undefined, | |
_v$12: undefined | |
}); | |
return _el$10; | |
})() | |
}), null); | |
insert(_el$, (() => { | |
const _c$ = createMemo(() => selectedItemIds().length > 0); | |
return () => _c$() && createComponent(SendButton, { | |
disableIcon: true, | |
get children() { | |
return props.options?.buttonLabel ?? defaultPictureChoiceOptions.buttonLabel; | |
} | |
}); | |
})(), null); | |
createRenderEffect(() => className(_el$3, 'flex flex-wrap justify-end gap-2' + (props.options?.isSearchable ? ' overflow-y-scroll max-h-[464px] rounded-md' : ''))); | |
return _el$; | |
})(); | |
}; | |
const [formattedMessages, setFormattedMessages] = createSignal([]); | |
// Copied from https://github.com/solidjs-community/solid-primitives/blob/main/packages/storage/src/types.ts | |
// Simplified version | |
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
function persist(signal, params) { | |
const [isRecovered, setIsRecovered] = createSignal(false); | |
if (!params.storage) return [...signal, () => false]; | |
const storage = parseRememberUserStorage$1(params.storage || defaultSettings.general.rememberUser.storage); | |
const serialize = JSON.stringify.bind(JSON); | |
const deserialize = JSON.parse.bind(JSON); | |
const init = storage.getItem(params.key); | |
const set = typeof signal[0] === 'function' ? data => signal[1](() => deserialize(data)) : data => signal[1](reconcile(deserialize(data))); | |
if (init) { | |
set(init); | |
setIsRecovered(true); | |
params.onRecovered?.(); | |
} | |
return [signal[0], typeof signal[0] === 'function' ? value => { | |
setIsRecovered(false); | |
const output = signal[1](value); | |
if (value) storage.setItem(params.key, serialize(output));else storage.removeItem(params.key); | |
return output; | |
} : (...args) => { | |
setIsRecovered(false); | |
signal[1](...args); | |
const value = serialize(untrack(() => signal[0])); | |
storage.setItem(params.key, value); | |
}, isRecovered]; | |
} | |
const parseRememberUserStorage$1 = storage => (storage ?? defaultSettings.general.rememberUser.storage) === 'session' ? sessionStorage : localStorage; | |
let BackgroundType = /*#__PURE__*/function (BackgroundType) { | |
BackgroundType["COLOR"] = "Color"; | |
BackgroundType["IMAGE"] = "Image"; | |
BackgroundType["NONE"] = "None"; | |
return BackgroundType; | |
}({}); | |
const defaultLightTextColor = '#303235'; | |
const defaultDarkTextColor = '#FFFFFF'; | |
/*---- General ----*/ | |
// Font | |
const defaultFontType = 'Google'; | |
const defaultFontFamily = 'Open Sans'; | |
// Background | |
const defaultBackgroundType = BackgroundType.COLOR; | |
const defaultBackgroundColor = '#ffffff'; | |
const defaultProgressBarColor = '#0042DA'; | |
const defaultProgressBarBackgroundColor = '#e0edff'; | |
const defaultProgressBarThickness = 4; | |
const defaultProgressBarPosition = 'absolute'; | |
const defaultProgressBarPlacement = 'Top'; | |
const defaultRoundness = 'medium'; | |
const defaultOpacity = 1; | |
const defaultBlur = 0; | |
/*---- Chat ----*/ | |
// Container | |
const defaultContainerMaxWidth = '800px'; | |
const defaultContainerMaxHeight = '100%'; | |
const defaultContainerBackgroundColor = 'transparent'; | |
// Host bubbles | |
const defaultHostBubblesBackgroundColor = '#F7F8FF'; | |
const defaultHostBubblesColor = defaultLightTextColor; | |
// Guest bubbles | |
const defaultGuestBubblesBackgroundColor = '#FF8E21'; | |
const defaultGuestBubblesColor = defaultDarkTextColor; | |
// Buttons | |
const defaultButtonsBackgroundColor = '#0042DA'; | |
const defaultButtonsColor = defaultDarkTextColor; | |
const defaultButtonsBorderThickness = 1; | |
// Inputs | |
const defaultInputsBackgroundColor = '#FFFFFF'; | |
const defaultInputsColor = defaultLightTextColor; | |
const defaultInputsPlaceholderColor = '#9095A0'; | |
const defaultInputsShadow = 'md'; | |
// Host avatar | |
const defaultHostAvatarIsEnabled = true; | |
// Guest avatar | |
const defaultGuestAvatarIsEnabled = false; | |
const _tmpl$$d = /*#__PURE__*/template(`<div>`), | |
_tmpl$2$6 = /*#__PURE__*/template(`<div class="flex justify-end animate-fade-in gap-2 typebot-input-container">`); | |
const InputChatBlock = props => { | |
const [answer, setAnswer] = persist(createSignal(), { | |
key: `typebot-${props.context.typebot.id}-input-${props.chunkIndex}`, | |
storage: props.context.storage | |
}); | |
const handleSubmit = async ({ | |
label, | |
value, | |
attachments | |
}) => { | |
setAnswer({ | |
text: props.block.type !== InputBlockType.FILE ? label ?? value : '', | |
attachments | |
}); | |
props.onSubmit(value ?? label, props.block.type === InputBlockType.FILE ? undefined : attachments); | |
}; | |
const handleSkip = label => { | |
setAnswer({ | |
text: label | |
}); | |
props.onSkip(); | |
}; | |
createEffect(() => { | |
const formattedMessage = formattedMessages().findLast(message => props.chunkIndex === message.inputIndex)?.formattedMessage; | |
if (formattedMessage && props.block.type !== InputBlockType.FILE) setAnswer(answer => ({ | |
...answer, | |
text: formattedMessage | |
})); | |
}); | |
return createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return answer() && !props.hasError; | |
}, | |
get children() { | |
return createComponent(GuestBubble, { | |
get message() { | |
return answer(); | |
}, | |
get showAvatar() { | |
return props.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled; | |
}, | |
get avatarSrc() { | |
return props.guestAvatar?.url && props.guestAvatar.url; | |
}, | |
get hasHostAvatar() { | |
return props.hasHostAvatar; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return isNotDefined(answer()) || props.hasError; | |
}, | |
get children() { | |
const _el$ = _tmpl$2$6(); | |
const _ref$ = props.ref; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$; | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.hasHostAvatar; | |
}, | |
get children() { | |
const _el$2 = _tmpl$$d(); | |
createRenderEffect(() => className(_el$2, 'flex flex-shrink-0 items-center ' + (isMobile() ? 'w-6 h-6' : 'w-10 h-10'))); | |
return _el$2; | |
} | |
}), null); | |
insert(_el$, createComponent(Input, { | |
get context() { | |
return props.context; | |
}, | |
get block() { | |
return props.block; | |
}, | |
get chunkIndex() { | |
return props.chunkIndex; | |
}, | |
get isInputPrefillEnabled() { | |
return props.isInputPrefillEnabled; | |
}, | |
get existingAnswer() { | |
return createMemo(() => !!props.hasError)() ? answer()?.text : undefined; | |
}, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
}, | |
onSubmit: handleSubmit, | |
onSkip: handleSkip | |
}), null); | |
createRenderEffect(() => setAttribute(_el$, "data-blockid", props.block.id)); | |
return _el$; | |
} | |
})]; | |
} | |
}); | |
}; | |
const Input = props => { | |
const onSubmit = answer => props.onSubmit(answer); | |
const getPrefilledValue = () => props.existingAnswer ?? (props.isInputPrefillEnabled ? props.block.prefilledValue : undefined); | |
const submitPaymentSuccess = () => props.onSubmit({ | |
value: props.block.options?.labels?.success ?? defaultPaymentInputOptions.labels.success | |
}); | |
return createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.TEXT; | |
}, | |
get children() { | |
return createComponent(TextInput, { | |
get block() { | |
return props.block; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
get context() { | |
return props.context; | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.NUMBER; | |
}, | |
get children() { | |
return createComponent(NumberInput, { | |
get block() { | |
return props.block; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.EMAIL; | |
}, | |
get children() { | |
return createComponent(EmailInput, { | |
get block() { | |
return props.block; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.URL; | |
}, | |
get children() { | |
return createComponent(UrlInput, { | |
get block() { | |
return props.block; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.PHONE; | |
}, | |
get children() { | |
return createComponent(PhoneInput, { | |
get labels() { | |
return props.block.options?.labels; | |
}, | |
get defaultCountryCode() { | |
return props.block.options?.defaultCountryCode; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.DATE; | |
}, | |
get children() { | |
return createComponent(DateForm, { | |
get options() { | |
return props.block.options; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return isButtonsBlock(props.block); | |
}, | |
keyed: true, | |
children: block => createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return !block.options?.isMultipleChoice; | |
}, | |
get children() { | |
return createComponent(Buttons, { | |
get chunkIndex() { | |
return props.chunkIndex; | |
}, | |
get defaultItems() { | |
return block.items; | |
}, | |
get options() { | |
return block.options; | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return block.options?.isMultipleChoice; | |
}, | |
get children() { | |
return createComponent(MultipleChoicesForm, { | |
get defaultItems() { | |
return block.items; | |
}, | |
get options() { | |
return block.options; | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
})]; | |
} | |
}) | |
}), createComponent(Match, { | |
get when() { | |
return isPictureChoiceBlock(props.block); | |
}, | |
keyed: true, | |
children: block => createComponent(Switch, { | |
get children() { | |
return [createComponent(Match, { | |
get when() { | |
return !block.options?.isMultipleChoice; | |
}, | |
get children() { | |
return createComponent(SinglePictureChoice, { | |
get defaultItems() { | |
return block.items; | |
}, | |
get options() { | |
return block.options; | |
}, | |
onSubmit: onSubmit, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return block.options?.isMultipleChoice; | |
}, | |
get children() { | |
return createComponent(MultiplePictureChoice, { | |
get defaultItems() { | |
return block.items; | |
}, | |
get options() { | |
return block.options; | |
}, | |
onSubmit: onSubmit, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
})]; | |
} | |
}) | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.RATING; | |
}, | |
get children() { | |
return createComponent(RatingForm, { | |
get block() { | |
return props.block; | |
}, | |
get defaultValue() { | |
return getPrefilledValue(); | |
}, | |
onSubmit: onSubmit | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.FILE; | |
}, | |
get children() { | |
return createComponent(FileUploadForm, { | |
get context() { | |
return props.context; | |
}, | |
get block() { | |
return props.block; | |
}, | |
onSubmit: onSubmit, | |
get onSkip() { | |
return props.onSkip; | |
} | |
}); | |
} | |
}), createComponent(Match, { | |
get when() { | |
return props.block.type === InputBlockType.PAYMENT; | |
}, | |
get children() { | |
return createComponent(PaymentForm, { | |
get context() { | |
return props.context; | |
}, | |
get options() { | |
return { | |
...props.block.options, | |
...props.block.runtimeOptions | |
}; | |
}, | |
onSuccess: submitPaymentSuccess, | |
get onTransitionEnd() { | |
return props.onTransitionEnd; | |
} | |
}); | |
} | |
})]; | |
} | |
}); | |
}; | |
const isButtonsBlock = block => block?.type === InputBlockType.CHOICE ? block : undefined; | |
const isPictureChoiceBlock = block => block?.type === InputBlockType.PICTURE_CHOICE ? block : undefined; | |
const _tmpl$$c = /*#__PURE__*/template(`<div><div>`); | |
const AvatarSideContainer = props => { | |
let avatarContainer; | |
const [top, setTop] = createSignal(0); | |
const resizeObserver = new ResizeObserver(entries => setTop(entries[0].target.clientHeight - (isMobile() ? 24 : 40))); | |
onMount(() => { | |
if (avatarContainer) { | |
resizeObserver.observe(avatarContainer); | |
} | |
}); | |
onCleanup(() => { | |
if (avatarContainer) { | |
resizeObserver.unobserve(avatarContainer); | |
} | |
}); | |
return (() => { | |
const _el$ = _tmpl$$c(), | |
_el$2 = _el$.firstChild; | |
const _ref$ = avatarContainer; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : avatarContainer = _el$; | |
insert(_el$2, createComponent(Avatar, { | |
get initialAvatarSrc() { | |
return props.hostAvatarSrc; | |
} | |
})); | |
createRenderEffect(_p$ => { | |
const _v$ = 'flex flex-shrink-0 items-center relative typebot-avatar-container ' + (isMobile() ? 'w-6' : 'w-10'), | |
_v$2 = 'absolute flex items-center top-0' + (isMobile() ? ' w-6 h-6' : ' w-10 h-10') + (props.hideAvatar ? ' opacity-0' : ' opacity-100'), | |
_v$3 = `${top()}px`, | |
_v$4 = props.isTransitionDisabled ? undefined : 'top 350ms ease-out, opacity 250ms ease-out'; | |
_v$ !== _p$._v$ && className(_el$, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && className(_el$2, _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$2.style.setProperty("top", _v$3) : _el$2.style.removeProperty("top")); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$2.style.setProperty("transition", _v$4) : _el$2.style.removeProperty("transition")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$; | |
})(); | |
}; | |
const [streamingMessage, setStreamingMessage] = createSignal(); | |
/** | |
* marked v9.0.3 - a markdown parser | |
* Copyright (c) 2011-2023, Christopher Jeffrey. (MIT Licensed) | |
* https://github.com/markedjs/marked | |
*/ | |
/** | |
* DO NOT EDIT THIS FILE | |
* The code in this file is generated from files in ./src/ | |
*/ | |
/** | |
* Gets the original marked default options. | |
*/ | |
function _getDefaults() { | |
return { | |
async: false, | |
breaks: false, | |
extensions: null, | |
gfm: true, | |
hooks: null, | |
pedantic: false, | |
renderer: null, | |
silent: false, | |
tokenizer: null, | |
walkTokens: null | |
}; | |
} | |
let _defaults = _getDefaults(); | |
function changeDefaults(newDefaults) { | |
_defaults = newDefaults; | |
} | |
/** | |
* Helpers | |
*/ | |
const escapeTest = /[&<>"']/; | |
const escapeReplace = new RegExp(escapeTest.source, 'g'); | |
const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/; | |
const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, 'g'); | |
const escapeReplacements = { | |
'&': '&', | |
'<': '<', | |
'>': '>', | |
'"': '"', | |
"'": ''' | |
}; | |
const getEscapeReplacement = (ch) => escapeReplacements[ch]; | |
function escape(html, encode) { | |
if (encode) { | |
if (escapeTest.test(html)) { | |
return html.replace(escapeReplace, getEscapeReplacement); | |
} | |
} | |
else { | |
if (escapeTestNoEncode.test(html)) { | |
return html.replace(escapeReplaceNoEncode, getEscapeReplacement); | |
} | |
} | |
return html; | |
} | |
const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; | |
function unescape(html) { | |
// explicitly match decimal, hex, and named HTML entities | |
return html.replace(unescapeTest, (_, n) => { | |
n = n.toLowerCase(); | |
if (n === 'colon') | |
return ':'; | |
if (n.charAt(0) === '#') { | |
return n.charAt(1) === 'x' | |
? String.fromCharCode(parseInt(n.substring(2), 16)) | |
: String.fromCharCode(+n.substring(1)); | |
} | |
return ''; | |
}); | |
} | |
const caret = /(^|[^\[])\^/g; | |
function edit(regex, opt) { | |
regex = typeof regex === 'string' ? regex : regex.source; | |
opt = opt || ''; | |
const obj = { | |
replace: (name, val) => { | |
val = typeof val === 'object' && 'source' in val ? val.source : val; | |
val = val.replace(caret, '$1'); | |
regex = regex.replace(name, val); | |
return obj; | |
}, | |
getRegex: () => { | |
return new RegExp(regex, opt); | |
} | |
}; | |
return obj; | |
} | |
function cleanUrl(href) { | |
try { | |
href = encodeURI(href).replace(/%25/g, '%'); | |
} | |
catch (e) { | |
return null; | |
} | |
return href; | |
} | |
const noopTest = { exec: () => null }; | |
function splitCells(tableRow, count) { | |
// ensure that every cell-delimiting pipe has a space | |
// before it to distinguish it from an escaped pipe | |
const row = tableRow.replace(/\|/g, (match, offset, str) => { | |
let escaped = false; | |
let curr = offset; | |
while (--curr >= 0 && str[curr] === '\\') | |
escaped = !escaped; | |
if (escaped) { | |
// odd number of slashes means | is escaped | |
// so we leave it alone | |
return '|'; | |
} | |
else { | |
// add space before unescaped | | |
return ' |'; | |
} | |
}), cells = row.split(/ \|/); | |
let i = 0; | |
// First/last cell in a row cannot be empty if it has no leading/trailing pipe | |
if (!cells[0].trim()) { | |
cells.shift(); | |
} | |
if (cells.length > 0 && !cells[cells.length - 1].trim()) { | |
cells.pop(); | |
} | |
if (count) { | |
if (cells.length > count) { | |
cells.splice(count); | |
} | |
else { | |
while (cells.length < count) | |
cells.push(''); | |
} | |
} | |
for (; i < cells.length; i++) { | |
// leading or trailing whitespace is ignored per the gfm spec | |
cells[i] = cells[i].trim().replace(/\\\|/g, '|'); | |
} | |
return cells; | |
} | |
/** | |
* Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). | |
* /c*$/ is vulnerable to REDOS. | |
* | |
* @param str | |
* @param c | |
* @param invert Remove suffix of non-c chars instead. Default falsey. | |
*/ | |
function rtrim(str, c, invert) { | |
const l = str.length; | |
if (l === 0) { | |
return ''; | |
} | |
// Length of suffix matching the invert condition. | |
let suffLen = 0; | |
// Step left until we fail to match the invert condition. | |
while (suffLen < l) { | |
const currChar = str.charAt(l - suffLen - 1); | |
if (currChar === c && !invert) { | |
suffLen++; | |
} | |
else if (currChar !== c && invert) { | |
suffLen++; | |
} | |
else { | |
break; | |
} | |
} | |
return str.slice(0, l - suffLen); | |
} | |
function findClosingBracket(str, b) { | |
if (str.indexOf(b[1]) === -1) { | |
return -1; | |
} | |
let level = 0; | |
for (let i = 0; i < str.length; i++) { | |
if (str[i] === '\\') { | |
i++; | |
} | |
else if (str[i] === b[0]) { | |
level++; | |
} | |
else if (str[i] === b[1]) { | |
level--; | |
if (level < 0) { | |
return i; | |
} | |
} | |
} | |
return -1; | |
} | |
function outputLink(cap, link, raw, lexer) { | |
const href = link.href; | |
const title = link.title ? escape(link.title) : null; | |
const text = cap[1].replace(/\\([\[\]])/g, '$1'); | |
if (cap[0].charAt(0) !== '!') { | |
lexer.state.inLink = true; | |
const token = { | |
type: 'link', | |
raw, | |
href, | |
title, | |
text, | |
tokens: lexer.inlineTokens(text) | |
}; | |
lexer.state.inLink = false; | |
return token; | |
} | |
return { | |
type: 'image', | |
raw, | |
href, | |
title, | |
text: escape(text) | |
}; | |
} | |
function indentCodeCompensation(raw, text) { | |
const matchIndentToCode = raw.match(/^(\s+)(?:```)/); | |
if (matchIndentToCode === null) { | |
return text; | |
} | |
const indentToCode = matchIndentToCode[1]; | |
return text | |
.split('\n') | |
.map(node => { | |
const matchIndentInNode = node.match(/^\s+/); | |
if (matchIndentInNode === null) { | |
return node; | |
} | |
const [indentInNode] = matchIndentInNode; | |
if (indentInNode.length >= indentToCode.length) { | |
return node.slice(indentToCode.length); | |
} | |
return node; | |
}) | |
.join('\n'); | |
} | |
/** | |
* Tokenizer | |
*/ | |
class _Tokenizer { | |
options; | |
// TODO: Fix this rules type | |
rules; | |
lexer; | |
constructor(options) { | |
this.options = options || _defaults; | |
} | |
space(src) { | |
const cap = this.rules.block.newline.exec(src); | |
if (cap && cap[0].length > 0) { | |
return { | |
type: 'space', | |
raw: cap[0] | |
}; | |
} | |
} | |
code(src) { | |
const cap = this.rules.block.code.exec(src); | |
if (cap) { | |
const text = cap[0].replace(/^ {1,4}/gm, ''); | |
return { | |
type: 'code', | |
raw: cap[0], | |
codeBlockStyle: 'indented', | |
text: !this.options.pedantic | |
? rtrim(text, '\n') | |
: text | |
}; | |
} | |
} | |
fences(src) { | |
const cap = this.rules.block.fences.exec(src); | |
if (cap) { | |
const raw = cap[0]; | |
const text = indentCodeCompensation(raw, cap[3] || ''); | |
return { | |
type: 'code', | |
raw, | |
lang: cap[2] ? cap[2].trim().replace(this.rules.inline._escapes, '$1') : cap[2], | |
text | |
}; | |
} | |
} | |
heading(src) { | |
const cap = this.rules.block.heading.exec(src); | |
if (cap) { | |
let text = cap[2].trim(); | |
// remove trailing #s | |
if (/#$/.test(text)) { | |
const trimmed = rtrim(text, '#'); | |
if (this.options.pedantic) { | |
text = trimmed.trim(); | |
} | |
else if (!trimmed || / $/.test(trimmed)) { | |
// CommonMark requires space before trailing #s | |
text = trimmed.trim(); | |
} | |
} | |
return { | |
type: 'heading', | |
raw: cap[0], | |
depth: cap[1].length, | |
text, | |
tokens: this.lexer.inline(text) | |
}; | |
} | |
} | |
hr(src) { | |
const cap = this.rules.block.hr.exec(src); | |
if (cap) { | |
return { | |
type: 'hr', | |
raw: cap[0] | |
}; | |
} | |
} | |
blockquote(src) { | |
const cap = this.rules.block.blockquote.exec(src); | |
if (cap) { | |
const text = cap[0].replace(/^ *>[ \t]?/gm, ''); | |
const top = this.lexer.state.top; | |
this.lexer.state.top = true; | |
const tokens = this.lexer.blockTokens(text); | |
this.lexer.state.top = top; | |
return { | |
type: 'blockquote', | |
raw: cap[0], | |
tokens, | |
text | |
}; | |
} | |
} | |
list(src) { | |
let cap = this.rules.block.list.exec(src); | |
if (cap) { | |
let bull = cap[1].trim(); | |
const isordered = bull.length > 1; | |
const list = { | |
type: 'list', | |
raw: '', | |
ordered: isordered, | |
start: isordered ? +bull.slice(0, -1) : '', | |
loose: false, | |
items: [] | |
}; | |
bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`; | |
if (this.options.pedantic) { | |
bull = isordered ? bull : '[*+-]'; | |
} | |
// Get next list item | |
const itemRegex = new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`); | |
let raw = ''; | |
let itemContents = ''; | |
let endsWithBlankLine = false; | |
// Check if current bullet point can start a new List Item | |
while (src) { | |
let endEarly = false; | |
if (!(cap = itemRegex.exec(src))) { | |
break; | |
} | |
if (this.rules.block.hr.test(src)) { // End list if bullet was actually HR (possibly move into itemRegex?) | |
break; | |
} | |
raw = cap[0]; | |
src = src.substring(raw.length); | |
let line = cap[2].split('\n', 1)[0].replace(/^\t+/, (t) => ' '.repeat(3 * t.length)); | |
let nextLine = src.split('\n', 1)[0]; | |
let indent = 0; | |
if (this.options.pedantic) { | |
indent = 2; | |
itemContents = line.trimStart(); | |
} | |
else { | |
indent = cap[2].search(/[^ ]/); // Find first non-space char | |
indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent | |
itemContents = line.slice(indent); | |
indent += cap[1].length; | |
} | |
let blankLine = false; | |
if (!line && /^ *$/.test(nextLine)) { // Items begin with at most one blank line | |
raw += nextLine + '\n'; | |
src = src.substring(nextLine.length + 1); | |
endEarly = true; | |
} | |
if (!endEarly) { | |
const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`); | |
const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`); | |
const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`); | |
const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`); | |
// Check if following lines should be included in List Item | |
while (src) { | |
const rawLine = src.split('\n', 1)[0]; | |
nextLine = rawLine; | |
// Re-align to follow commonmark nesting rules | |
if (this.options.pedantic) { | |
nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); | |
} | |
// End list item if found code fences | |
if (fencesBeginRegex.test(nextLine)) { | |
break; | |
} | |
// End list item if found start of new heading | |
if (headingBeginRegex.test(nextLine)) { | |
break; | |
} | |
// End list item if found start of new bullet | |
if (nextBulletRegex.test(nextLine)) { | |
break; | |
} | |
// Horizontal rule found | |
if (hrRegex.test(src)) { | |
break; | |
} | |
if (nextLine.search(/[^ ]/) >= indent || !nextLine.trim()) { // Dedent if possible | |
itemContents += '\n' + nextLine.slice(indent); | |
} | |
else { | |
// not enough indentation | |
if (blankLine) { | |
break; | |
} | |
// paragraph continuation unless last line was a different block level element | |
if (line.search(/[^ ]/) >= 4) { // indented code block | |
break; | |
} | |
if (fencesBeginRegex.test(line)) { | |
break; | |
} | |
if (headingBeginRegex.test(line)) { | |
break; | |
} | |
if (hrRegex.test(line)) { | |
break; | |
} | |
itemContents += '\n' + nextLine; | |
} | |
if (!blankLine && !nextLine.trim()) { // Check if current line is blank | |
blankLine = true; | |
} | |
raw += rawLine + '\n'; | |
src = src.substring(rawLine.length + 1); | |
line = nextLine.slice(indent); | |
} | |
} | |
if (!list.loose) { | |
// If the previous item ended with a blank line, the list is loose | |
if (endsWithBlankLine) { | |
list.loose = true; | |
} | |
else if (/\n *\n *$/.test(raw)) { | |
endsWithBlankLine = true; | |
} | |
} | |
let istask = null; | |
let ischecked; | |
// Check for task list items | |
if (this.options.gfm) { | |
istask = /^\[[ xX]\] /.exec(itemContents); | |
if (istask) { | |
ischecked = istask[0] !== '[ ] '; | |
itemContents = itemContents.replace(/^\[[ xX]\] +/, ''); | |
} | |
} | |
list.items.push({ | |
type: 'list_item', | |
raw, | |
task: !!istask, | |
checked: ischecked, | |
loose: false, | |
text: itemContents, | |
tokens: [] | |
}); | |
list.raw += raw; | |
} | |
// Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic | |
list.items[list.items.length - 1].raw = raw.trimEnd(); | |
list.items[list.items.length - 1].text = itemContents.trimEnd(); | |
list.raw = list.raw.trimEnd(); | |
// Item child tokens handled here at end because we needed to have the final item to trim it first | |
for (let i = 0; i < list.items.length; i++) { | |
this.lexer.state.top = false; | |
list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []); | |
if (!list.loose) { | |
// Check if list should be loose | |
const spacers = list.items[i].tokens.filter(t => t.type === 'space'); | |
const hasMultipleLineBreaks = spacers.length > 0 && spacers.some(t => /\n.*\n/.test(t.raw)); | |
list.loose = hasMultipleLineBreaks; | |
} | |
} | |
// Set all items to loose if list is loose | |
if (list.loose) { | |
for (let i = 0; i < list.items.length; i++) { | |
list.items[i].loose = true; | |
} | |
} | |
return list; | |
} | |
} | |
html(src) { | |
const cap = this.rules.block.html.exec(src); | |
if (cap) { | |
const token = { | |
type: 'html', | |
block: true, | |
raw: cap[0], | |
pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', | |
text: cap[0] | |
}; | |
return token; | |
} | |
} | |
def(src) { | |
const cap = this.rules.block.def.exec(src); | |
if (cap) { | |
const tag = cap[1].toLowerCase().replace(/\s+/g, ' '); | |
const href = cap[2] ? cap[2].replace(/^<(.*)>$/, '$1').replace(this.rules.inline._escapes, '$1') : ''; | |
const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline._escapes, '$1') : cap[3]; | |
return { | |
type: 'def', | |
tag, | |
raw: cap[0], | |
href, | |
title | |
}; | |
} | |
} | |
table(src) { | |
const cap = this.rules.block.table.exec(src); | |
if (cap) { | |
if (!/[:|]/.test(cap[2])) { | |
// delimiter row must have a pipe (|) or colon (:) otherwise it is a setext heading | |
return; | |
} | |
const item = { | |
type: 'table', | |
raw: cap[0], | |
header: splitCells(cap[1]).map(c => { | |
return { text: c, tokens: [] }; | |
}), | |
align: cap[2].replace(/^\||\| *$/g, '').split('|'), | |
rows: cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : [] | |
}; | |
if (item.header.length === item.align.length) { | |
let l = item.align.length; | |
let i, j, k, row; | |
for (i = 0; i < l; i++) { | |
const align = item.align[i]; | |
if (align) { | |
if (/^ *-+: *$/.test(align)) { | |
item.align[i] = 'right'; | |
} | |
else if (/^ *:-+: *$/.test(align)) { | |
item.align[i] = 'center'; | |
} | |
else if (/^ *:-+ *$/.test(align)) { | |
item.align[i] = 'left'; | |
} | |
else { | |
item.align[i] = null; | |
} | |
} | |
} | |
l = item.rows.length; | |
for (i = 0; i < l; i++) { | |
item.rows[i] = splitCells(item.rows[i], item.header.length).map(c => { | |
return { text: c, tokens: [] }; | |
}); | |
} | |
// parse child tokens inside headers and cells | |
// header child tokens | |
l = item.header.length; | |
for (j = 0; j < l; j++) { | |
item.header[j].tokens = this.lexer.inline(item.header[j].text); | |
} | |
// cell child tokens | |
l = item.rows.length; | |
for (j = 0; j < l; j++) { | |
row = item.rows[j]; | |
for (k = 0; k < row.length; k++) { | |
row[k].tokens = this.lexer.inline(row[k].text); | |
} | |
} | |
return item; | |
} | |
} | |
} | |
lheading(src) { | |
const cap = this.rules.block.lheading.exec(src); | |
if (cap) { | |
return { | |
type: 'heading', | |
raw: cap[0], | |
depth: cap[2].charAt(0) === '=' ? 1 : 2, | |
text: cap[1], | |
tokens: this.lexer.inline(cap[1]) | |
}; | |
} | |
} | |
paragraph(src) { | |
const cap = this.rules.block.paragraph.exec(src); | |
if (cap) { | |
const text = cap[1].charAt(cap[1].length - 1) === '\n' | |
? cap[1].slice(0, -1) | |
: cap[1]; | |
return { | |
type: 'paragraph', | |
raw: cap[0], | |
text, | |
tokens: this.lexer.inline(text) | |
}; | |
} | |
} | |
text(src) { | |
const cap = this.rules.block.text.exec(src); | |
if (cap) { | |
return { | |
type: 'text', | |
raw: cap[0], | |
text: cap[0], | |
tokens: this.lexer.inline(cap[0]) | |
}; | |
} | |
} | |
escape(src) { | |
const cap = this.rules.inline.escape.exec(src); | |
if (cap) { | |
return { | |
type: 'escape', | |
raw: cap[0], | |
text: escape(cap[1]) | |
}; | |
} | |
} | |
tag(src) { | |
const cap = this.rules.inline.tag.exec(src); | |
if (cap) { | |
if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) { | |
this.lexer.state.inLink = true; | |
} | |
else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) { | |
this.lexer.state.inLink = false; | |
} | |
if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { | |
this.lexer.state.inRawBlock = true; | |
} | |
else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { | |
this.lexer.state.inRawBlock = false; | |
} | |
return { | |
type: 'html', | |
raw: cap[0], | |
inLink: this.lexer.state.inLink, | |
inRawBlock: this.lexer.state.inRawBlock, | |
block: false, | |
text: cap[0] | |
}; | |
} | |
} | |
link(src) { | |
const cap = this.rules.inline.link.exec(src); | |
if (cap) { | |
const trimmedUrl = cap[2].trim(); | |
if (!this.options.pedantic && /^</.test(trimmedUrl)) { | |
// commonmark requires matching angle brackets | |
if (!(/>$/.test(trimmedUrl))) { | |
return; | |
} | |
// ending angle bracket cannot be escaped | |
const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\'); | |
if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { | |
return; | |
} | |
} | |
else { | |
// find closing parenthesis | |
const lastParenIndex = findClosingBracket(cap[2], '()'); | |
if (lastParenIndex > -1) { | |
const start = cap[0].indexOf('!') === 0 ? 5 : 4; | |
const linkLen = start + cap[1].length + lastParenIndex; | |
cap[2] = cap[2].substring(0, lastParenIndex); | |
cap[0] = cap[0].substring(0, linkLen).trim(); | |
cap[3] = ''; | |
} | |
} | |
let href = cap[2]; | |
let title = ''; | |
if (this.options.pedantic) { | |
// split pedantic href and title | |
const link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); | |
if (link) { | |
href = link[1]; | |
title = link[3]; | |
} | |
} | |
else { | |
title = cap[3] ? cap[3].slice(1, -1) : ''; | |
} | |
href = href.trim(); | |
if (/^</.test(href)) { | |
if (this.options.pedantic && !(/>$/.test(trimmedUrl))) { | |
// pedantic allows starting angle bracket without ending angle bracket | |
href = href.slice(1); | |
} | |
else { | |
href = href.slice(1, -1); | |
} | |
} | |
return outputLink(cap, { | |
href: href ? href.replace(this.rules.inline._escapes, '$1') : href, | |
title: title ? title.replace(this.rules.inline._escapes, '$1') : title | |
}, cap[0], this.lexer); | |
} | |
} | |
reflink(src, links) { | |
let cap; | |
if ((cap = this.rules.inline.reflink.exec(src)) | |
|| (cap = this.rules.inline.nolink.exec(src))) { | |
let link = (cap[2] || cap[1]).replace(/\s+/g, ' '); | |
link = links[link.toLowerCase()]; | |
if (!link) { | |
const text = cap[0].charAt(0); | |
return { | |
type: 'text', | |
raw: text, | |
text | |
}; | |
} | |
return outputLink(cap, link, cap[0], this.lexer); | |
} | |
} | |
emStrong(src, maskedSrc, prevChar = '') { | |
let match = this.rules.inline.emStrong.lDelim.exec(src); | |
if (!match) | |
return; | |
// _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well | |
if (match[3] && prevChar.match(/[\p{L}\p{N}]/u)) | |
return; | |
const nextChar = match[1] || match[2] || ''; | |
if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) { | |
// unicode Regex counts emoji as 1 char; spread into array for proper count (used multiple times below) | |
const lLength = [...match[0]].length - 1; | |
let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0; | |
const endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd; | |
endReg.lastIndex = 0; | |
// Clip maskedSrc to same section of string as src (move to lexer?) | |
maskedSrc = maskedSrc.slice(-1 * src.length + match[0].length - 1); | |
while ((match = endReg.exec(maskedSrc)) != null) { | |
rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6]; | |
if (!rDelim) | |
continue; // skip single * in __abc*abc__ | |
rLength = [...rDelim].length; | |
if (match[3] || match[4]) { // found another Left Delim | |
delimTotal += rLength; | |
continue; | |
} | |
else if (match[5] || match[6]) { // either Left or Right Delim | |
if (lLength % 3 && !((lLength + rLength) % 3)) { | |
midDelimTotal += rLength; | |
continue; // CommonMark Emphasis Rules 9-10 | |
} | |
} | |
delimTotal -= rLength; | |
if (delimTotal > 0) | |
continue; // Haven't found enough closing delimiters | |
// Remove extra characters. *a*** -> *a* | |
rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); | |
const raw = [...src].slice(0, lLength + match.index + rLength + 1).join(''); | |
// Create `em` if smallest delimiter has odd char count. *a*** | |
if (Math.min(lLength, rLength) % 2) { | |
const text = raw.slice(1, -1); | |
return { | |
type: 'em', | |
raw, | |
text, | |
tokens: this.lexer.inlineTokens(text) | |
}; | |
} | |
// Create 'strong' if smallest delimiter has even char count. **a*** | |
const text = raw.slice(2, -2); | |
return { | |
type: 'strong', | |
raw, | |
text, | |
tokens: this.lexer.inlineTokens(text) | |
}; | |
} | |
} | |
} | |
codespan(src) { | |
const cap = this.rules.inline.code.exec(src); | |
if (cap) { | |
let text = cap[2].replace(/\n/g, ' '); | |
const hasNonSpaceChars = /[^ ]/.test(text); | |
const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text); | |
if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { | |
text = text.substring(1, text.length - 1); | |
} | |
text = escape(text, true); | |
return { | |
type: 'codespan', | |
raw: cap[0], | |
text | |
}; | |
} | |
} | |
br(src) { | |
const cap = this.rules.inline.br.exec(src); | |
if (cap) { | |
return { | |
type: 'br', | |
raw: cap[0] | |
}; | |
} | |
} | |
del(src) { | |
const cap = this.rules.inline.del.exec(src); | |
if (cap) { | |
return { | |
type: 'del', | |
raw: cap[0], | |
text: cap[2], | |
tokens: this.lexer.inlineTokens(cap[2]) | |
}; | |
} | |
} | |
autolink(src) { | |
const cap = this.rules.inline.autolink.exec(src); | |
if (cap) { | |
let text, href; | |
if (cap[2] === '@') { | |
text = escape(cap[1]); | |
href = 'mailto:' + text; | |
} | |
else { | |
text = escape(cap[1]); | |
href = text; | |
} | |
return { | |
type: 'link', | |
raw: cap[0], | |
text, | |
href, | |
tokens: [ | |
{ | |
type: 'text', | |
raw: text, | |
text | |
} | |
] | |
}; | |
} | |
} | |
url(src) { | |
let cap; | |
if (cap = this.rules.inline.url.exec(src)) { | |
let text, href; | |
if (cap[2] === '@') { | |
text = escape(cap[0]); | |
href = 'mailto:' + text; | |
} | |
else { | |
// do extended autolink path validation | |
let prevCapZero; | |
do { | |
prevCapZero = cap[0]; | |
cap[0] = this.rules.inline._backpedal.exec(cap[0])[0]; | |
} while (prevCapZero !== cap[0]); | |
text = escape(cap[0]); | |
if (cap[1] === 'www.') { | |
href = 'http://' + cap[0]; | |
} | |
else { | |
href = cap[0]; | |
} | |
} | |
return { | |
type: 'link', | |
raw: cap[0], | |
text, | |
href, | |
tokens: [ | |
{ | |
type: 'text', | |
raw: text, | |
text | |
} | |
] | |
}; | |
} | |
} | |
inlineText(src) { | |
const cap = this.rules.inline.text.exec(src); | |
if (cap) { | |
let text; | |
if (this.lexer.state.inRawBlock) { | |
text = cap[0]; | |
} | |
else { | |
text = escape(cap[0]); | |
} | |
return { | |
type: 'text', | |
raw: cap[0], | |
text | |
}; | |
} | |
} | |
} | |
/** | |
* Block-Level Grammar | |
*/ | |
// Not all rules are defined in the object literal | |
// @ts-expect-error | |
const block = { | |
newline: /^(?: *(?:\n|$))+/, | |
code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/, | |
fences: /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/, | |
hr: /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/, | |
heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, | |
blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, | |
list: /^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/, | |
html: '^ {0,3}(?:' // optional indentation | |
+ '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1) | |
+ '|comment[^\\n]*(\\n+|$)' // (2) | |
+ '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3) | |
+ '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4) | |
+ '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5) | |
+ '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6) | |
+ '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag | |
+ '|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag | |
+ ')', | |
def: /^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/, | |
table: noopTest, | |
lheading: /^(?!bull )((?:.|\n(?!\s*?\n|bull ))+?)\n {0,3}(=+|-+) *(?:\n+|$)/, | |
// regex template, placeholders will be replaced according to different paragraph | |
// interruption rules of commonmark and the original markdown spec: | |
_paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/, | |
text: /^[^\n]+/ | |
}; | |
block._label = /(?!\s*\])(?:\\.|[^\[\]\\])+/; | |
block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; | |
block.def = edit(block.def) | |
.replace('label', block._label) | |
.replace('title', block._title) | |
.getRegex(); | |
block.bullet = /(?:[*+-]|\d{1,9}[.)])/; | |
block.listItemStart = edit(/^( *)(bull) */) | |
.replace('bull', block.bullet) | |
.getRegex(); | |
block.list = edit(block.list) | |
.replace(/bull/g, block.bullet) | |
.replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') | |
.replace('def', '\\n+(?=' + block.def.source + ')') | |
.getRegex(); | |
block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' | |
+ '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' | |
+ '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' | |
+ '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' | |
+ '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' | |
+ '|track|ul'; | |
block._comment = /<!--(?!-?>)[\s\S]*?(?:-->|$)/; | |
block.html = edit(block.html, 'i') | |
.replace('comment', block._comment) | |
.replace('tag', block._tag) | |
.replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) | |
.getRegex(); | |
block.lheading = edit(block.lheading) | |
.replace(/bull/g, block.bullet) // lists can interrupt | |
.getRegex(); | |
block.paragraph = edit(block._paragraph) | |
.replace('hr', block.hr) | |
.replace('heading', ' {0,3}#{1,6} ') | |
.replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs | |
.replace('|table', '') | |
.replace('blockquote', ' {0,3}>') | |
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') | |
.replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt | |
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)') | |
.replace('tag', block._tag) // pars can be interrupted by type (6) html blocks | |
.getRegex(); | |
block.blockquote = edit(block.blockquote) | |
.replace('paragraph', block.paragraph) | |
.getRegex(); | |
/** | |
* Normal Block Grammar | |
*/ | |
block.normal = { ...block }; | |
/** | |
* GFM Block Grammar | |
*/ | |
block.gfm = { | |
...block.normal, | |
table: '^ *([^\\n ].*)\\n' // Header | |
+ ' {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)' // Align | |
+ '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells | |
}; | |
block.gfm.table = edit(block.gfm.table) | |
.replace('hr', block.hr) | |
.replace('heading', ' {0,3}#{1,6} ') | |
.replace('blockquote', ' {0,3}>') | |
.replace('code', ' {4}[^\\n]') | |
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') | |
.replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt | |
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)') | |
.replace('tag', block._tag) // tables can be interrupted by type (6) html blocks | |
.getRegex(); | |
block.gfm.paragraph = edit(block._paragraph) | |
.replace('hr', block.hr) | |
.replace('heading', ' {0,3}#{1,6} ') | |
.replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs | |
.replace('table', block.gfm.table) // interrupt paragraphs with table | |
.replace('blockquote', ' {0,3}>') | |
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') | |
.replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt | |
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)') | |
.replace('tag', block._tag) // pars can be interrupted by type (6) html blocks | |
.getRegex(); | |
/** | |
* Pedantic grammar (original John Gruber's loose markdown specification) | |
*/ | |
block.pedantic = { | |
...block.normal, | |
html: edit('^ *(?:comment *(?:\\n|\\s*$)' | |
+ '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag | |
+ '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') | |
.replace('comment', block._comment) | |
.replace(/tag/g, '(?!(?:' | |
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' | |
+ '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' | |
+ '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') | |
.getRegex(), | |
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, | |
heading: /^(#{1,6})(.*)(?:\n+|$)/, | |
fences: noopTest, | |
lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, | |
paragraph: edit(block.normal._paragraph) | |
.replace('hr', block.hr) | |
.replace('heading', ' *#{1,6} *[^\n]') | |
.replace('lheading', block.lheading) | |
.replace('blockquote', ' {0,3}>') | |
.replace('|fences', '') | |
.replace('|list', '') | |
.replace('|html', '') | |
.getRegex() | |
}; | |
/** | |
* Inline-Level Grammar | |
*/ | |
// Not all rules are defined in the object literal | |
// @ts-expect-error | |
const inline = { | |
escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, | |
autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, | |
url: noopTest, | |
tag: '^comment' | |
+ '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag | |
+ '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag | |
+ '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?> | |
+ '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html> | |
+ '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', | |
link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/, | |
reflink: /^!?\[(label)\]\[(ref)\]/, | |
nolink: /^!?\[(ref)\](?:\[\])?/, | |
reflinkSearch: 'reflink|nolink(?!\\()', | |
emStrong: { | |
lDelim: /^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, | |
// (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right. | |
// | Skip orphan inside strong | Consume to delim | (1) #*** | (2) a***#, a*** | (3) #***a, ***a | (4) ***# | (5) #***# | (6) a***a | |
rDelimAst: /^[^_*]*?__[^_*]*?\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\*)[punct](\*+)(?=[\s]|$)|[^punct\s](\*+)(?!\*)(?=[punct\s]|$)|(?!\*)[punct\s](\*+)(?=[^punct\s])|[\s](\*+)(?!\*)(?=[punct])|(?!\*)[punct](\*+)(?!\*)(?=[punct])|[^punct\s](\*+)(?=[^punct\s])/, | |
rDelimUnd: /^[^_*]*?\*\*[^_*]*?_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\s]|$)|[^punct\s](_+)(?!_)(?=[punct\s]|$)|(?!_)[punct\s](_+)(?=[^punct\s])|[\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])/ // ^- Not allowed for _ | |
}, | |
code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, | |
br: /^( {2,}|\\)\n(?!\s*$)/, | |
del: noopTest, | |
text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/, | |
punctuation: /^((?![*_])[\spunctuation])/ | |
}; | |
// list of unicode punctuation marks, plus any missing characters from CommonMark spec | |
inline._punctuation = '\\p{P}$+<=>`^|~'; | |
inline.punctuation = edit(inline.punctuation, 'u').replace(/punctuation/g, inline._punctuation).getRegex(); | |
// sequences em should skip over [title](link), `code`, <html> | |
inline.blockSkip = /\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g; | |
inline.anyPunctuation = /\\[punct]/g; | |
inline._escapes = /\\([punct])/g; | |
inline._comment = edit(block._comment).replace('(?:-->|$)', '-->').getRegex(); | |
inline.emStrong.lDelim = edit(inline.emStrong.lDelim, 'u') | |
.replace(/punct/g, inline._punctuation) | |
.getRegex(); | |
inline.emStrong.rDelimAst = edit(inline.emStrong.rDelimAst, 'gu') | |
.replace(/punct/g, inline._punctuation) | |
.getRegex(); | |
inline.emStrong.rDelimUnd = edit(inline.emStrong.rDelimUnd, 'gu') | |
.replace(/punct/g, inline._punctuation) | |
.getRegex(); | |
inline.anyPunctuation = edit(inline.anyPunctuation, 'gu') | |
.replace(/punct/g, inline._punctuation) | |
.getRegex(); | |
inline._escapes = edit(inline._escapes, 'gu') | |
.replace(/punct/g, inline._punctuation) | |
.getRegex(); | |
inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; | |
inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; | |
inline.autolink = edit(inline.autolink) | |
.replace('scheme', inline._scheme) | |
.replace('email', inline._email) | |
.getRegex(); | |
inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; | |
inline.tag = edit(inline.tag) | |
.replace('comment', inline._comment) | |
.replace('attribute', inline._attribute) | |
.getRegex(); | |
inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; | |
inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/; | |
inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; | |
inline.link = edit(inline.link) | |
.replace('label', inline._label) | |
.replace('href', inline._href) | |
.replace('title', inline._title) | |
.getRegex(); | |
inline.reflink = edit(inline.reflink) | |
.replace('label', inline._label) | |
.replace('ref', block._label) | |
.getRegex(); | |
inline.nolink = edit(inline.nolink) | |
.replace('ref', block._label) | |
.getRegex(); | |
inline.reflinkSearch = edit(inline.reflinkSearch, 'g') | |
.replace('reflink', inline.reflink) | |
.replace('nolink', inline.nolink) | |
.getRegex(); | |
/** | |
* Normal Inline Grammar | |
*/ | |
inline.normal = { ...inline }; | |
/** | |
* Pedantic Inline Grammar | |
*/ | |
inline.pedantic = { | |
...inline.normal, | |
strong: { | |
start: /^__|\*\*/, | |
middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, | |
endAst: /\*\*(?!\*)/g, | |
endUnd: /__(?!_)/g | |
}, | |
em: { | |
start: /^_|\*/, | |
middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/, | |
endAst: /\*(?!\*)/g, | |
endUnd: /_(?!_)/g | |
}, | |
link: edit(/^!?\[(label)\]\((.*?)\)/) | |
.replace('label', inline._label) | |
.getRegex(), | |
reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) | |
.replace('label', inline._label) | |
.getRegex() | |
}; | |
/** | |
* GFM Inline Grammar | |
*/ | |
inline.gfm = { | |
...inline.normal, | |
escape: edit(inline.escape).replace('])', '~|])').getRegex(), | |
_extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, | |
url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, | |
_backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, | |
del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/, | |
text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/ | |
}; | |
inline.gfm.url = edit(inline.gfm.url, 'i') | |
.replace('email', inline.gfm._extended_email) | |
.getRegex(); | |
/** | |
* GFM + Line Breaks Inline Grammar | |
*/ | |
inline.breaks = { | |
...inline.gfm, | |
br: edit(inline.br).replace('{2,}', '*').getRegex(), | |
text: edit(inline.gfm.text) | |
.replace('\\b_', '\\b_| {2,}\\n') | |
.replace(/\{2,\}/g, '*') | |
.getRegex() | |
}; | |
/** | |
* Block Lexer | |
*/ | |
class _Lexer { | |
tokens; | |
options; | |
state; | |
tokenizer; | |
inlineQueue; | |
constructor(options) { | |
// TokenList cannot be created in one go | |
// @ts-expect-error | |
this.tokens = []; | |
this.tokens.links = Object.create(null); | |
this.options = options || _defaults; | |
this.options.tokenizer = this.options.tokenizer || new _Tokenizer(); | |
this.tokenizer = this.options.tokenizer; | |
this.tokenizer.options = this.options; | |
this.tokenizer.lexer = this; | |
this.inlineQueue = []; | |
this.state = { | |
inLink: false, | |
inRawBlock: false, | |
top: true | |
}; | |
const rules = { | |
block: block.normal, | |
inline: inline.normal | |
}; | |
if (this.options.pedantic) { | |
rules.block = block.pedantic; | |
rules.inline = inline.pedantic; | |
} | |
else if (this.options.gfm) { | |
rules.block = block.gfm; | |
if (this.options.breaks) { | |
rules.inline = inline.breaks; | |
} | |
else { | |
rules.inline = inline.gfm; | |
} | |
} | |
this.tokenizer.rules = rules; | |
} | |
/** | |
* Expose Rules | |
*/ | |
static get rules() { | |
return { | |
block, | |
inline | |
}; | |
} | |
/** | |
* Static Lex Method | |
*/ | |
static lex(src, options) { | |
const lexer = new _Lexer(options); | |
return lexer.lex(src); | |
} | |
/** | |
* Static Lex Inline Method | |
*/ | |
static lexInline(src, options) { | |
const lexer = new _Lexer(options); | |
return lexer.inlineTokens(src); | |
} | |
/** | |
* Preprocessing | |
*/ | |
lex(src) { | |
src = src | |
.replace(/\r\n|\r/g, '\n'); | |
this.blockTokens(src, this.tokens); | |
let next; | |
while (next = this.inlineQueue.shift()) { | |
this.inlineTokens(next.src, next.tokens); | |
} | |
return this.tokens; | |
} | |
blockTokens(src, tokens = []) { | |
if (this.options.pedantic) { | |
src = src.replace(/\t/g, ' ').replace(/^ +$/gm, ''); | |
} | |
else { | |
src = src.replace(/^( *)(\t+)/gm, (_, leading, tabs) => { | |
return leading + ' '.repeat(tabs.length); | |
}); | |
} | |
let token; | |
let lastToken; | |
let cutSrc; | |
let lastParagraphClipped; | |
while (src) { | |
if (this.options.extensions | |
&& this.options.extensions.block | |
&& this.options.extensions.block.some((extTokenizer) => { | |
if (token = extTokenizer.call({ lexer: this }, src, tokens)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
return true; | |
} | |
return false; | |
})) { | |
continue; | |
} | |
// newline | |
if (token = this.tokenizer.space(src)) { | |
src = src.substring(token.raw.length); | |
if (token.raw.length === 1 && tokens.length > 0) { | |
// if there's a single \n as a spacer, it's terminating the last line, | |
// so move it there so that we don't get unecessary paragraph tags | |
tokens[tokens.length - 1].raw += '\n'; | |
} | |
else { | |
tokens.push(token); | |
} | |
continue; | |
} | |
// code | |
if (token = this.tokenizer.code(src)) { | |
src = src.substring(token.raw.length); | |
lastToken = tokens[tokens.length - 1]; | |
// An indented code block cannot interrupt a paragraph. | |
if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { | |
lastToken.raw += '\n' + token.raw; | |
lastToken.text += '\n' + token.text; | |
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; | |
} | |
else { | |
tokens.push(token); | |
} | |
continue; | |
} | |
// fences | |
if (token = this.tokenizer.fences(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// heading | |
if (token = this.tokenizer.heading(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// hr | |
if (token = this.tokenizer.hr(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// blockquote | |
if (token = this.tokenizer.blockquote(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// list | |
if (token = this.tokenizer.list(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// html | |
if (token = this.tokenizer.html(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// def | |
if (token = this.tokenizer.def(src)) { | |
src = src.substring(token.raw.length); | |
lastToken = tokens[tokens.length - 1]; | |
if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { | |
lastToken.raw += '\n' + token.raw; | |
lastToken.text += '\n' + token.raw; | |
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; | |
} | |
else if (!this.tokens.links[token.tag]) { | |
this.tokens.links[token.tag] = { | |
href: token.href, | |
title: token.title | |
}; | |
} | |
continue; | |
} | |
// table (gfm) | |
if (token = this.tokenizer.table(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// lheading | |
if (token = this.tokenizer.lheading(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// top-level paragraph | |
// prevent paragraph consuming extensions by clipping 'src' to extension start | |
cutSrc = src; | |
if (this.options.extensions && this.options.extensions.startBlock) { | |
let startIndex = Infinity; | |
const tempSrc = src.slice(1); | |
let tempStart; | |
this.options.extensions.startBlock.forEach((getStartIndex) => { | |
tempStart = getStartIndex.call({ lexer: this }, tempSrc); | |
if (typeof tempStart === 'number' && tempStart >= 0) { | |
startIndex = Math.min(startIndex, tempStart); | |
} | |
}); | |
if (startIndex < Infinity && startIndex >= 0) { | |
cutSrc = src.substring(0, startIndex + 1); | |
} | |
} | |
if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) { | |
lastToken = tokens[tokens.length - 1]; | |
if (lastParagraphClipped && lastToken.type === 'paragraph') { | |
lastToken.raw += '\n' + token.raw; | |
lastToken.text += '\n' + token.text; | |
this.inlineQueue.pop(); | |
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; | |
} | |
else { | |
tokens.push(token); | |
} | |
lastParagraphClipped = (cutSrc.length !== src.length); | |
src = src.substring(token.raw.length); | |
continue; | |
} | |
// text | |
if (token = this.tokenizer.text(src)) { | |
src = src.substring(token.raw.length); | |
lastToken = tokens[tokens.length - 1]; | |
if (lastToken && lastToken.type === 'text') { | |
lastToken.raw += '\n' + token.raw; | |
lastToken.text += '\n' + token.text; | |
this.inlineQueue.pop(); | |
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; | |
} | |
else { | |
tokens.push(token); | |
} | |
continue; | |
} | |
if (src) { | |
const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); | |
if (this.options.silent) { | |
console.error(errMsg); | |
break; | |
} | |
else { | |
throw new Error(errMsg); | |
} | |
} | |
} | |
this.state.top = true; | |
return tokens; | |
} | |
inline(src, tokens = []) { | |
this.inlineQueue.push({ src, tokens }); | |
return tokens; | |
} | |
/** | |
* Lexing/Compiling | |
*/ | |
inlineTokens(src, tokens = []) { | |
let token, lastToken, cutSrc; | |
// String with links masked to avoid interference with em and strong | |
let maskedSrc = src; | |
let match; | |
let keepPrevChar, prevChar; | |
// Mask out reflinks | |
if (this.tokens.links) { | |
const links = Object.keys(this.tokens.links); | |
if (links.length > 0) { | |
while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) { | |
if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) { | |
maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); | |
} | |
} | |
} | |
} | |
// Mask out other blocks | |
while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) { | |
maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); | |
} | |
// Mask out escaped characters | |
while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) { | |
maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex); | |
} | |
while (src) { | |
if (!keepPrevChar) { | |
prevChar = ''; | |
} | |
keepPrevChar = false; | |
// extensions | |
if (this.options.extensions | |
&& this.options.extensions.inline | |
&& this.options.extensions.inline.some((extTokenizer) => { | |
if (token = extTokenizer.call({ lexer: this }, src, tokens)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
return true; | |
} | |
return false; | |
})) { | |
continue; | |
} | |
// escape | |
if (token = this.tokenizer.escape(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// tag | |
if (token = this.tokenizer.tag(src)) { | |
src = src.substring(token.raw.length); | |
lastToken = tokens[tokens.length - 1]; | |
if (lastToken && token.type === 'text' && lastToken.type === 'text') { | |
lastToken.raw += token.raw; | |
lastToken.text += token.text; | |
} | |
else { | |
tokens.push(token); | |
} | |
continue; | |
} | |
// link | |
if (token = this.tokenizer.link(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// reflink, nolink | |
if (token = this.tokenizer.reflink(src, this.tokens.links)) { | |
src = src.substring(token.raw.length); | |
lastToken = tokens[tokens.length - 1]; | |
if (lastToken && token.type === 'text' && lastToken.type === 'text') { | |
lastToken.raw += token.raw; | |
lastToken.text += token.text; | |
} | |
else { | |
tokens.push(token); | |
} | |
continue; | |
} | |
// em & strong | |
if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// code | |
if (token = this.tokenizer.codespan(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// br | |
if (token = this.tokenizer.br(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// del (gfm) | |
if (token = this.tokenizer.del(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// autolink | |
if (token = this.tokenizer.autolink(src)) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// url (gfm) | |
if (!this.state.inLink && (token = this.tokenizer.url(src))) { | |
src = src.substring(token.raw.length); | |
tokens.push(token); | |
continue; | |
} | |
// text | |
// prevent inlineText consuming extensions by clipping 'src' to extension start | |
cutSrc = src; | |
if (this.options.extensions && this.options.extensions.startInline) { | |
let startIndex = Infinity; | |
const tempSrc = src.slice(1); | |
let tempStart; | |
this.options.extensions.startInline.forEach((getStartIndex) => { | |
tempStart = getStartIndex.call({ lexer: this }, tempSrc); | |
if (typeof tempStart === 'number' && tempStart >= 0) { | |
startIndex = Math.min(startIndex, tempStart); | |
} | |
}); | |
if (startIndex < Infinity && startIndex >= 0) { | |
cutSrc = src.substring(0, startIndex + 1); | |
} | |
} | |
if (token = this.tokenizer.inlineText(cutSrc)) { | |
src = src.substring(token.raw.length); | |
if (token.raw.slice(-1) !== '_') { // Track prevChar before string of ____ started | |
prevChar = token.raw.slice(-1); | |
} | |
keepPrevChar = true; | |
lastToken = tokens[tokens.length - 1]; | |
if (lastToken && lastToken.type === 'text') { | |
lastToken.raw += token.raw; | |
lastToken.text += token.text; | |
} | |
else { | |
tokens.push(token); | |
} | |
continue; | |
} | |
if (src) { | |
const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); | |
if (this.options.silent) { | |
console.error(errMsg); | |
break; | |
} | |
else { | |
throw new Error(errMsg); | |
} | |
} | |
} | |
return tokens; | |
} | |
} | |
/** | |
* Renderer | |
*/ | |
class _Renderer { | |
options; | |
constructor(options) { | |
this.options = options || _defaults; | |
} | |
code(code, infostring, escaped) { | |
const lang = (infostring || '').match(/^\S*/)?.[0]; | |
code = code.replace(/\n$/, '') + '\n'; | |
if (!lang) { | |
return '<pre><code>' | |
+ (escaped ? code : escape(code, true)) | |
+ '</code></pre>\n'; | |
} | |
return '<pre><code class="language-' | |
+ escape(lang) | |
+ '">' | |
+ (escaped ? code : escape(code, true)) | |
+ '</code></pre>\n'; | |
} | |
blockquote(quote) { | |
return `<blockquote>\n${quote}</blockquote>\n`; | |
} | |
html(html, block) { | |
return html; | |
} | |
heading(text, level, raw) { | |
// ignore IDs | |
return `<h${level}>${text}</h${level}>\n`; | |
} | |
hr() { | |
return '<hr>\n'; | |
} | |
list(body, ordered, start) { | |
const type = ordered ? 'ol' : 'ul'; | |
const startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; | |
return '<' + type + startatt + '>\n' + body + '</' + type + '>\n'; | |
} | |
listitem(text, task, checked) { | |
return `<li>${text}</li>\n`; | |
} | |
checkbox(checked) { | |
return '<input ' | |
+ (checked ? 'checked="" ' : '') | |
+ 'disabled="" type="checkbox">'; | |
} | |
paragraph(text) { | |
return `<p>${text}</p>\n`; | |
} | |
table(header, body) { | |
if (body) | |
body = `<tbody>${body}</tbody>`; | |
return '<table>\n' | |
+ '<thead>\n' | |
+ header | |
+ '</thead>\n' | |
+ body | |
+ '</table>\n'; | |
} | |
tablerow(content) { | |
return `<tr>\n${content}</tr>\n`; | |
} | |
tablecell(content, flags) { | |
const type = flags.header ? 'th' : 'td'; | |
const tag = flags.align | |
? `<${type} align="${flags.align}">` | |
: `<${type}>`; | |
return tag + content + `</${type}>\n`; | |
} | |
/** | |
* span level renderer | |
*/ | |
strong(text) { | |
return `<strong>${text}</strong>`; | |
} | |
em(text) { | |
return `<em>${text}</em>`; | |
} | |
codespan(text) { | |
return `<code>${text}</code>`; | |
} | |
br() { | |
return '<br>'; | |
} | |
del(text) { | |
return `<del>${text}</del>`; | |
} | |
link(href, title, text) { | |
const cleanHref = cleanUrl(href); | |
if (cleanHref === null) { | |
return text; | |
} | |
href = cleanHref; | |
let out = '<a href="' + href + '"'; | |
if (title) { | |
out += ' title="' + title + '"'; | |
} | |
out += '>' + text + '</a>'; | |
return out; | |
} | |
image(href, title, text) { | |
const cleanHref = cleanUrl(href); | |
if (cleanHref === null) { | |
return text; | |
} | |
href = cleanHref; | |
let out = `<img src="${href}" alt="${text}"`; | |
if (title) { | |
out += ` title="${title}"`; | |
} | |
out += '>'; | |
return out; | |
} | |
text(text) { | |
return text; | |
} | |
} | |
/** | |
* TextRenderer | |
* returns only the textual part of the token | |
*/ | |
class _TextRenderer { | |
// no need for block level renderers | |
strong(text) { | |
return text; | |
} | |
em(text) { | |
return text; | |
} | |
codespan(text) { | |
return text; | |
} | |
del(text) { | |
return text; | |
} | |
html(text) { | |
return text; | |
} | |
text(text) { | |
return text; | |
} | |
link(href, title, text) { | |
return '' + text; | |
} | |
image(href, title, text) { | |
return '' + text; | |
} | |
br() { | |
return ''; | |
} | |
} | |
/** | |
* Parsing & Compiling | |
*/ | |
class _Parser { | |
options; | |
renderer; | |
textRenderer; | |
constructor(options) { | |
this.options = options || _defaults; | |
this.options.renderer = this.options.renderer || new _Renderer(); | |
this.renderer = this.options.renderer; | |
this.renderer.options = this.options; | |
this.textRenderer = new _TextRenderer(); | |
} | |
/** | |
* Static Parse Method | |
*/ | |
static parse(tokens, options) { | |
const parser = new _Parser(options); | |
return parser.parse(tokens); | |
} | |
/** | |
* Static Parse Inline Method | |
*/ | |
static parseInline(tokens, options) { | |
const parser = new _Parser(options); | |
return parser.parseInline(tokens); | |
} | |
/** | |
* Parse Loop | |
*/ | |
parse(tokens, top = true) { | |
let out = ''; | |
for (let i = 0; i < tokens.length; i++) { | |
const token = tokens[i]; | |
// Run any renderer extensions | |
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) { | |
const genericToken = token; | |
const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken); | |
if (ret !== false || !['space', 'hr', 'heading', 'code', 'table', 'blockquote', 'list', 'html', 'paragraph', 'text'].includes(genericToken.type)) { | |
out += ret || ''; | |
continue; | |
} | |
} | |
switch (token.type) { | |
case 'space': { | |
continue; | |
} | |
case 'hr': { | |
out += this.renderer.hr(); | |
continue; | |
} | |
case 'heading': { | |
const headingToken = token; | |
out += this.renderer.heading(this.parseInline(headingToken.tokens), headingToken.depth, unescape(this.parseInline(headingToken.tokens, this.textRenderer))); | |
continue; | |
} | |
case 'code': { | |
const codeToken = token; | |
out += this.renderer.code(codeToken.text, codeToken.lang, !!codeToken.escaped); | |
continue; | |
} | |
case 'table': { | |
const tableToken = token; | |
let header = ''; | |
// header | |
let cell = ''; | |
for (let j = 0; j < tableToken.header.length; j++) { | |
cell += this.renderer.tablecell(this.parseInline(tableToken.header[j].tokens), { header: true, align: tableToken.align[j] }); | |
} | |
header += this.renderer.tablerow(cell); | |
let body = ''; | |
for (let j = 0; j < tableToken.rows.length; j++) { | |
const row = tableToken.rows[j]; | |
cell = ''; | |
for (let k = 0; k < row.length; k++) { | |
cell += this.renderer.tablecell(this.parseInline(row[k].tokens), { header: false, align: tableToken.align[k] }); | |
} | |
body += this.renderer.tablerow(cell); | |
} | |
out += this.renderer.table(header, body); | |
continue; | |
} | |
case 'blockquote': { | |
const blockquoteToken = token; | |
const body = this.parse(blockquoteToken.tokens); | |
out += this.renderer.blockquote(body); | |
continue; | |
} | |
case 'list': { | |
const listToken = token; | |
const ordered = listToken.ordered; | |
const start = listToken.start; | |
const loose = listToken.loose; | |
let body = ''; | |
for (let j = 0; j < listToken.items.length; j++) { | |
const item = listToken.items[j]; | |
const checked = item.checked; | |
const task = item.task; | |
let itemBody = ''; | |
if (item.task) { | |
const checkbox = this.renderer.checkbox(!!checked); | |
if (loose) { | |
if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') { | |
item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; | |
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { | |
item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text; | |
} | |
} | |
else { | |
item.tokens.unshift({ | |
type: 'text', | |
text: checkbox + ' ' | |
}); | |
} | |
} | |
else { | |
itemBody += checkbox + ' '; | |
} | |
} | |
itemBody += this.parse(item.tokens, loose); | |
body += this.renderer.listitem(itemBody, task, !!checked); | |
} | |
out += this.renderer.list(body, ordered, start); | |
continue; | |
} | |
case 'html': { | |
const htmlToken = token; | |
out += this.renderer.html(htmlToken.text, htmlToken.block); | |
continue; | |
} | |
case 'paragraph': { | |
const paragraphToken = token; | |
out += this.renderer.paragraph(this.parseInline(paragraphToken.tokens)); | |
continue; | |
} | |
case 'text': { | |
let textToken = token; | |
let body = textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text; | |
while (i + 1 < tokens.length && tokens[i + 1].type === 'text') { | |
textToken = tokens[++i]; | |
body += '\n' + (textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text); | |
} | |
out += top ? this.renderer.paragraph(body) : body; | |
continue; | |
} | |
default: { | |
const errMsg = 'Token with "' + token.type + '" type was not found.'; | |
if (this.options.silent) { | |
console.error(errMsg); | |
return ''; | |
} | |
else { | |
throw new Error(errMsg); | |
} | |
} | |
} | |
} | |
return out; | |
} | |
/** | |
* Parse Inline Tokens | |
*/ | |
parseInline(tokens, renderer) { | |
renderer = renderer || this.renderer; | |
let out = ''; | |
for (let i = 0; i < tokens.length; i++) { | |
const token = tokens[i]; | |
// Run any renderer extensions | |
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) { | |
const ret = this.options.extensions.renderers[token.type].call({ parser: this }, token); | |
if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) { | |
out += ret || ''; | |
continue; | |
} | |
} | |
switch (token.type) { | |
case 'escape': { | |
const escapeToken = token; | |
out += renderer.text(escapeToken.text); | |
break; | |
} | |
case 'html': { | |
const tagToken = token; | |
out += renderer.html(tagToken.text); | |
break; | |
} | |
case 'link': { | |
const linkToken = token; | |
out += renderer.link(linkToken.href, linkToken.title, this.parseInline(linkToken.tokens, renderer)); | |
break; | |
} | |
case 'image': { | |
const imageToken = token; | |
out += renderer.image(imageToken.href, imageToken.title, imageToken.text); | |
break; | |
} | |
case 'strong': { | |
const strongToken = token; | |
out += renderer.strong(this.parseInline(strongToken.tokens, renderer)); | |
break; | |
} | |
case 'em': { | |
const emToken = token; | |
out += renderer.em(this.parseInline(emToken.tokens, renderer)); | |
break; | |
} | |
case 'codespan': { | |
const codespanToken = token; | |
out += renderer.codespan(codespanToken.text); | |
break; | |
} | |
case 'br': { | |
out += renderer.br(); | |
break; | |
} | |
case 'del': { | |
const delToken = token; | |
out += renderer.del(this.parseInline(delToken.tokens, renderer)); | |
break; | |
} | |
case 'text': { | |
const textToken = token; | |
out += renderer.text(textToken.text); | |
break; | |
} | |
default: { | |
const errMsg = 'Token with "' + token.type + '" type was not found.'; | |
if (this.options.silent) { | |
console.error(errMsg); | |
return ''; | |
} | |
else { | |
throw new Error(errMsg); | |
} | |
} | |
} | |
} | |
return out; | |
} | |
} | |
class _Hooks { | |
options; | |
constructor(options) { | |
this.options = options || _defaults; | |
} | |
static passThroughHooks = new Set([ | |
'preprocess', | |
'postprocess' | |
]); | |
/** | |
* Process markdown before marked | |
*/ | |
preprocess(markdown) { | |
return markdown; | |
} | |
/** | |
* Process HTML after marked is finished | |
*/ | |
postprocess(html) { | |
return html; | |
} | |
} | |
class Marked { | |
defaults = _getDefaults(); | |
options = this.setOptions; | |
parse = this.#parseMarkdown(_Lexer.lex, _Parser.parse); | |
parseInline = this.#parseMarkdown(_Lexer.lexInline, _Parser.parseInline); | |
Parser = _Parser; | |
parser = _Parser.parse; | |
Renderer = _Renderer; | |
TextRenderer = _TextRenderer; | |
Lexer = _Lexer; | |
lexer = _Lexer.lex; | |
Tokenizer = _Tokenizer; | |
Hooks = _Hooks; | |
constructor(...args) { | |
this.use(...args); | |
} | |
/** | |
* Run callback for every token | |
*/ | |
walkTokens(tokens, callback) { | |
let values = []; | |
for (const token of tokens) { | |
values = values.concat(callback.call(this, token)); | |
switch (token.type) { | |
case 'table': { | |
const tableToken = token; | |
for (const cell of tableToken.header) { | |
values = values.concat(this.walkTokens(cell.tokens, callback)); | |
} | |
for (const row of tableToken.rows) { | |
for (const cell of row) { | |
values = values.concat(this.walkTokens(cell.tokens, callback)); | |
} | |
} | |
break; | |
} | |
case 'list': { | |
const listToken = token; | |
values = values.concat(this.walkTokens(listToken.items, callback)); | |
break; | |
} | |
default: { | |
const genericToken = token; | |
if (this.defaults.extensions?.childTokens?.[genericToken.type]) { | |
this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => { | |
values = values.concat(this.walkTokens(genericToken[childTokens], callback)); | |
}); | |
} | |
else if (genericToken.tokens) { | |
values = values.concat(this.walkTokens(genericToken.tokens, callback)); | |
} | |
} | |
} | |
} | |
return values; | |
} | |
use(...args) { | |
const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} }; | |
args.forEach((pack) => { | |
// copy options to new object | |
const opts = { ...pack }; | |
// set async to true if it was set to true before | |
opts.async = this.defaults.async || opts.async || false; | |
// ==-- Parse "addon" extensions --== // | |
if (pack.extensions) { | |
pack.extensions.forEach((ext) => { | |
if (!ext.name) { | |
throw new Error('extension name required'); | |
} | |
if ('renderer' in ext) { // Renderer extensions | |
const prevRenderer = extensions.renderers[ext.name]; | |
if (prevRenderer) { | |
// Replace extension with func to run new extension but fall back if false | |
extensions.renderers[ext.name] = function (...args) { | |
let ret = ext.renderer.apply(this, args); | |
if (ret === false) { | |
ret = prevRenderer.apply(this, args); | |
} | |
return ret; | |
}; | |
} | |
else { | |
extensions.renderers[ext.name] = ext.renderer; | |
} | |
} | |
if ('tokenizer' in ext) { // Tokenizer Extensions | |
if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) { | |
throw new Error("extension level must be 'block' or 'inline'"); | |
} | |
const extLevel = extensions[ext.level]; | |
if (extLevel) { | |
extLevel.unshift(ext.tokenizer); | |
} | |
else { | |
extensions[ext.level] = [ext.tokenizer]; | |
} | |
if (ext.start) { // Function to check for start of token | |
if (ext.level === 'block') { | |
if (extensions.startBlock) { | |
extensions.startBlock.push(ext.start); | |
} | |
else { | |
extensions.startBlock = [ext.start]; | |
} | |
} | |
else if (ext.level === 'inline') { | |
if (extensions.startInline) { | |
extensions.startInline.push(ext.start); | |
} | |
else { | |
extensions.startInline = [ext.start]; | |
} | |
} | |
} | |
} | |
if ('childTokens' in ext && ext.childTokens) { // Child tokens to be visited by walkTokens | |
extensions.childTokens[ext.name] = ext.childTokens; | |
} | |
}); | |
opts.extensions = extensions; | |
} | |
// ==-- Parse "overwrite" extensions --== // | |
if (pack.renderer) { | |
const renderer = this.defaults.renderer || new _Renderer(this.defaults); | |
for (const prop in pack.renderer) { | |
const rendererFunc = pack.renderer[prop]; | |
const rendererKey = prop; | |
const prevRenderer = renderer[rendererKey]; | |
// Replace renderer with func to run extension, but fall back if false | |
renderer[rendererKey] = (...args) => { | |
let ret = rendererFunc.apply(renderer, args); | |
if (ret === false) { | |
ret = prevRenderer.apply(renderer, args); | |
} | |
return ret || ''; | |
}; | |
} | |
opts.renderer = renderer; | |
} | |
if (pack.tokenizer) { | |
const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults); | |
for (const prop in pack.tokenizer) { | |
const tokenizerFunc = pack.tokenizer[prop]; | |
const tokenizerKey = prop; | |
const prevTokenizer = tokenizer[tokenizerKey]; | |
// Replace tokenizer with func to run extension, but fall back if false | |
tokenizer[tokenizerKey] = (...args) => { | |
let ret = tokenizerFunc.apply(tokenizer, args); | |
if (ret === false) { | |
ret = prevTokenizer.apply(tokenizer, args); | |
} | |
return ret; | |
}; | |
} | |
opts.tokenizer = tokenizer; | |
} | |
// ==-- Parse Hooks extensions --== // | |
if (pack.hooks) { | |
const hooks = this.defaults.hooks || new _Hooks(); | |
for (const prop in pack.hooks) { | |
const hooksFunc = pack.hooks[prop]; | |
const hooksKey = prop; | |
const prevHook = hooks[hooksKey]; | |
if (_Hooks.passThroughHooks.has(prop)) { | |
hooks[hooksKey] = (arg) => { | |
if (this.defaults.async) { | |
return Promise.resolve(hooksFunc.call(hooks, arg)).then(ret => { | |
return prevHook.call(hooks, ret); | |
}); | |
} | |
const ret = hooksFunc.call(hooks, arg); | |
return prevHook.call(hooks, ret); | |
}; | |
} | |
else { | |
hooks[hooksKey] = (...args) => { | |
let ret = hooksFunc.apply(hooks, args); | |
if (ret === false) { | |
ret = prevHook.apply(hooks, args); | |
} | |
return ret; | |
}; | |
} | |
} | |
opts.hooks = hooks; | |
} | |
// ==-- Parse WalkTokens extensions --== // | |
if (pack.walkTokens) { | |
const walkTokens = this.defaults.walkTokens; | |
const packWalktokens = pack.walkTokens; | |
opts.walkTokens = function (token) { | |
let values = []; | |
values.push(packWalktokens.call(this, token)); | |
if (walkTokens) { | |
values = values.concat(walkTokens.call(this, token)); | |
} | |
return values; | |
}; | |
} | |
this.defaults = { ...this.defaults, ...opts }; | |
}); | |
return this; | |
} | |
setOptions(opt) { | |
this.defaults = { ...this.defaults, ...opt }; | |
return this; | |
} | |
#parseMarkdown(lexer, parser) { | |
return (src, options) => { | |
const origOpt = { ...options }; | |
const opt = { ...this.defaults, ...origOpt }; | |
// Show warning if an extension set async to true but the parse was called with async: false | |
if (this.defaults.async === true && origOpt.async === false) { | |
if (!opt.silent) { | |
console.warn('marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored.'); | |
} | |
opt.async = true; | |
} | |
const throwError = this.#onError(!!opt.silent, !!opt.async); | |
// throw error in case of non string input | |
if (typeof src === 'undefined' || src === null) { | |
return throwError(new Error('marked(): input parameter is undefined or null')); | |
} | |
if (typeof src !== 'string') { | |
return throwError(new Error('marked(): input parameter is of type ' | |
+ Object.prototype.toString.call(src) + ', string expected')); | |
} | |
if (opt.hooks) { | |
opt.hooks.options = opt; | |
} | |
if (opt.async) { | |
return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src) | |
.then(src => lexer(src, opt)) | |
.then(tokens => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens) | |
.then(tokens => parser(tokens, opt)) | |
.then(html => opt.hooks ? opt.hooks.postprocess(html) : html) | |
.catch(throwError); | |
} | |
try { | |
if (opt.hooks) { | |
src = opt.hooks.preprocess(src); | |
} | |
const tokens = lexer(src, opt); | |
if (opt.walkTokens) { | |
this.walkTokens(tokens, opt.walkTokens); | |
} | |
let html = parser(tokens, opt); | |
if (opt.hooks) { | |
html = opt.hooks.postprocess(html); | |
} | |
return html; | |
} | |
catch (e) { | |
return throwError(e); | |
} | |
}; | |
} | |
#onError(silent, async) { | |
return (e) => { | |
e.message += '\nPlease report this to https://github.com/markedjs/marked.'; | |
if (silent) { | |
const msg = '<p>An error occurred:</p><pre>' | |
+ escape(e.message + '', true) | |
+ '</pre>'; | |
if (async) { | |
return Promise.resolve(msg); | |
} | |
return msg; | |
} | |
if (async) { | |
return Promise.reject(e); | |
} | |
throw e; | |
}; | |
} | |
} | |
const markedInstance = new Marked(); | |
function marked(src, opt) { | |
return markedInstance.parse(src, opt); | |
} | |
/** | |
* Sets the default options. | |
* | |
* @param options Hash of options | |
*/ | |
marked.options = | |
marked.setOptions = function (options) { | |
markedInstance.setOptions(options); | |
marked.defaults = markedInstance.defaults; | |
changeDefaults(marked.defaults); | |
return marked; | |
}; | |
/** | |
* Gets the original marked default options. | |
*/ | |
marked.getDefaults = _getDefaults; | |
marked.defaults = _defaults; | |
/** | |
* Use Extension | |
*/ | |
marked.use = function (...args) { | |
markedInstance.use(...args); | |
marked.defaults = markedInstance.defaults; | |
changeDefaults(marked.defaults); | |
return marked; | |
}; | |
/** | |
* Run callback for every token | |
*/ | |
marked.walkTokens = function (tokens, callback) { | |
return markedInstance.walkTokens(tokens, callback); | |
}; | |
/** | |
* Compiles markdown to HTML without enclosing `p` tag. | |
* | |
* @param src String of markdown source to be compiled | |
* @param options Hash of options | |
* @return String of compiled HTML | |
*/ | |
marked.parseInline = markedInstance.parseInline; | |
/** | |
* Expose | |
*/ | |
marked.Parser = _Parser; | |
marked.parser = _Parser.parse; | |
marked.Renderer = _Renderer; | |
marked.TextRenderer = _TextRenderer; | |
marked.Lexer = _Lexer; | |
marked.lexer = _Lexer.lex; | |
marked.Tokenizer = _Tokenizer; | |
marked.Hooks = _Hooks; | |
marked.parse = marked; | |
marked.options; | |
marked.setOptions; | |
marked.use; | |
marked.walkTokens; | |
marked.parseInline; | |
_Parser.parse; | |
_Lexer.lex; | |
/*! @license DOMPurify 3.0.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.6/LICENSE */ | |
const { | |
entries, | |
setPrototypeOf, | |
isFrozen, | |
getPrototypeOf, | |
getOwnPropertyDescriptor | |
} = Object; | |
let { | |
freeze, | |
seal, | |
create | |
} = Object; // eslint-disable-line import/no-mutable-exports | |
let { | |
apply, | |
construct | |
} = typeof Reflect !== 'undefined' && Reflect; | |
if (!freeze) { | |
freeze = function freeze(x) { | |
return x; | |
}; | |
} | |
if (!seal) { | |
seal = function seal(x) { | |
return x; | |
}; | |
} | |
if (!apply) { | |
apply = function apply(fun, thisValue, args) { | |
return fun.apply(thisValue, args); | |
}; | |
} | |
if (!construct) { | |
construct = function construct(Func, args) { | |
return new Func(...args); | |
}; | |
} | |
const arrayForEach = unapply(Array.prototype.forEach); | |
const arrayPop = unapply(Array.prototype.pop); | |
const arrayPush = unapply(Array.prototype.push); | |
const stringToLowerCase = unapply(String.prototype.toLowerCase); | |
const stringToString = unapply(String.prototype.toString); | |
const stringMatch = unapply(String.prototype.match); | |
const stringReplace = unapply(String.prototype.replace); | |
const stringIndexOf = unapply(String.prototype.indexOf); | |
const stringTrim = unapply(String.prototype.trim); | |
const regExpTest = unapply(RegExp.prototype.test); | |
const typeErrorCreate = unconstruct(TypeError); | |
/** | |
* Creates a new function that calls the given function with a specified thisArg and arguments. | |
* | |
* @param {Function} func - The function to be wrapped and called. | |
* @returns {Function} A new function that calls the given function with a specified thisArg and arguments. | |
*/ | |
function unapply(func) { | |
return function (thisArg) { | |
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | |
args[_key - 1] = arguments[_key]; | |
} | |
return apply(func, thisArg, args); | |
}; | |
} | |
/** | |
* Creates a new function that constructs an instance of the given constructor function with the provided arguments. | |
* | |
* @param {Function} func - The constructor function to be wrapped and called. | |
* @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments. | |
*/ | |
function unconstruct(func) { | |
return function () { | |
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | |
args[_key2] = arguments[_key2]; | |
} | |
return construct(func, args); | |
}; | |
} | |
/** | |
* Add properties to a lookup table | |
* | |
* @param {Object} set - The set to which elements will be added. | |
* @param {Array} array - The array containing elements to be added to the set. | |
* @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set. | |
* @returns {Object} The modified set with added elements. | |
*/ | |
function addToSet(set, array) { | |
let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase; | |
if (setPrototypeOf) { | |
// Make 'in' and truthy checks like Boolean(set.constructor) | |
// independent of any properties defined on Object.prototype. | |
// Prevent prototype setters from intercepting set as a this value. | |
setPrototypeOf(set, null); | |
} | |
let l = array.length; | |
while (l--) { | |
let element = array[l]; | |
if (typeof element === 'string') { | |
const lcElement = transformCaseFunc(element); | |
if (lcElement !== element) { | |
// Config presets (e.g. tags.js, attrs.js) are immutable. | |
if (!isFrozen(array)) { | |
array[l] = lcElement; | |
} | |
element = lcElement; | |
} | |
} | |
set[element] = true; | |
} | |
return set; | |
} | |
/** | |
* Shallow clone an object | |
* | |
* @param {Object} object - The object to be cloned. | |
* @returns {Object} A new object that copies the original. | |
*/ | |
function clone(object) { | |
const newObject = create(null); | |
for (const [property, value] of entries(object)) { | |
if (getOwnPropertyDescriptor(object, property) !== undefined) { | |
newObject[property] = value; | |
} | |
} | |
return newObject; | |
} | |
/** | |
* This method automatically checks if the prop is function or getter and behaves accordingly. | |
* | |
* @param {Object} object - The object to look up the getter function in its prototype chain. | |
* @param {String} prop - The property name for which to find the getter function. | |
* @returns {Function} The getter function found in the prototype chain or a fallback function. | |
*/ | |
function lookupGetter(object, prop) { | |
while (object !== null) { | |
const desc = getOwnPropertyDescriptor(object, prop); | |
if (desc) { | |
if (desc.get) { | |
return unapply(desc.get); | |
} | |
if (typeof desc.value === 'function') { | |
return unapply(desc.value); | |
} | |
} | |
object = getPrototypeOf(object); | |
} | |
function fallbackValue(element) { | |
console.warn('fallback value for', element); | |
return null; | |
} | |
return fallbackValue; | |
} | |
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); // SVG | |
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); | |
const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); // List of SVG elements that are disallowed by default. | |
// We still need to know them so that we can do namespace | |
// checks properly in case one wants to add them to | |
// allow-list. | |
const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']); | |
const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']); // Similarly to SVG, we want to know all MathML elements, | |
// even those that we disallow by default. | |
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']); | |
const text = freeze(['#text']); | |
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns', 'slot']); | |
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']); | |
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']); | |
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); | |
const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode | |
const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); | |
const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); | |
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape | |
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape | |
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape | |
); | |
const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); | |
const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex | |
); | |
const DOCTYPE_NAME = seal(/^html$/i); | |
var EXPRESSIONS = /*#__PURE__*/Object.freeze({ | |
__proto__: null, | |
MUSTACHE_EXPR: MUSTACHE_EXPR, | |
ERB_EXPR: ERB_EXPR, | |
TMPLIT_EXPR: TMPLIT_EXPR, | |
DATA_ATTR: DATA_ATTR, | |
ARIA_ATTR: ARIA_ATTR, | |
IS_ALLOWED_URI: IS_ALLOWED_URI, | |
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA, | |
ATTR_WHITESPACE: ATTR_WHITESPACE, | |
DOCTYPE_NAME: DOCTYPE_NAME | |
}); | |
const getGlobal = function getGlobal() { | |
return typeof window === 'undefined' ? null : window; | |
}; | |
/** | |
* Creates a no-op policy for internal use only. | |
* Don't export this function outside this module! | |
* @param {?TrustedTypePolicyFactory} trustedTypes The policy factory. | |
* @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). | |
* @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types | |
* are not supported or creating the policy failed). | |
*/ | |
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) { | |
if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') { | |
return null; | |
} // Allow the callers to control the unique policy name | |
// by adding a data-tt-policy-suffix to the script element with the DOMPurify. | |
// Policy creation with duplicate names throws in Trusted Types. | |
let suffix = null; | |
const ATTR_NAME = 'data-tt-policy-suffix'; | |
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) { | |
suffix = purifyHostElement.getAttribute(ATTR_NAME); | |
} | |
const policyName = 'dompurify' + (suffix ? '#' + suffix : ''); | |
try { | |
return trustedTypes.createPolicy(policyName, { | |
createHTML(html) { | |
return html; | |
}, | |
createScriptURL(scriptUrl) { | |
return scriptUrl; | |
} | |
}); | |
} catch (_) { | |
// Policy creation failed (most likely another DOMPurify script has | |
// already run). Skip creating the policy, as this will only cause errors | |
// if TT are enforced. | |
console.warn('TrustedTypes policy ' + policyName + ' could not be created.'); | |
return null; | |
} | |
}; | |
function createDOMPurify() { | |
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); | |
const DOMPurify = root => createDOMPurify(root); | |
/** | |
* Version label, exposed for easier checks | |
* if DOMPurify is up to date or not | |
*/ | |
DOMPurify.version = '3.0.6'; | |
/** | |
* Array of elements that DOMPurify removed during sanitation. | |
* Empty if nothing was removed. | |
*/ | |
DOMPurify.removed = []; | |
if (!window || !window.document || window.document.nodeType !== 9) { | |
// Not running in a browser, provide a factory function | |
// so that you can pass your own Window | |
DOMPurify.isSupported = false; | |
return DOMPurify; | |
} | |
let { | |
document | |
} = window; | |
const originalDocument = document; | |
const currentScript = originalDocument.currentScript; | |
const { | |
DocumentFragment, | |
HTMLTemplateElement, | |
Node, | |
Element, | |
NodeFilter, | |
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap, | |
HTMLFormElement, | |
DOMParser, | |
trustedTypes | |
} = window; | |
const ElementPrototype = Element.prototype; | |
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); | |
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); | |
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); | |
const getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a | |
// new document created via createHTMLDocument. As per the spec | |
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) | |
// a new empty registry is used when creating a template contents owner | |
// document, so we use that as our parent document to ensure nothing | |
// is inherited. | |
if (typeof HTMLTemplateElement === 'function') { | |
const template = document.createElement('template'); | |
if (template.content && template.content.ownerDocument) { | |
document = template.content.ownerDocument; | |
} | |
} | |
let trustedTypesPolicy; | |
let emptyHTML = ''; | |
const { | |
implementation, | |
createNodeIterator, | |
createDocumentFragment, | |
getElementsByTagName | |
} = document; | |
const { | |
importNode | |
} = originalDocument; | |
let hooks = {}; | |
/** | |
* Expose whether this browser supports running the full DOMPurify. | |
*/ | |
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined; | |
const { | |
MUSTACHE_EXPR, | |
ERB_EXPR, | |
TMPLIT_EXPR, | |
DATA_ATTR, | |
ARIA_ATTR, | |
IS_SCRIPT_OR_DATA, | |
ATTR_WHITESPACE | |
} = EXPRESSIONS; | |
let { | |
IS_ALLOWED_URI: IS_ALLOWED_URI$1 | |
} = EXPRESSIONS; | |
/** | |
* We consider the elements and attributes below to be safe. Ideally | |
* don't add any new ones but feel free to remove unwanted ones. | |
*/ | |
/* allowed element names */ | |
let ALLOWED_TAGS = null; | |
const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]); | |
/* Allowed attribute names */ | |
let ALLOWED_ATTR = null; | |
const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]); | |
/* | |
* Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. | |
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) | |
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) | |
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. | |
*/ | |
let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, { | |
tagNameCheck: { | |
writable: true, | |
configurable: false, | |
enumerable: true, | |
value: null | |
}, | |
attributeNameCheck: { | |
writable: true, | |
configurable: false, | |
enumerable: true, | |
value: null | |
}, | |
allowCustomizedBuiltInElements: { | |
writable: true, | |
configurable: false, | |
enumerable: true, | |
value: false | |
} | |
})); | |
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ | |
let FORBID_TAGS = null; | |
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ | |
let FORBID_ATTR = null; | |
/* Decide if ARIA attributes are okay */ | |
let ALLOW_ARIA_ATTR = true; | |
/* Decide if custom data attributes are okay */ | |
let ALLOW_DATA_ATTR = true; | |
/* Decide if unknown protocols are okay */ | |
let ALLOW_UNKNOWN_PROTOCOLS = false; | |
/* Decide if self-closing tags in attributes are allowed. | |
* Usually removed due to a mXSS issue in jQuery 3.0 */ | |
let ALLOW_SELF_CLOSE_IN_ATTR = true; | |
/* Output should be safe for common template engines. | |
* This means, DOMPurify removes data attributes, mustaches and ERB | |
*/ | |
let SAFE_FOR_TEMPLATES = false; | |
/* Decide if document with <html>... should be returned */ | |
let WHOLE_DOCUMENT = false; | |
/* Track whether config is already set on this instance of DOMPurify. */ | |
let SET_CONFIG = false; | |
/* Decide if all elements (e.g. style, script) must be children of | |
* document.body. By default, browsers might move them to document.head */ | |
let FORCE_BODY = false; | |
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html | |
* string (or a TrustedHTML object if Trusted Types are supported). | |
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead | |
*/ | |
let RETURN_DOM = false; | |
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html | |
* string (or a TrustedHTML object if Trusted Types are supported) */ | |
let RETURN_DOM_FRAGMENT = false; | |
/* Try to return a Trusted Type object instead of a string, return a string in | |
* case Trusted Types are not supported */ | |
let RETURN_TRUSTED_TYPE = false; | |
/* Output should be free from DOM clobbering attacks? | |
* This sanitizes markups named with colliding, clobberable built-in DOM APIs. | |
*/ | |
let SANITIZE_DOM = true; | |
/* Achieve full DOM Clobbering protection by isolating the namespace of named | |
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. | |
* | |
* HTML/DOM spec rules that enable DOM Clobbering: | |
* - Named Access on Window (§7.3.3) | |
* - DOM Tree Accessors (§3.1.5) | |
* - Form Element Parent-Child Relations (§4.10.3) | |
* - Iframe srcdoc / Nested WindowProxies (§4.8.5) | |
* - HTMLCollection (§4.2.10.2) | |
* | |
* Namespace isolation is implemented by prefixing `id` and `name` attributes | |
* with a constant string, i.e., `user-content-` | |
*/ | |
let SANITIZE_NAMED_PROPS = false; | |
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; | |
/* Keep element content when removing element? */ | |
let KEEP_CONTENT = true; | |
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead | |
* of importing it into a new Document and returning a sanitized copy */ | |
let IN_PLACE = false; | |
/* Allow usage of profiles like html, svg and mathMl */ | |
let USE_PROFILES = {}; | |
/* Tags to ignore content of when KEEP_CONTENT is true */ | |
let FORBID_CONTENTS = null; | |
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); | |
/* Tags that are safe for data: URIs */ | |
let DATA_URI_TAGS = null; | |
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); | |
/* Attributes safe for values like "javascript:" */ | |
let URI_SAFE_ATTRIBUTES = null; | |
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); | |
const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; | |
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; | |
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; | |
/* Document namespace */ | |
let NAMESPACE = HTML_NAMESPACE; | |
let IS_EMPTY_INPUT = false; | |
/* Allowed XHTML+XML namespaces */ | |
let ALLOWED_NAMESPACES = null; | |
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString); | |
/* Parsing of strict XHTML documents */ | |
let PARSER_MEDIA_TYPE = null; | |
const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; | |
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; | |
let transformCaseFunc = null; | |
/* Keep a reference to config to pass to hooks */ | |
let CONFIG = null; | |
/* Ideally, do not touch anything below this line */ | |
/* ______________________________________________ */ | |
const formElement = document.createElement('form'); | |
const isRegexOrFunction = function isRegexOrFunction(testValue) { | |
return testValue instanceof RegExp || testValue instanceof Function; | |
}; | |
/** | |
* _parseConfig | |
* | |
* @param {Object} cfg optional config literal | |
*/ | |
// eslint-disable-next-line complexity | |
const _parseConfig = function _parseConfig() { | |
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | |
if (CONFIG && CONFIG === cfg) { | |
return; | |
} | |
/* Shield configuration object from tampering */ | |
if (!cfg || typeof cfg !== 'object') { | |
cfg = {}; | |
} | |
/* Shield configuration object from prototype pollution */ | |
cfg = clone(cfg); | |
PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes | |
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. | |
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase; | |
/* Set configuration parameters */ | |
ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; | |
ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; | |
ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES; | |
URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent | |
cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent | |
transformCaseFunc // eslint-disable-line indent | |
) // eslint-disable-line indent | |
: DEFAULT_URI_SAFE_ATTRIBUTES; | |
DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent | |
cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent | |
transformCaseFunc // eslint-disable-line indent | |
) // eslint-disable-line indent | |
: DEFAULT_DATA_URI_TAGS; | |
FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS; | |
FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {}; | |
FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {}; | |
USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; | |
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true | |
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true | |
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false | |
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true | |
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false | |
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false | |
RETURN_DOM = cfg.RETURN_DOM || false; // Default false | |
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false | |
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false | |
FORCE_BODY = cfg.FORCE_BODY || false; // Default false | |
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true | |
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false | |
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true | |
IN_PLACE = cfg.IN_PLACE || false; // Default false | |
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI; | |
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; | |
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; | |
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { | |
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; | |
} | |
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) { | |
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck; | |
} | |
if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') { | |
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; | |
} | |
if (SAFE_FOR_TEMPLATES) { | |
ALLOW_DATA_ATTR = false; | |
} | |
if (RETURN_DOM_FRAGMENT) { | |
RETURN_DOM = true; | |
} | |
/* Parse profile info */ | |
if (USE_PROFILES) { | |
ALLOWED_TAGS = addToSet({}, [...text]); | |
ALLOWED_ATTR = []; | |
if (USE_PROFILES.html === true) { | |
addToSet(ALLOWED_TAGS, html$1); | |
addToSet(ALLOWED_ATTR, html); | |
} | |
if (USE_PROFILES.svg === true) { | |
addToSet(ALLOWED_TAGS, svg$1); | |
addToSet(ALLOWED_ATTR, svg); | |
addToSet(ALLOWED_ATTR, xml); | |
} | |
if (USE_PROFILES.svgFilters === true) { | |
addToSet(ALLOWED_TAGS, svgFilters); | |
addToSet(ALLOWED_ATTR, svg); | |
addToSet(ALLOWED_ATTR, xml); | |
} | |
if (USE_PROFILES.mathMl === true) { | |
addToSet(ALLOWED_TAGS, mathMl$1); | |
addToSet(ALLOWED_ATTR, mathMl); | |
addToSet(ALLOWED_ATTR, xml); | |
} | |
} | |
/* Merge configuration parameters */ | |
if (cfg.ADD_TAGS) { | |
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { | |
ALLOWED_TAGS = clone(ALLOWED_TAGS); | |
} | |
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); | |
} | |
if (cfg.ADD_ATTR) { | |
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { | |
ALLOWED_ATTR = clone(ALLOWED_ATTR); | |
} | |
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); | |
} | |
if (cfg.ADD_URI_SAFE_ATTR) { | |
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); | |
} | |
if (cfg.FORBID_CONTENTS) { | |
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { | |
FORBID_CONTENTS = clone(FORBID_CONTENTS); | |
} | |
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); | |
} | |
/* Add #text in case KEEP_CONTENT is set to true */ | |
if (KEEP_CONTENT) { | |
ALLOWED_TAGS['#text'] = true; | |
} | |
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ | |
if (WHOLE_DOCUMENT) { | |
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); | |
} | |
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ | |
if (ALLOWED_TAGS.table) { | |
addToSet(ALLOWED_TAGS, ['tbody']); | |
delete FORBID_TAGS.tbody; | |
} | |
if (cfg.TRUSTED_TYPES_POLICY) { | |
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') { | |
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); | |
} | |
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') { | |
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); | |
} // Overwrite existing TrustedTypes policy. | |
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; // Sign local variables required by `sanitize`. | |
emptyHTML = trustedTypesPolicy.createHTML(''); | |
} else { | |
// Uninitialized policy, attempt to initialize the internal dompurify policy. | |
if (trustedTypesPolicy === undefined) { | |
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript); | |
} // If creating the internal policy succeeded sign internal variables. | |
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') { | |
emptyHTML = trustedTypesPolicy.createHTML(''); | |
} | |
} // Prevent further manipulation of configuration. | |
// Not available in IE8, Safari 5, etc. | |
if (freeze) { | |
freeze(cfg); | |
} | |
CONFIG = cfg; | |
}; | |
const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); | |
const HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML | |
// namespace. We need to specify them explicitly | |
// so that they don't get erroneously deleted from | |
// HTML namespace. | |
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); | |
/* Keep track of all possible SVG and MathML tags | |
* so that we can perform the namespace checks | |
* correctly. */ | |
const ALL_SVG_TAGS = addToSet({}, svg$1); | |
addToSet(ALL_SVG_TAGS, svgFilters); | |
addToSet(ALL_SVG_TAGS, svgDisallowed); | |
const ALL_MATHML_TAGS = addToSet({}, mathMl$1); | |
addToSet(ALL_MATHML_TAGS, mathMlDisallowed); | |
/** | |
* @param {Element} element a DOM element whose namespace is being checked | |
* @returns {boolean} Return false if the element has a | |
* namespace that a spec-compliant parser would never | |
* return. Return true otherwise. | |
*/ | |
const _checkValidNamespace = function _checkValidNamespace(element) { | |
let parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode | |
// can be null. We just simulate parent in this case. | |
if (!parent || !parent.tagName) { | |
parent = { | |
namespaceURI: NAMESPACE, | |
tagName: 'template' | |
}; | |
} | |
const tagName = stringToLowerCase(element.tagName); | |
const parentTagName = stringToLowerCase(parent.tagName); | |
if (!ALLOWED_NAMESPACES[element.namespaceURI]) { | |
return false; | |
} | |
if (element.namespaceURI === SVG_NAMESPACE) { | |
// The only way to switch from HTML namespace to SVG | |
// is via <svg>. If it happens via any other tag, then | |
// it should be killed. | |
if (parent.namespaceURI === HTML_NAMESPACE) { | |
return tagName === 'svg'; | |
} // The only way to switch from MathML to SVG is via` | |
// svg if parent is either <annotation-xml> or MathML | |
// text integration points. | |
if (parent.namespaceURI === MATHML_NAMESPACE) { | |
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); | |
} // We only allow elements that are defined in SVG | |
// spec. All others are disallowed in SVG namespace. | |
return Boolean(ALL_SVG_TAGS[tagName]); | |
} | |
if (element.namespaceURI === MATHML_NAMESPACE) { | |
// The only way to switch from HTML namespace to MathML | |
// is via <math>. If it happens via any other tag, then | |
// it should be killed. | |
if (parent.namespaceURI === HTML_NAMESPACE) { | |
return tagName === 'math'; | |
} // The only way to switch from SVG to MathML is via | |
// <math> and HTML integration points | |
if (parent.namespaceURI === SVG_NAMESPACE) { | |
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; | |
} // We only allow elements that are defined in MathML | |
// spec. All others are disallowed in MathML namespace. | |
return Boolean(ALL_MATHML_TAGS[tagName]); | |
} | |
if (element.namespaceURI === HTML_NAMESPACE) { | |
// The only way to switch from SVG to HTML is via | |
// HTML integration points, and from MathML to HTML | |
// is via MathML text integration points | |
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) { | |
return false; | |
} | |
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { | |
return false; | |
} // We disallow tags that are specific for MathML | |
// or SVG and should never appear in HTML namespace | |
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); | |
} // For XHTML and XML documents that support custom namespaces | |
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) { | |
return true; | |
} // The code should never reach this place (this means | |
// that the element somehow got namespace that is not | |
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). | |
// Return false just in case. | |
return false; | |
}; | |
/** | |
* _forceRemove | |
* | |
* @param {Node} node a DOM node | |
*/ | |
const _forceRemove = function _forceRemove(node) { | |
arrayPush(DOMPurify.removed, { | |
element: node | |
}); | |
try { | |
// eslint-disable-next-line unicorn/prefer-dom-node-remove | |
node.parentNode.removeChild(node); | |
} catch (_) { | |
node.remove(); | |
} | |
}; | |
/** | |
* _removeAttribute | |
* | |
* @param {String} name an Attribute name | |
* @param {Node} node a DOM node | |
*/ | |
const _removeAttribute = function _removeAttribute(name, node) { | |
try { | |
arrayPush(DOMPurify.removed, { | |
attribute: node.getAttributeNode(name), | |
from: node | |
}); | |
} catch (_) { | |
arrayPush(DOMPurify.removed, { | |
attribute: null, | |
from: node | |
}); | |
} | |
node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes | |
if (name === 'is' && !ALLOWED_ATTR[name]) { | |
if (RETURN_DOM || RETURN_DOM_FRAGMENT) { | |
try { | |
_forceRemove(node); | |
} catch (_) {} | |
} else { | |
try { | |
node.setAttribute(name, ''); | |
} catch (_) {} | |
} | |
} | |
}; | |
/** | |
* _initDocument | |
* | |
* @param {String} dirty a string of dirty markup | |
* @return {Document} a DOM, filled with the dirty markup | |
*/ | |
const _initDocument = function _initDocument(dirty) { | |
/* Create a HTML document */ | |
let doc = null; | |
let leadingWhitespace = null; | |
if (FORCE_BODY) { | |
dirty = '<remove></remove>' + dirty; | |
} else { | |
/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ | |
const matches = stringMatch(dirty, /^[\r\n\t ]+/); | |
leadingWhitespace = matches && matches[0]; | |
} | |
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) { | |
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict) | |
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>'; | |
} | |
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty; | |
/* | |
* Use the DOMParser API by default, fallback later if needs be | |
* DOMParser not work for svg when has multiple root element. | |
*/ | |
if (NAMESPACE === HTML_NAMESPACE) { | |
try { | |
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); | |
} catch (_) {} | |
} | |
/* Use createHTMLDocument in case DOMParser is not available */ | |
if (!doc || !doc.documentElement) { | |
doc = implementation.createDocument(NAMESPACE, 'template', null); | |
try { | |
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload; | |
} catch (_) {// Syntax error if dirtyPayload is invalid xml | |
} | |
} | |
const body = doc.body || doc.documentElement; | |
if (dirty && leadingWhitespace) { | |
body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); | |
} | |
/* Work on whole document or just its body */ | |
if (NAMESPACE === HTML_NAMESPACE) { | |
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; | |
} | |
return WHOLE_DOCUMENT ? doc.documentElement : body; | |
}; | |
/** | |
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document. | |
* | |
* @param {Node} root The root element or node to start traversing on. | |
* @return {NodeIterator} The created NodeIterator | |
*/ | |
const _createNodeIterator = function _createNodeIterator(root) { | |
return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise | |
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null); | |
}; | |
/** | |
* _isClobbered | |
* | |
* @param {Node} elm element to check for clobbering attacks | |
* @return {Boolean} true if clobbered, false if safe | |
*/ | |
const _isClobbered = function _isClobbered(elm) { | |
return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function'); | |
}; | |
/** | |
* Checks whether the given object is a DOM node. | |
* | |
* @param {Node} object object to check whether it's a DOM node | |
* @return {Boolean} true is object is a DOM node | |
*/ | |
const _isNode = function _isNode(object) { | |
return typeof Node === 'function' && object instanceof Node; | |
}; | |
/** | |
* _executeHook | |
* Execute user configurable hooks | |
* | |
* @param {String} entryPoint Name of the hook's entry point | |
* @param {Node} currentNode node to work on with the hook | |
* @param {Object} data additional hook parameters | |
*/ | |
const _executeHook = function _executeHook(entryPoint, currentNode, data) { | |
if (!hooks[entryPoint]) { | |
return; | |
} | |
arrayForEach(hooks[entryPoint], hook => { | |
hook.call(DOMPurify, currentNode, data, CONFIG); | |
}); | |
}; | |
/** | |
* _sanitizeElements | |
* | |
* @protect nodeName | |
* @protect textContent | |
* @protect removeChild | |
* | |
* @param {Node} currentNode to check for permission to exist | |
* @return {Boolean} true if node was killed, false if left alive | |
*/ | |
const _sanitizeElements = function _sanitizeElements(currentNode) { | |
let content = null; | |
/* Execute a hook if present */ | |
_executeHook('beforeSanitizeElements', currentNode, null); | |
/* Check if element is clobbered or can clobber */ | |
if (_isClobbered(currentNode)) { | |
_forceRemove(currentNode); | |
return true; | |
} | |
/* Now let's check the element's type and name */ | |
const tagName = transformCaseFunc(currentNode.nodeName); | |
/* Execute a hook if present */ | |
_executeHook('uponSanitizeElement', currentNode, { | |
tagName, | |
allowedTags: ALLOWED_TAGS | |
}); | |
/* Detect mXSS attempts abusing namespace confusion */ | |
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { | |
_forceRemove(currentNode); | |
return true; | |
} | |
/* Remove element if anything forbids its presence */ | |
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { | |
/* Check if we have a custom element to handle */ | |
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) { | |
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) { | |
return false; | |
} | |
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) { | |
return false; | |
} | |
} | |
/* Keep content except for bad-listed elements */ | |
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { | |
const parentNode = getParentNode(currentNode) || currentNode.parentNode; | |
const childNodes = getChildNodes(currentNode) || currentNode.childNodes; | |
if (childNodes && parentNode) { | |
const childCount = childNodes.length; | |
for (let i = childCount - 1; i >= 0; --i) { | |
parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode)); | |
} | |
} | |
} | |
_forceRemove(currentNode); | |
return true; | |
} | |
/* Check whether element has a valid namespace */ | |
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { | |
_forceRemove(currentNode); | |
return true; | |
} | |
/* Make sure that older browsers don't get fallback-tag mXSS */ | |
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) { | |
_forceRemove(currentNode); | |
return true; | |
} | |
/* Sanitize element content to be template-safe */ | |
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { | |
/* Get the element's text content */ | |
content = currentNode.textContent; | |
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { | |
content = stringReplace(content, expr, ' '); | |
}); | |
if (currentNode.textContent !== content) { | |
arrayPush(DOMPurify.removed, { | |
element: currentNode.cloneNode() | |
}); | |
currentNode.textContent = content; | |
} | |
} | |
/* Execute a hook if present */ | |
_executeHook('afterSanitizeElements', currentNode, null); | |
return false; | |
}; | |
/** | |
* _isValidAttribute | |
* | |
* @param {string} lcTag Lowercase tag name of containing element. | |
* @param {string} lcName Lowercase attribute name. | |
* @param {string} value Attribute value. | |
* @return {Boolean} Returns true if `value` is valid, otherwise false. | |
*/ | |
// eslint-disable-next-line complexity | |
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { | |
/* Make sure attribute cannot clobber */ | |
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { | |
return false; | |
} | |
/* Allow valid data-* attributes: At least one character after "-" | |
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) | |
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) | |
We don't need to check the value; it's always URI safe. */ | |
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { | |
if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND | |
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck | |
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck | |
_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || // Alternative, second condition checks if it's an `is`-attribute, AND | |
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck | |
lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else { | |
return false; | |
} | |
/* Check value is safe. First, is attr inert? If so, is safe */ | |
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) { | |
return false; | |
} else ; | |
return true; | |
}; | |
/** | |
* _isBasicCustomElement | |
* checks if at least one dash is included in tagName, and it's not the first char | |
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name | |
* | |
* @param {string} tagName name of the tag of the node to sanitize | |
* @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false. | |
*/ | |
const _isBasicCustomElement = function _isBasicCustomElement(tagName) { | |
return tagName.indexOf('-') > 0; | |
}; | |
/** | |
* _sanitizeAttributes | |
* | |
* @protect attributes | |
* @protect nodeName | |
* @protect removeAttribute | |
* @protect setAttribute | |
* | |
* @param {Node} currentNode to sanitize | |
*/ | |
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) { | |
/* Execute a hook if present */ | |
_executeHook('beforeSanitizeAttributes', currentNode, null); | |
const { | |
attributes | |
} = currentNode; | |
/* Check if we have attributes; if not we might have a text node */ | |
if (!attributes) { | |
return; | |
} | |
const hookEvent = { | |
attrName: '', | |
attrValue: '', | |
keepAttr: true, | |
allowedAttributes: ALLOWED_ATTR | |
}; | |
let l = attributes.length; | |
/* Go backwards over all attributes; safely remove bad ones */ | |
while (l--) { | |
const attr = attributes[l]; | |
const { | |
name, | |
namespaceURI, | |
value: attrValue | |
} = attr; | |
const lcName = transformCaseFunc(name); | |
let value = name === 'value' ? attrValue : stringTrim(attrValue); | |
/* Execute a hook if present */ | |
hookEvent.attrName = lcName; | |
hookEvent.attrValue = value; | |
hookEvent.keepAttr = true; | |
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set | |
_executeHook('uponSanitizeAttribute', currentNode, hookEvent); | |
value = hookEvent.attrValue; | |
/* Did the hooks approve of the attribute? */ | |
if (hookEvent.forceKeepAttr) { | |
continue; | |
} | |
/* Remove attribute */ | |
_removeAttribute(name, currentNode); | |
/* Did the hooks approve of the attribute? */ | |
if (!hookEvent.keepAttr) { | |
continue; | |
} | |
/* Work around a security issue in jQuery 3.0 */ | |
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { | |
_removeAttribute(name, currentNode); | |
continue; | |
} | |
/* Sanitize attribute content to be template-safe */ | |
if (SAFE_FOR_TEMPLATES) { | |
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { | |
value = stringReplace(value, expr, ' '); | |
}); | |
} | |
/* Is `value` valid for this attribute? */ | |
const lcTag = transformCaseFunc(currentNode.nodeName); | |
if (!_isValidAttribute(lcTag, lcName, value)) { | |
continue; | |
} | |
/* Full DOM Clobbering protection via namespace isolation, | |
* Prefix id and name attributes with `user-content-` | |
*/ | |
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { | |
// Remove the attribute with this value | |
_removeAttribute(name, currentNode); // Prefix the value and later re-create the attribute with the sanitized value | |
value = SANITIZE_NAMED_PROPS_PREFIX + value; | |
} | |
/* Handle attributes that require Trusted Types */ | |
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') { | |
if (namespaceURI) ; else { | |
switch (trustedTypes.getAttributeType(lcTag, lcName)) { | |
case 'TrustedHTML': | |
{ | |
value = trustedTypesPolicy.createHTML(value); | |
break; | |
} | |
case 'TrustedScriptURL': | |
{ | |
value = trustedTypesPolicy.createScriptURL(value); | |
break; | |
} | |
} | |
} | |
} | |
/* Handle invalid data-* attribute set by try-catching it */ | |
try { | |
if (namespaceURI) { | |
currentNode.setAttributeNS(namespaceURI, name, value); | |
} else { | |
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ | |
currentNode.setAttribute(name, value); | |
} | |
arrayPop(DOMPurify.removed); | |
} catch (_) {} | |
} | |
/* Execute a hook if present */ | |
_executeHook('afterSanitizeAttributes', currentNode, null); | |
}; | |
/** | |
* _sanitizeShadowDOM | |
* | |
* @param {DocumentFragment} fragment to iterate over recursively | |
*/ | |
const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { | |
let shadowNode = null; | |
const shadowIterator = _createNodeIterator(fragment); | |
/* Execute a hook if present */ | |
_executeHook('beforeSanitizeShadowDOM', fragment, null); | |
while (shadowNode = shadowIterator.nextNode()) { | |
/* Execute a hook if present */ | |
_executeHook('uponSanitizeShadowNode', shadowNode, null); | |
/* Sanitize tags and elements */ | |
if (_sanitizeElements(shadowNode)) { | |
continue; | |
} | |
/* Deep shadow DOM detected */ | |
if (shadowNode.content instanceof DocumentFragment) { | |
_sanitizeShadowDOM(shadowNode.content); | |
} | |
/* Check attributes, sanitize if necessary */ | |
_sanitizeAttributes(shadowNode); | |
} | |
/* Execute a hook if present */ | |
_executeHook('afterSanitizeShadowDOM', fragment, null); | |
}; | |
/** | |
* Sanitize | |
* Public method providing core sanitation functionality | |
* | |
* @param {String|Node} dirty string or DOM node | |
* @param {Object} cfg object | |
*/ | |
// eslint-disable-next-line complexity | |
DOMPurify.sanitize = function (dirty) { | |
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | |
let body = null; | |
let importedNode = null; | |
let currentNode = null; | |
let returnNode = null; | |
/* Make sure we have a string to sanitize. | |
DO NOT return early, as this will return the wrong type if | |
the user has requested a DOM object rather than a string */ | |
IS_EMPTY_INPUT = !dirty; | |
if (IS_EMPTY_INPUT) { | |
dirty = '<!-->'; | |
} | |
/* Stringify, in case dirty is an object */ | |
if (typeof dirty !== 'string' && !_isNode(dirty)) { | |
if (typeof dirty.toString === 'function') { | |
dirty = dirty.toString(); | |
if (typeof dirty !== 'string') { | |
throw typeErrorCreate('dirty is not a string, aborting'); | |
} | |
} else { | |
throw typeErrorCreate('toString is not a function'); | |
} | |
} | |
/* Return dirty HTML if DOMPurify cannot run */ | |
if (!DOMPurify.isSupported) { | |
return dirty; | |
} | |
/* Assign config vars */ | |
if (!SET_CONFIG) { | |
_parseConfig(cfg); | |
} | |
/* Clean up removed elements */ | |
DOMPurify.removed = []; | |
/* Check if dirty is correctly typed for IN_PLACE */ | |
if (typeof dirty === 'string') { | |
IN_PLACE = false; | |
} | |
if (IN_PLACE) { | |
/* Do some early pre-sanitization to avoid unsafe root nodes */ | |
if (dirty.nodeName) { | |
const tagName = transformCaseFunc(dirty.nodeName); | |
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { | |
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place'); | |
} | |
} | |
} else if (dirty instanceof Node) { | |
/* If dirty is a DOM element, append to an empty document to avoid | |
elements being stripped by the parser */ | |
body = _initDocument('<!---->'); | |
importedNode = body.ownerDocument.importNode(dirty, true); | |
if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { | |
/* Node is already a body, use as is */ | |
body = importedNode; | |
} else if (importedNode.nodeName === 'HTML') { | |
body = importedNode; | |
} else { | |
// eslint-disable-next-line unicorn/prefer-dom-node-append | |
body.appendChild(importedNode); | |
} | |
} else { | |
/* Exit directly if we have nothing to do */ | |
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes | |
dirty.indexOf('<') === -1) { | |
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty; | |
} | |
/* Initialize the document to work on */ | |
body = _initDocument(dirty); | |
/* Check we have a DOM node from the data */ | |
if (!body) { | |
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; | |
} | |
} | |
/* Remove first element node (ours) if FORCE_BODY is set */ | |
if (body && FORCE_BODY) { | |
_forceRemove(body.firstChild); | |
} | |
/* Get node iterator */ | |
const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body); | |
/* Now start iterating over the created document */ | |
while (currentNode = nodeIterator.nextNode()) { | |
/* Sanitize tags and elements */ | |
if (_sanitizeElements(currentNode)) { | |
continue; | |
} | |
/* Shadow DOM detected, sanitize it */ | |
if (currentNode.content instanceof DocumentFragment) { | |
_sanitizeShadowDOM(currentNode.content); | |
} | |
/* Check attributes, sanitize if necessary */ | |
_sanitizeAttributes(currentNode); | |
} | |
/* If we sanitized `dirty` in-place, return it. */ | |
if (IN_PLACE) { | |
return dirty; | |
} | |
/* Return sanitized string or DOM */ | |
if (RETURN_DOM) { | |
if (RETURN_DOM_FRAGMENT) { | |
returnNode = createDocumentFragment.call(body.ownerDocument); | |
while (body.firstChild) { | |
// eslint-disable-next-line unicorn/prefer-dom-node-append | |
returnNode.appendChild(body.firstChild); | |
} | |
} else { | |
returnNode = body; | |
} | |
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) { | |
/* | |
AdoptNode() is not used because internal state is not reset | |
(e.g. the past names map of a HTMLFormElement), this is safe | |
in theory but we would rather not risk another attack vector. | |
The state that is cloned by importNode() is explicitly defined | |
by the specs. | |
*/ | |
returnNode = importNode.call(originalDocument, returnNode, true); | |
} | |
return returnNode; | |
} | |
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; | |
/* Serialize doctype if allowed */ | |
if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) { | |
serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML; | |
} | |
/* Sanitize final string template-safe */ | |
if (SAFE_FOR_TEMPLATES) { | |
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { | |
serializedHTML = stringReplace(serializedHTML, expr, ' '); | |
}); | |
} | |
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML; | |
}; | |
/** | |
* Public method to set the configuration once | |
* setConfig | |
* | |
* @param {Object} cfg configuration object | |
*/ | |
DOMPurify.setConfig = function () { | |
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | |
_parseConfig(cfg); | |
SET_CONFIG = true; | |
}; | |
/** | |
* Public method to remove the configuration | |
* clearConfig | |
* | |
*/ | |
DOMPurify.clearConfig = function () { | |
CONFIG = null; | |
SET_CONFIG = false; | |
}; | |
/** | |
* Public method to check if an attribute value is valid. | |
* Uses last set config, if any. Otherwise, uses config defaults. | |
* isValidAttribute | |
* | |
* @param {String} tag Tag name of containing element. | |
* @param {String} attr Attribute name. | |
* @param {String} value Attribute value. | |
* @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. | |
*/ | |
DOMPurify.isValidAttribute = function (tag, attr, value) { | |
/* Initialize shared config vars if necessary. */ | |
if (!CONFIG) { | |
_parseConfig({}); | |
} | |
const lcTag = transformCaseFunc(tag); | |
const lcName = transformCaseFunc(attr); | |
return _isValidAttribute(lcTag, lcName, value); | |
}; | |
/** | |
* AddHook | |
* Public method to add DOMPurify hooks | |
* | |
* @param {String} entryPoint entry point for the hook to add | |
* @param {Function} hookFunction function to execute | |
*/ | |
DOMPurify.addHook = function (entryPoint, hookFunction) { | |
if (typeof hookFunction !== 'function') { | |
return; | |
} | |
hooks[entryPoint] = hooks[entryPoint] || []; | |
arrayPush(hooks[entryPoint], hookFunction); | |
}; | |
/** | |
* RemoveHook | |
* Public method to remove a DOMPurify hook at a given entryPoint | |
* (pops it from the stack of hooks if more are present) | |
* | |
* @param {String} entryPoint entry point for the hook to remove | |
* @return {Function} removed(popped) hook | |
*/ | |
DOMPurify.removeHook = function (entryPoint) { | |
if (hooks[entryPoint]) { | |
return arrayPop(hooks[entryPoint]); | |
} | |
}; | |
/** | |
* RemoveHooks | |
* Public method to remove all DOMPurify hooks at a given entryPoint | |
* | |
* @param {String} entryPoint entry point for the hooks to remove | |
*/ | |
DOMPurify.removeHooks = function (entryPoint) { | |
if (hooks[entryPoint]) { | |
hooks[entryPoint] = []; | |
} | |
}; | |
/** | |
* RemoveAllHooks | |
* Public method to remove all DOMPurify hooks | |
*/ | |
DOMPurify.removeAllHooks = function () { | |
hooks = {}; | |
}; | |
return DOMPurify; | |
} | |
var purify = createDOMPurify(); | |
const _tmpl$$b = /*#__PURE__*/template(`<div class="flex flex-col animate-fade-in typebot-streaming-container"><div class="flex w-full items-center"><div class="flex relative items-start typebot-host-bubble max-w-full"><div class="flex items-center absolute px-4 py-2 bubble-typing " data-testid="host-bubble"></div><div class="flex flex-col overflow-hidden text-fade-in mx-4 my-2 relative text-ellipsis h-full gap-6">`), | |
_tmpl$2$5 = /*#__PURE__*/template(`<span>`); | |
const StreamingBubble = props => { | |
const [content, setContent] = createSignal([]); | |
marked.use({ | |
renderer: { | |
link: (href, _title, text) => { | |
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`; | |
} | |
} | |
}); | |
createEffect(() => { | |
if (streamingMessage()?.id !== props.streamingMessageId) return []; | |
setContent(streamingMessage()?.content.split('```').map((block, index) => { | |
if (index % 2 === 0) { | |
return block.split('\n\n').map(line => purify.sanitize(marked.parse(line.replace(/【.+】/g, ''), { | |
breaks: true | |
}), { | |
ADD_ATTR: ['target'] | |
})); | |
} else { | |
return [purify.sanitize(marked.parse('```' + block + '```', { | |
breaks: true | |
}), { | |
ADD_ATTR: ['target'] | |
})]; | |
} | |
}).flat().filter(isNotEmpty) ?? []); | |
}); | |
return (() => { | |
const _el$ = _tmpl$$b(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild, | |
_el$5 = _el$4.nextSibling; | |
_el$4.style.setProperty("width", "100%"); | |
_el$4.style.setProperty("height", "100%"); | |
insert(_el$5, createComponent(For, { | |
get each() { | |
return content(); | |
}, | |
children: line => (() => { | |
const _el$6 = _tmpl$2$5(); | |
_el$6.innerHTML = line; | |
return _el$6; | |
})() | |
})); | |
return _el$; | |
})(); | |
}; | |
const _tmpl$$a = /*#__PURE__*/template(`<div><div class="flex flex-col flex-1 gap-2">`), | |
_tmpl$2$4 = /*#__PURE__*/template(`<div class="flex flex-col w-full min-w-0 gap-2 typebot-chat-chunk">`); | |
const ChatChunk = props => { | |
let inputRef; | |
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(props.isTransitionDisabled ? props.messages.length : 0); | |
const [lastBubble, setLastBubble] = createSignal(); | |
onMount(() => { | |
if (props.streamingMessageId) return; | |
if (props.messages.length === 0) { | |
props.onAllBubblesDisplayed(); | |
} | |
props.onScrollToBottom(inputRef, 50); | |
}); | |
const displayNextMessage = async bubbleRef => { | |
if ((props.settings.typingEmulation?.delayBetweenBubbles ?? defaultSettings.typingEmulation.delayBetweenBubbles) > 0 && displayedMessageIndex() < props.messages.length - 1) { | |
await new Promise(resolve => setTimeout(resolve, (props.settings.typingEmulation?.delayBetweenBubbles ?? defaultSettings.typingEmulation.delayBetweenBubbles) * 1000)); | |
} | |
const lastBubbleBlockId = props.messages[displayedMessageIndex()].id; | |
await props.onNewBubbleDisplayed(lastBubbleBlockId); | |
setDisplayedMessageIndex(displayedMessageIndex() === props.messages.length ? displayedMessageIndex() : displayedMessageIndex() + 1); | |
props.onScrollToBottom(bubbleRef); | |
if (displayedMessageIndex() === props.messages.length) { | |
setLastBubble(bubbleRef); | |
props.onAllBubblesDisplayed(); | |
} | |
}; | |
return (() => { | |
const _el$ = _tmpl$2$4(); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.messages.length > 0; | |
}, | |
get children() { | |
const _el$2 = _tmpl$$a(), | |
_el$3 = _el$2.firstChild; | |
insert(_el$2, createComponent(Show, { | |
get when() { | |
return (props.theme.chat?.hostAvatar?.isEnabled ?? defaultHostAvatarIsEnabled) && props.messages.length > 0; | |
}, | |
get children() { | |
return createComponent(AvatarSideContainer, { | |
get hostAvatarSrc() { | |
return props.theme.chat?.hostAvatar?.url; | |
}, | |
get hideAvatar() { | |
return props.hideAvatar; | |
}, | |
get isTransitionDisabled() { | |
return props.isTransitionDisabled; | |
} | |
}); | |
} | |
}), _el$3); | |
insert(_el$3, createComponent(For, { | |
get each() { | |
return props.messages.slice(0, displayedMessageIndex() + 1); | |
}, | |
children: (message, idx) => createComponent(HostBubble, { | |
message: message, | |
get typingEmulation() { | |
return props.settings.typingEmulation; | |
}, | |
get isTypingSkipped() { | |
return createMemo(() => !!((props.settings.typingEmulation?.isDisabledOnFirstMessage ?? defaultSettings.typingEmulation.isDisabledOnFirstMessage) && props.index === 0))() && idx() === 0; | |
}, | |
get onTransitionEnd() { | |
return props.isTransitionDisabled ? undefined : displayNextMessage; | |
}, | |
get onCompleted() { | |
return props.onSubmit; | |
} | |
}) | |
})); | |
createRenderEffect(_p$ => { | |
const _v$ = 'flex' + (isMobile() ? ' gap-1' : ' gap-2'), | |
_v$2 = props.theme.chat?.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled ? isMobile() ? 'calc(100% - 60px)' : 'calc(100% - 48px - 48px)' : '100%'; | |
_v$ !== _p$._v$ && className(_el$2, _p$._v$ = _v$); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$3.style.setProperty("max-width", _v$2) : _el$3.style.removeProperty("max-width")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined | |
}); | |
return _el$2; | |
} | |
}), null); | |
insert(_el$, (() => { | |
const _c$ = createMemo(() => !!(props.input && displayedMessageIndex() === props.messages.length)); | |
return () => _c$() && createComponent(InputChatBlock, { | |
ref(r$) { | |
const _ref$ = inputRef; | |
typeof _ref$ === "function" ? _ref$(r$) : inputRef = r$; | |
}, | |
get block() { | |
return props.input; | |
}, | |
get chunkIndex() { | |
return props.index; | |
}, | |
get hasHostAvatar() { | |
return props.theme.chat?.hostAvatar?.isEnabled ?? defaultHostAvatarIsEnabled; | |
}, | |
get guestAvatar() { | |
return props.theme.chat?.guestAvatar; | |
}, | |
get context() { | |
return props.context; | |
}, | |
get isInputPrefillEnabled() { | |
return props.settings.general?.isInputPrefillEnabled ?? defaultSettings.general.isInputPrefillEnabled; | |
}, | |
get hasError() { | |
return props.hasError; | |
}, | |
onTransitionEnd: () => props.onScrollToBottom(lastBubble()), | |
get onSubmit() { | |
return props.onSubmit; | |
}, | |
get onSkip() { | |
return props.onSkip; | |
} | |
}); | |
})(), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return props.streamingMessageId; | |
}, | |
keyed: true, | |
children: streamingMessageId => (() => { | |
const _el$4 = _tmpl$$a(), | |
_el$5 = _el$4.firstChild; | |
insert(_el$4, createComponent(Show, { | |
get when() { | |
return props.theme.chat?.hostAvatar?.isEnabled ?? defaultHostAvatarIsEnabled; | |
}, | |
get children() { | |
return createComponent(AvatarSideContainer, { | |
get hostAvatarSrc() { | |
return props.theme.chat?.hostAvatar?.url; | |
}, | |
get hideAvatar() { | |
return props.hideAvatar; | |
} | |
}); | |
} | |
}), _el$5); | |
insert(_el$5, createComponent(StreamingBubble, { | |
streamingMessageId: streamingMessageId | |
})); | |
createRenderEffect(_p$ => { | |
const _v$3 = 'flex' + (isMobile() ? ' gap-1' : ' gap-2'), | |
_v$4 = props.theme.chat?.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled ? isMobile() ? 'calc(100% - 60px)' : 'calc(100% - 48px - 48px)' : '100%'; | |
_v$3 !== _p$._v$3 && className(_el$4, _p$._v$3 = _v$3); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$5.style.setProperty("max-width", _v$4) : _el$5.style.removeProperty("max-width")); | |
return _p$; | |
}, { | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$4; | |
})() | |
}), null); | |
return _el$; | |
})(); | |
}; | |
const executeChatwoot = chatwoot => { | |
executeScript(chatwoot.scriptToExecute); | |
}; | |
const initGoogleAnalytics = id => { | |
if (isDefined(window.gtag)) return Promise.resolve(); | |
return new Promise(resolve => { | |
const existingScript = document.getElementById('gtag'); | |
if (!existingScript) { | |
const script = document.createElement('script'); | |
script.src = `https://www.googletagmanager.com/gtag/js?id=${id}`; | |
script.id = 'gtag'; | |
const initScript = document.createElement('script'); | |
initScript.innerHTML = `window.dataLayer = window.dataLayer || []; | |
function gtag(){dataLayer.push(arguments);} | |
gtag('js', new Date()); | |
gtag('config', '${id}'); | |
`; | |
document.body.appendChild(script); | |
document.body.appendChild(initScript); | |
script.onload = () => { | |
resolve(); | |
}; | |
} | |
if (existingScript) resolve(); | |
}); | |
}; | |
const sendGaEvent = options => { | |
if (!options) return; | |
if (!window.gtag) { | |
console.error('Google Analytics was not properly initialized'); | |
return; | |
} | |
window.gtag('event', options.action, { | |
event_category: isEmpty$2(options.category) ? undefined : options.category, | |
event_label: isEmpty$2(options.label) ? undefined : options.label, | |
value: options.value, | |
send_to: isEmpty$2(options.sendTo) ? undefined : options.sendTo | |
}); | |
}; | |
const executeGoogleAnalyticsBlock = async options => { | |
if (!options?.trackingId) return; | |
sendGaEvent(options); | |
}; | |
const textStreamPart = { | |
code: '0', | |
name: 'text', | |
parse: value => { | |
if (typeof value !== 'string') { | |
throw new Error('"text" parts expect a string value.'); | |
} | |
return { | |
type: 'text', | |
value | |
}; | |
} | |
}; | |
const functionCallStreamPart = { | |
code: '1', | |
name: 'function_call', | |
parse: value => { | |
if (value == null || typeof value !== 'object' || !('function_call' in value) || typeof value.function_call !== 'object' || value.function_call == null || !('name' in value.function_call) || !('arguments' in value.function_call) || typeof value.function_call.name !== 'string' || typeof value.function_call.arguments !== 'string') { | |
throw new Error('"function_call" parts expect an object with a "function_call" property.'); | |
} | |
return { | |
type: 'function_call', | |
value: value | |
}; | |
} | |
}; | |
const dataStreamPart = { | |
code: '2', | |
name: 'data', | |
parse: value => { | |
if (!Array.isArray(value)) { | |
throw new Error('"data" parts expect an array value.'); | |
} | |
return { | |
type: 'data', | |
value | |
}; | |
} | |
}; | |
const errorStreamPart = { | |
code: '3', | |
name: 'error', | |
parse: value => { | |
if (typeof value !== 'string') { | |
throw new Error('"error" parts expect a string value.'); | |
} | |
return { | |
type: 'error', | |
value | |
}; | |
} | |
}; | |
const assistantMessageStreamPart = { | |
code: '4', | |
name: 'assistant_message', | |
parse: value => { | |
if (value == null || typeof value !== 'object' || !('id' in value) || !('role' in value) || !('content' in value) || typeof value.id !== 'string' || typeof value.role !== 'string' || value.role !== 'assistant' || !Array.isArray(value.content) || !value.content.every(item => item != null && typeof item === 'object' && 'type' in item && item.type === 'text' && 'text' in item && item.text != null && typeof item.text === 'object' && 'value' in item.text && typeof item.text.value === 'string')) { | |
throw new Error('"assistant_message" parts expect an object with an "id", "role", and "content" property.'); | |
} | |
return { | |
type: 'assistant_message', | |
value: value | |
}; | |
} | |
}; | |
const assistantControlDataStreamPart = { | |
code: '5', | |
name: 'assistant_control_data', | |
parse: value => { | |
if (value == null || typeof value !== 'object' || !('threadId' in value) || !('messageId' in value) || typeof value.threadId !== 'string' || typeof value.messageId !== 'string') { | |
throw new Error('"assistant_control_data" parts expect an object with a "threadId" and "messageId" property.'); | |
} | |
return { | |
type: 'assistant_control_data', | |
value: { | |
threadId: value.threadId, | |
messageId: value.messageId | |
} | |
}; | |
} | |
}; | |
const dataMessageStreamPart = { | |
code: '6', | |
name: 'data_message', | |
parse: value => { | |
if (value == null || typeof value !== 'object' || !('role' in value) || !('data' in value) || typeof value.role !== 'string' || value.role !== 'data') { | |
throw new Error('"data_message" parts expect an object with a "role" and "data" property.'); | |
} | |
return { | |
type: 'data_message', | |
value: value | |
}; | |
} | |
}; | |
const toolCallStreamPart = { | |
code: '7', | |
name: 'tool_calls', | |
parse: value => { | |
if (value == null || typeof value !== 'object' || !('tool_calls' in value) || typeof value.tool_calls !== 'object' || value.tool_calls == null || !Array.isArray(value.tool_calls) || value.tool_calls.some(tc => tc == null || typeof tc !== 'object' || !('id' in tc) || typeof tc.id !== 'string' || !('type' in tc) || typeof tc.type !== 'string' || !('function' in tc) || tc.function == null || typeof tc.function !== 'object' || !('arguments' in tc.function) || typeof tc.function.name !== 'string' || typeof tc.function.arguments !== 'string')) { | |
throw new Error('"tool_calls" parts expect an object with a ToolCallPayload.'); | |
} | |
return { | |
type: 'tool_calls', | |
value: value | |
}; | |
} | |
}; | |
const messageAnnotationsStreamPart = { | |
code: '8', | |
name: 'message_annotations', | |
parse: value => { | |
if (!Array.isArray(value)) { | |
throw new Error('"message_annotations" parts expect an array value.'); | |
} | |
return { | |
type: 'message_annotations', | |
value | |
}; | |
} | |
}; | |
const streamParts = [textStreamPart, functionCallStreamPart, dataStreamPart, errorStreamPart, assistantMessageStreamPart, assistantControlDataStreamPart, dataMessageStreamPart, toolCallStreamPart, messageAnnotationsStreamPart]; | |
const streamPartsByCode = { | |
[textStreamPart.code]: textStreamPart, | |
[functionCallStreamPart.code]: functionCallStreamPart, | |
[dataStreamPart.code]: dataStreamPart, | |
[errorStreamPart.code]: errorStreamPart, | |
[assistantMessageStreamPart.code]: assistantMessageStreamPart, | |
[assistantControlDataStreamPart.code]: assistantControlDataStreamPart, | |
[dataMessageStreamPart.code]: dataMessageStreamPart, | |
[toolCallStreamPart.code]: toolCallStreamPart, | |
[messageAnnotationsStreamPart.code]: messageAnnotationsStreamPart | |
}; | |
/** | |
* The map of prefixes for data in the stream | |
* | |
* - 0: Text from the LLM response | |
* - 1: (OpenAI) function_call responses | |
* - 2: custom JSON added by the user using `Data` | |
* - 6: (OpenAI) tool_call responses | |
* | |
* Example: | |
* ``` | |
* 0:Vercel | |
* 0:'s | |
* 0: AI | |
* 0: AI | |
* 0: SDK | |
* 0: is great | |
* 0:! | |
* 2: { "someJson": "value" } | |
* 1: {"function_call": {"name": "get_current_weather", "arguments": "{\\n\\"location\\": \\"Charlottesville, Virginia\\",\\n\\"format\\": \\"celsius\\"\\n}"}} | |
* 6: {"tool_call": {"id": "tool_0", "type": "function", "function": {"name": "get_current_weather", "arguments": "{\\n\\"location\\": \\"Charlottesville, Virginia\\",\\n\\"format\\": \\"celsius\\"\\n}"}}} | |
*``` | |
*/ | |
({ | |
[textStreamPart.name]: textStreamPart.code, | |
[functionCallStreamPart.name]: functionCallStreamPart.code, | |
[dataStreamPart.name]: dataStreamPart.code, | |
[errorStreamPart.name]: errorStreamPart.code, | |
[assistantMessageStreamPart.name]: assistantMessageStreamPart.code, | |
[assistantControlDataStreamPart.name]: assistantControlDataStreamPart.code, | |
[dataMessageStreamPart.name]: dataMessageStreamPart.code, | |
[toolCallStreamPart.name]: toolCallStreamPart.code, | |
[messageAnnotationsStreamPart.name]: messageAnnotationsStreamPart.code | |
}); | |
const validCodes = streamParts.map(part => part.code); | |
/** | |
Parses a stream part from a string. | |
@param line The string to parse. | |
@returns The parsed stream part. | |
@throws An error if the string cannot be parsed. | |
*/ | |
const parseStreamPart = line => { | |
const firstSeparatorIndex = line.indexOf(':'); | |
if (firstSeparatorIndex === -1) { | |
throw new Error('Failed to parse stream string. No separator found.'); | |
} | |
const prefix = line.slice(0, firstSeparatorIndex); | |
if (!validCodes.includes(prefix)) { | |
throw new Error(`Failed to parse stream string. Invalid code ${prefix}.`); | |
} | |
const code = prefix; | |
const textValue = line.slice(firstSeparatorIndex + 1); | |
const jsonValue = JSON.parse(textValue); | |
return streamPartsByCode[code].parse(jsonValue); | |
}; | |
const NEWLINE = '\n'.charCodeAt(0); | |
// concatenates all the chunks into a single Uint8Array | |
function concatChunks(chunks, totalLength) { | |
const concatenatedChunks = new Uint8Array(totalLength); | |
let offset = 0; | |
for (const chunk of chunks) { | |
concatenatedChunks.set(chunk, offset); | |
offset += chunk.length; | |
} | |
chunks.length = 0; | |
return concatenatedChunks; | |
} | |
/** | |
Converts a ReadableStreamDefaultReader into an async generator that yields | |
StreamPart objects. | |
@param reader | |
Reader for the stream to read from. | |
@param isAborted | |
Optional function that returns true if the request has been aborted. | |
If the function returns true, the generator will stop reading the stream. | |
If the function is not provided, the generator will not stop reading the stream. | |
*/ | |
async function* readDataStream(reader, { | |
isAborted | |
} = {}) { | |
// implementation note: this slightly more complex algorithm is required | |
// to pass the tests in the edge environment. | |
const decoder = new TextDecoder(); | |
const chunks = []; | |
let totalLength = 0; | |
while (true) { | |
const { | |
value | |
} = await reader.read(); | |
if (value) { | |
chunks.push(value); | |
totalLength += value.length; | |
if (value[value.length - 1] !== NEWLINE) { | |
// if the last character is not a newline, we have not read the whole JSON value | |
continue; | |
} | |
} | |
if (chunks.length === 0) { | |
break; // we have reached the end of the stream | |
} | |
const concatenatedChunks = concatChunks(chunks, totalLength); | |
totalLength = 0; | |
const streamParts = decoder.decode(concatenatedChunks, { | |
stream: true | |
}).split('\n').filter(line => line !== '') // splitting leaves an empty string at the end | |
.map(parseStreamPart); | |
for (const streamPart of streamParts) { | |
yield streamPart; | |
} | |
// The request has been aborted, stop reading the stream. | |
if (isAborted?.()) { | |
reader.cancel(); | |
break; | |
} | |
} | |
} | |
let abortController = null; | |
const secondsToWaitBeforeRetries = 3; | |
const maxRetryAttempts = 1; | |
const streamChat = context => async ({ | |
messages, | |
onMessageStream | |
}) => { | |
try { | |
abortController = new AbortController(); | |
const apiHost = context.apiHost; | |
const res = await fetch((isNotEmpty(apiHost) ? apiHost : guessApiHost()) + `/api/v2/sessions/${context.sessionId}/streamMessage`, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
messages | |
}), | |
signal: abortController.signal | |
}); | |
if (!res.ok) { | |
if ((context.retryAttempt ?? 0) < maxRetryAttempts && (res.status === 403 || res.status === 500 || res.status === 503)) { | |
await new Promise(resolve => setTimeout(resolve, secondsToWaitBeforeRetries * 1000)); | |
return streamChat({ | |
...context, | |
retryAttempt: (context.retryAttempt ?? 0) + 1 | |
})({ | |
messages, | |
onMessageStream | |
}); | |
} | |
return { | |
error: (await res.json()) || 'Failed to fetch the chat response.' | |
}; | |
} | |
if (!res.body) { | |
throw new Error('The response body is empty.'); | |
} | |
let message = ''; | |
const reader = res.body.getReader(); | |
const id = createUniqueId(); | |
for await (const { | |
type, | |
value | |
} of readDataStream(reader, { | |
isAborted: () => abortController === null | |
})) { | |
if (type === 'text') { | |
message += value; | |
if (onMessageStream) onMessageStream({ | |
id, | |
message | |
}); | |
} | |
} | |
abortController = null; | |
return { | |
message | |
}; | |
} catch (err) { | |
console.error(err); | |
// Ignore abort errors as they are expected. | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
if (err.name === 'AbortError') { | |
abortController = null; | |
return { | |
error: { | |
message: 'Request aborted' | |
} | |
}; | |
} | |
if (err instanceof Error) return { | |
error: { | |
message: err.message | |
} | |
}; | |
return { | |
error: { | |
message: 'Failed to fetch the chat response.' | |
} | |
}; | |
} | |
}; | |
const executeRedirect = ({ | |
url, | |
isNewTab | |
} = {}) => { | |
if (!url) return; | |
const updatedWindow = window.open(url, isNewTab ? '_blank' : '_top'); | |
if (!updatedWindow) return { | |
blockedPopupUrl: url | |
}; | |
}; | |
const safeStringify = val => { | |
if (isNotDefined(val)) return null; | |
if (typeof val === 'string') return val; | |
try { | |
return JSON.stringify(val); | |
} catch { | |
console.warn('Failed to safely stringify variable value', val); | |
return null; | |
} | |
}; | |
// eslint-disable-next-line @typescript-eslint/no-empty-function | |
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; | |
const executeSetVariable = async ({ | |
content, | |
args, | |
isCode | |
}) => { | |
try { | |
// To avoid octal number evaluation | |
if (!isNaN(content) && /0[^.].+/.test(content)) return { | |
replyToSend: content | |
}; | |
const func = AsyncFunction(...args.map(arg => arg.id), content.includes('return ') ? content : `return ${content}`); | |
const replyToSend = await func(...args.map(arg => arg.value)); | |
return { | |
replyToSend: safeStringify(replyToSend) ?? undefined | |
}; | |
} catch (err) { | |
console.error(err); | |
return { | |
replyToSend: safeStringify(content) ?? undefined, | |
logs: isCode ? [{ | |
status: 'error', | |
description: 'Failed to execute Set Variable code', | |
details: stringifyError(err) | |
}] : undefined | |
}; | |
} | |
}; | |
const executeWait = async ({ | |
secondsToWaitFor | |
}) => { | |
await new Promise(resolve => setTimeout(resolve, secondsToWaitFor * 1000)); | |
}; | |
const executeWebhook = async webhookToExecute => { | |
const { | |
url, | |
method, | |
body, | |
headers | |
} = webhookToExecute; | |
try { | |
const response = await fetch(url, { | |
method, | |
body: method !== 'GET' && body ? JSON.stringify(body) : undefined, | |
headers | |
}); | |
const statusCode = response.status; | |
const data = await response.json(); | |
return JSON.stringify({ | |
statusCode, | |
data | |
}); | |
} catch (error) { | |
console.error(error); | |
return JSON.stringify({ | |
statusCode: 500, | |
data: 'An error occured while executing the webhook on the client' | |
}); | |
} | |
}; | |
const initPixel = pixelIds => { | |
const script = document.createElement('script'); | |
script.innerHTML = `!function(f,b,e,v,n,t,s) | |
{if(f.fbq)return;n=f.fbq=function(){n.callMethod? | |
n.callMethod.apply(n,arguments):n.queue.push(arguments)}; | |
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; | |
n.queue=[];t=b.createElement(e);t.async=!0; | |
t.src=v;s=b.getElementsByTagName(e)[0]; | |
s.parentNode.insertBefore(t,s)}(window, document,'script', | |
'https://connect.facebook.net/en_US/fbevents.js'); | |
${pixelIds.map(pixelId => `fbq('init', '${pixelId}');`).join('\n')} | |
fbq('track', 'PageView');`; | |
document.head.appendChild(script); | |
}; | |
const trackPixelEvent = options => { | |
if (!options?.eventType || !options.pixelId) return; | |
if (!window.fbq) { | |
console.error('Facebook Pixel was not properly initialized'); | |
return; | |
} | |
const params = options.params?.length ? options.params.reduce((obj, param) => { | |
if (!param.key || !param.value) return obj; | |
return { | |
...obj, | |
[param.key]: param.value | |
}; | |
}, {}) : undefined; | |
if (options.eventType === 'Custom') { | |
if (!options.name) return; | |
window.fbq('trackSingleCustom', options.pixelId, options.name, params); | |
} | |
window.fbq('trackSingle', options.pixelId, options.eventType, params); | |
}; | |
const executePixel = async options => { | |
if (isEmpty$2(options?.pixelId)) return; | |
trackPixelEvent(options); | |
}; | |
const gtmBodyElement = googleTagManagerId => { | |
if (document.getElementById('gtm-noscript')) return ''; | |
const noScriptElement = document.createElement('noscript'); | |
noScriptElement.id = 'gtm-noscript'; | |
const iframeElement = document.createElement('iframe'); | |
iframeElement.src = `https://www.googletagmanager.com/ns.html?id=${googleTagManagerId}`; | |
iframeElement.height = '0'; | |
iframeElement.width = '0'; | |
iframeElement.style.display = 'none'; | |
iframeElement.style.visibility = 'hidden'; | |
noScriptElement.appendChild(iframeElement); | |
return noScriptElement; | |
}; | |
const injectStartProps = async startPropsToInject => { | |
const customHeadCode = startPropsToInject.customHeadCode; | |
if (isNotEmpty(customHeadCode)) injectCustomHeadCode(customHeadCode); | |
const gtmId = startPropsToInject.gtmId; | |
if (isNotEmpty(gtmId)) document.body.prepend(gtmBodyElement(gtmId)); | |
const googleAnalyticsId = startPropsToInject.googleAnalyticsId; | |
if (isNotEmpty(googleAnalyticsId)) await initGoogleAnalytics(googleAnalyticsId); | |
const pixelIds = startPropsToInject.pixelIds; | |
if (isDefined(pixelIds)) initPixel(pixelIds); | |
}; | |
const executeClientSideAction = async ({ | |
clientSideAction, | |
context, | |
onMessageStream | |
}) => { | |
if ('chatwoot' in clientSideAction) { | |
return executeChatwoot(clientSideAction.chatwoot); | |
} | |
if ('googleAnalytics' in clientSideAction) { | |
return executeGoogleAnalyticsBlock(clientSideAction.googleAnalytics); | |
} | |
if ('scriptToExecute' in clientSideAction) { | |
return executeScript(clientSideAction.scriptToExecute); | |
} | |
if ('redirect' in clientSideAction) { | |
return executeRedirect(clientSideAction.redirect); | |
} | |
if ('wait' in clientSideAction) { | |
await executeWait(clientSideAction.wait); | |
return clientSideAction.expectsDedicatedReply ? { | |
replyToSend: undefined | |
} : undefined; | |
} | |
if ('setVariable' in clientSideAction) { | |
return executeSetVariable(clientSideAction.setVariable.scriptToExecute); | |
} | |
if ('streamOpenAiChatCompletion' in clientSideAction || 'stream' in clientSideAction) { | |
const { | |
error, | |
message | |
} = await streamChat(context)({ | |
messages: 'streamOpenAiChatCompletion' in clientSideAction ? clientSideAction.streamOpenAiChatCompletion?.messages : undefined, | |
onMessageStream | |
}); | |
if (error) return { | |
replyToSend: undefined, | |
logs: [{ | |
status: 'error', | |
description: 'Message streaming returned an error', | |
details: JSON.stringify(error, null, 2) | |
}] | |
}; | |
return { | |
replyToSend: message | |
}; | |
} | |
if ('webhookToExecute' in clientSideAction) { | |
const response = await executeWebhook(clientSideAction.webhookToExecute); | |
return { | |
replyToSend: response | |
}; | |
} | |
if ('startPropsToInject' in clientSideAction) { | |
return injectStartProps(clientSideAction.startPropsToInject); | |
} | |
if ('pixel' in clientSideAction) { | |
return executePixel(clientSideAction.pixel); | |
} | |
if ('codeToExecute' in clientSideAction) { | |
return executeCode(clientSideAction.codeToExecute); | |
} | |
}; | |
const _tmpl$$9 = /*#__PURE__*/template(`<div class="flex flex-col animate-fade-in"><div class="flex w-full items-center"><div class="flex relative items-start typebot-host-bubble"><div class="flex items-center absolute px-4 py-2 bubble-typing " data-testid="host-bubble"></div><p class="overflow-hidden text-fade-in mx-4 my-2 whitespace-pre-wrap slate-html-container relative opacity-0 h-6 text-ellipsis">`); | |
const LoadingBubble = () => (() => { | |
const _el$ = _tmpl$$9(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.firstChild; | |
_el$4.style.setProperty("width", "64px"); | |
_el$4.style.setProperty("height", "32px"); | |
insert(_el$4, createComponent(TypingBubble, {})); | |
return _el$; | |
})(); | |
const _tmpl$$8 = /*#__PURE__*/template(`<div class="flex w-full typebot-loading-chunk"><div class="flex flex-col w-full min-w-0"><div class="flex gap-2">`); | |
const LoadingChunk = props => (() => { | |
const _el$ = _tmpl$$8(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.firstChild; | |
insert(_el$3, createComponent(Show, { | |
get when() { | |
return props.theme.chat?.hostAvatar?.isEnabled ?? defaultHostAvatarIsEnabled; | |
}, | |
get children() { | |
return createComponent(AvatarSideContainer, { | |
get hostAvatarSrc() { | |
return props.theme.chat?.hostAvatar?.url; | |
} | |
}); | |
} | |
}), null); | |
insert(_el$3, createComponent(LoadingBubble, {}), null); | |
return _el$; | |
})(); | |
const _tmpl$$7 = /*#__PURE__*/template(`<div class="w-full max-w-xs p-4 text-gray-500 bg-white shadow flex flex-col gap-2 typebot-popup-blocked-toast" role="alert"><div class="flex flex-col gap-1"><span class=" text-sm font-semibold text-gray-900">Popup blocked</span><div class="text-sm font-normal">The bot wants to open a new tab but it was blocked by your browser. It needs a manual approval.</div></div><a target="_blank" class="py-1 px-4 justify-center text-sm font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 filter hover:brightness-90 active:brightness-75 typebot-button" rel="noreferrer">Continue in new tab`); | |
const PopupBlockedToast = props => { | |
return (() => { | |
const _el$ = _tmpl$$7(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.nextSibling; | |
_el$3.$$click = () => props.onLinkClick(); | |
createRenderEffect(() => setAttribute(_el$3, "href", props.url)); | |
return _el$; | |
})(); | |
}; | |
delegateEvents(["click"]); | |
const saveClientLogsQuery = async ({ | |
apiHost, | |
sessionId, | |
clientLogs | |
}) => { | |
try { | |
await ky$1.post(`${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sessions/${sessionId}/clientLogs`, { | |
json: { | |
clientLogs | |
} | |
}); | |
} catch (e) { | |
console.log(e); | |
} | |
}; | |
const _tmpl$$6 = /*#__PURE__*/template(`<div class="flex flex-col overflow-y-auto w-full px-3 pt-10 relative scrollable-container typebot-chat-view scroll-smooth gap-2">`), | |
_tmpl$2$3 = /*#__PURE__*/template(`<div class="flex justify-end">`), | |
_tmpl$3$1 = /*#__PURE__*/template(`<div class="w-full flex-shrink-0 typebot-bottom-spacer">`); | |
const autoScrollBottomToleranceScreenPercent = 0.6; | |
const bottomSpacerHeight = 128; | |
const parseDynamicTheme = (initialTheme, dynamicTheme) => ({ | |
...initialTheme, | |
chat: { | |
...initialTheme.chat, | |
hostAvatar: initialTheme.chat?.hostAvatar && dynamicTheme?.hostAvatarUrl ? { | |
...initialTheme.chat.hostAvatar, | |
url: dynamicTheme.hostAvatarUrl | |
} : initialTheme.chat?.hostAvatar, | |
guestAvatar: initialTheme.chat?.guestAvatar && dynamicTheme?.guestAvatarUrl ? { | |
...initialTheme.chat.guestAvatar, | |
url: dynamicTheme?.guestAvatarUrl | |
} : initialTheme.chat?.guestAvatar | |
} | |
}); | |
const ConversationContainer = props => { | |
let chatContainer; | |
const [chatChunks, setChatChunks, isRecovered] = persist(createSignal([{ | |
input: props.initialChatReply.input, | |
messages: props.initialChatReply.messages, | |
clientSideActions: props.initialChatReply.clientSideActions | |
}]), { | |
key: `typebot-${props.context.typebot.id}-chatChunks`, | |
storage: props.context.storage, | |
onRecovered: () => { | |
setTimeout(() => { | |
chatContainer?.scrollTo(0, chatContainer.scrollHeight); | |
}, 200); | |
} | |
}); | |
const [dynamicTheme, setDynamicTheme] = createSignal(props.initialChatReply.dynamicTheme); | |
const [theme, setTheme] = createSignal(props.initialChatReply.typebot.theme); | |
const [isSending, setIsSending] = createSignal(false); | |
const [blockedPopupUrl, setBlockedPopupUrl] = createSignal(); | |
const [hasError, setHasError] = createSignal(false); | |
onMount(() => { | |
(async () => { | |
const initialChunk = chatChunks()[0]; | |
if (!initialChunk.clientSideActions) return; | |
const actionsBeforeFirstBubble = initialChunk.clientSideActions.filter(action => isNotDefined(action.lastBubbleBlockId)); | |
await processClientSideActions(actionsBeforeFirstBubble); | |
})(); | |
}); | |
const streamMessage = ({ | |
id, | |
message | |
}) => { | |
setIsSending(false); | |
const lastChunk = [...chatChunks()].pop(); | |
if (!lastChunk) return; | |
if (lastChunk.streamingMessageId !== id) setChatChunks(displayedChunks => [...displayedChunks, { | |
messages: [], | |
streamingMessageId: id | |
}]); | |
setStreamingMessage({ | |
id, | |
content: message | |
}); | |
}; | |
createEffect(() => { | |
setTheme(parseDynamicTheme(props.initialChatReply.typebot.theme, dynamicTheme())); | |
}); | |
const saveLogs = async clientLogs => { | |
if (!clientLogs) return; | |
props.onNewLogs?.(clientLogs); | |
if (props.context.isPreview) return; | |
await saveClientLogsQuery({ | |
apiHost: props.context.apiHost, | |
sessionId: props.initialChatReply.sessionId, | |
clientLogs | |
}); | |
}; | |
const sendMessage = async (message, attachments) => { | |
setHasError(false); | |
const currentInputBlock = [...chatChunks()].pop()?.input; | |
if (currentInputBlock?.id && props.onAnswer && message) props.onAnswer({ | |
message, | |
blockId: currentInputBlock.id | |
}); | |
const longRequest = setTimeout(() => { | |
setIsSending(true); | |
}, 1000); | |
autoScrollToBottom(); | |
const { | |
data, | |
error | |
} = await continueChatQuery({ | |
apiHost: props.context.apiHost, | |
sessionId: props.initialChatReply.sessionId, | |
message: message ? { | |
type: 'text', | |
text: message, | |
attachedFileUrls: attachments?.map(attachment => attachment.url) | |
} : undefined | |
}); | |
clearTimeout(longRequest); | |
setIsSending(false); | |
if (error) { | |
setHasError(true); | |
const errorLogs = [{ | |
description: 'Failed to send the reply', | |
details: error instanceof HTTPError ? { | |
status: error.response.status, | |
body: await error.response.json() | |
} : error, | |
status: 'error' | |
}]; | |
await saveClientLogsQuery({ | |
apiHost: props.context.apiHost, | |
sessionId: props.initialChatReply.sessionId, | |
clientLogs: errorLogs | |
}); | |
props.onNewLogs?.(errorLogs); | |
return; | |
} | |
if (!data) return; | |
if (data.progress) props.onProgressUpdate?.(data.progress); | |
if (data.lastMessageNewFormat) { | |
setFormattedMessages([...formattedMessages(), { | |
inputIndex: [...chatChunks()].length - 1, | |
formattedMessage: data.lastMessageNewFormat | |
}]); | |
} | |
if (data.logs) props.onNewLogs?.(data.logs); | |
if (data.dynamicTheme) setDynamicTheme(data.dynamicTheme); | |
if (data.input && props.onNewInputBlock) { | |
props.onNewInputBlock(data.input); | |
} | |
if (data.clientSideActions) { | |
const actionsBeforeFirstBubble = data.clientSideActions.filter(action => isNotDefined(action.lastBubbleBlockId)); | |
await processClientSideActions(actionsBeforeFirstBubble); | |
if (data.clientSideActions.length === 1 && data.clientSideActions[0].type === 'stream' && data.messages.length === 0 && data.input === undefined) return; | |
} | |
setChatChunks(displayedChunks => [...displayedChunks, { | |
input: data.input, | |
messages: data.messages, | |
clientSideActions: data.clientSideActions | |
}]); | |
}; | |
const autoScrollToBottom = (lastElement, offset = 0) => { | |
if (!chatContainer) return; | |
const bottomTolerance = chatContainer.clientHeight * autoScrollBottomToleranceScreenPercent - bottomSpacerHeight; | |
const isBottomOfLastElementInView = chatContainer.scrollTop + chatContainer.clientHeight >= chatContainer.scrollHeight - bottomTolerance; | |
if (isBottomOfLastElementInView) { | |
setTimeout(() => { | |
chatContainer?.scrollTo(0, lastElement ? lastElement.offsetTop - offset : chatContainer.scrollHeight); | |
}, 50); | |
} | |
}; | |
const handleAllBubblesDisplayed = async () => { | |
const lastChunk = [...chatChunks()].pop(); | |
if (!lastChunk) return; | |
if (isNotDefined(lastChunk.input)) { | |
props.onEnd?.(); | |
} | |
}; | |
const handleNewBubbleDisplayed = async blockId => { | |
const lastChunk = [...chatChunks()].pop(); | |
if (!lastChunk) return; | |
if (lastChunk.clientSideActions) { | |
const actionsToExecute = lastChunk.clientSideActions.filter(action => action.lastBubbleBlockId === blockId); | |
await processClientSideActions(actionsToExecute); | |
} | |
}; | |
const processClientSideActions = async actions => { | |
if (isRecovered()) return; | |
for (const action of actions) { | |
if ('streamOpenAiChatCompletion' in action || 'webhookToExecute' in action || 'stream' in action) setIsSending(true); | |
const response = await executeClientSideAction({ | |
clientSideAction: action, | |
context: { | |
apiHost: props.context.apiHost, | |
sessionId: props.initialChatReply.sessionId | |
}, | |
onMessageStream: streamMessage | |
}); | |
if (response && 'logs' in response) saveLogs(response.logs); | |
if (response && 'replyToSend' in response) { | |
setIsSending(false); | |
sendMessage(response.replyToSend); | |
return; | |
} | |
if (response && 'blockedPopupUrl' in response) setBlockedPopupUrl(response.blockedPopupUrl); | |
} | |
}; | |
onCleanup(() => { | |
setStreamingMessage(undefined); | |
setFormattedMessages([]); | |
}); | |
const handleSkip = () => sendMessage(undefined); | |
return (() => { | |
const _el$ = _tmpl$$6(); | |
const _ref$ = chatContainer; | |
typeof _ref$ === "function" ? use(_ref$, _el$) : chatContainer = _el$; | |
insert(_el$, createComponent(For, { | |
get each() { | |
return chatChunks(); | |
}, | |
children: (chatChunk, index) => createComponent(ChatChunk, { | |
get index() { | |
return index(); | |
}, | |
get messages() { | |
return chatChunk.messages; | |
}, | |
get input() { | |
return chatChunk.input; | |
}, | |
get theme() { | |
return theme(); | |
}, | |
get settings() { | |
return props.initialChatReply.typebot.settings; | |
}, | |
get streamingMessageId() { | |
return chatChunk.streamingMessageId; | |
}, | |
get context() { | |
return props.context; | |
}, | |
get hideAvatar() { | |
return createMemo(() => !!!chatChunk.input)() && ((chatChunks()[index() + 1]?.messages ?? 0).length > 0 || chatChunks()[index() + 1]?.streamingMessageId !== undefined || chatChunk.messages.length > 0 && isSending()); | |
}, | |
get hasError() { | |
return createMemo(() => !!hasError())() && index() === chatChunks().length - 1; | |
}, | |
get isTransitionDisabled() { | |
return index() !== chatChunks().length - 1; | |
}, | |
onNewBubbleDisplayed: handleNewBubbleDisplayed, | |
onAllBubblesDisplayed: handleAllBubblesDisplayed, | |
onSubmit: sendMessage, | |
onScrollToBottom: autoScrollToBottom, | |
onSkip: handleSkip | |
}) | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return isSending(); | |
}, | |
get children() { | |
return createComponent(LoadingChunk, { | |
get theme() { | |
return theme(); | |
} | |
}); | |
} | |
}), null); | |
insert(_el$, createComponent(Show, { | |
get when() { | |
return blockedPopupUrl(); | |
}, | |
keyed: true, | |
children: blockedPopupUrl => (() => { | |
const _el$2 = _tmpl$2$3(); | |
insert(_el$2, createComponent(PopupBlockedToast, { | |
url: blockedPopupUrl, | |
onLinkClick: () => setBlockedPopupUrl(undefined) | |
})); | |
return _el$2; | |
})() | |
}), null); | |
insert(_el$, createComponent(BottomSpacer, {}), null); | |
return _el$; | |
})(); | |
}; | |
const BottomSpacer = () => (() => { | |
const _el$3 = _tmpl$3$1(); | |
_el$3.style.setProperty("height", "128px"); | |
return _el$3; | |
})(); | |
const _tmpl$$5 = /*#__PURE__*/template(`<div class="h-full flex justify-center items-center flex-col"><p class="text-2xl text-center"></p><pre>`); | |
const ErrorMessage = props => { | |
return (() => { | |
const _el$ = _tmpl$$5(), | |
_el$2 = _el$.firstChild, | |
_el$3 = _el$2.nextSibling; | |
insert(_el$2, () => props.error.message); | |
insert(_el$3, () => JSON.stringify(props.error.cause, null, 2)); | |
return _el$; | |
})(); | |
}; | |
const storageResultIdKey = 'resultId'; | |
const getExistingResultIdFromStorage = typebotId => { | |
if (!typebotId) return; | |
try { | |
return sessionStorage.getItem(`${storageResultIdKey}-${typebotId}`) ?? localStorage.getItem(`${storageResultIdKey}-${typebotId}`) ?? undefined; | |
} catch { | |
/* empty */ | |
} | |
}; | |
const setResultInStorage = (storageType = 'session') => (typebotId, resultId) => { | |
try { | |
parseRememberUserStorage(storageType).setItem(`${storageResultIdKey}-${typebotId}`, resultId); | |
} catch { | |
/* empty */ | |
} | |
}; | |
const getInitialChatReplyFromStorage = typebotId => { | |
if (!typebotId) return; | |
try { | |
const rawInitialChatReply = sessionStorage.getItem(`typebot-${typebotId}-initialChatReply`) ?? localStorage.getItem(`typebot-${typebotId}-initialChatReply`); | |
if (!rawInitialChatReply) return; | |
return JSON.parse(rawInitialChatReply); | |
} catch { | |
/* empty */ | |
} | |
}; | |
const setInitialChatReplyInStorage = (initialChatReply, { | |
typebotId, | |
storage | |
}) => { | |
try { | |
const rawInitialChatReply = JSON.stringify(initialChatReply); | |
parseRememberUserStorage(storage).setItem(`typebot-${typebotId}-initialChatReply`, rawInitialChatReply); | |
} catch { | |
/* empty */ | |
} | |
}; | |
const setBotOpenedStateInStorage = () => { | |
try { | |
sessionStorage.setItem(`typebot-botOpened`, 'true'); | |
} catch { | |
/* empty */ | |
} | |
}; | |
const removeBotOpenedStateInStorage = () => { | |
try { | |
sessionStorage.removeItem(`typebot-botOpened`); | |
} catch { | |
/* empty */ | |
} | |
}; | |
const getBotOpenedStateFromStorage = () => { | |
try { | |
return sessionStorage.getItem(`typebot-botOpened`) === 'true'; | |
} catch { | |
return false; | |
} | |
}; | |
const parseRememberUserStorage = storage => (storage ?? defaultSettings.general.rememberUser.storage) === 'session' ? sessionStorage : localStorage; | |
const wipeExistingChatStateInStorage = typebotId => { | |
Object.keys(localStorage).forEach(key => { | |
if (key.startsWith(`typebot-${typebotId}`)) localStorage.removeItem(key); | |
}); | |
Object.keys(sessionStorage).forEach(key => { | |
if (key.startsWith(`typebot-${typebotId}`)) sessionStorage.removeItem(key); | |
}); | |
}; | |
const isChatContainerLight = ({ | |
chatContainer, | |
generalBackground | |
}) => { | |
const chatContainerBgColor = chatContainer?.backgroundColor ?? defaultContainerBackgroundColor; | |
const ignoreChatBackground = (chatContainer?.opacity ?? defaultOpacity) <= 0.3 || chatContainerBgColor === 'transparent' || isEmpty$2(chatContainerBgColor); | |
if (ignoreChatBackground) { | |
const bgType = generalBackground?.type ?? defaultBackgroundType; | |
const backgroundColor = bgType === BackgroundType.IMAGE ? '#000000' : bgType === BackgroundType.COLOR && isNotEmpty(generalBackground?.content) ? generalBackground.content : '#ffffff'; | |
return isLight(backgroundColor); | |
} | |
return isLight(chatContainer?.backgroundColor ?? defaultBackgroundColor); | |
}; | |
const cssVariableNames = { | |
general: { | |
bgImage: '--typebot-container-bg-image', | |
bgColor: '--typebot-container-bg-color', | |
fontFamily: '--typebot-container-font-family', | |
progressBar: { | |
position: '--typebot-progress-bar-position', | |
color: '--typebot-progress-bar-color', | |
colorRgb: '--typebot-progress-bar-bg-rgb', | |
height: '--typebot-progress-bar-height', | |
top: '--typebot-progress-bar-top', | |
bottom: '--typebot-progress-bar-bottom' | |
} | |
}, | |
chat: { | |
container: { | |
maxWidth: '--typebot-chat-container-max-width', | |
maxHeight: '--typebot-chat-container-max-height', | |
bgColor: '--typebot-chat-container-bg-rgb', | |
color: '--typebot-chat-container-color', | |
borderRadius: '--typebot-chat-container-border-radius', | |
borderWidth: '--typebot-chat-container-border-width', | |
borderColor: '--typebot-chat-container-border-rgb', | |
borderOpacity: '--typebot-chat-container-border-opacity', | |
opacity: '--typebot-chat-container-opacity', | |
blur: '--typebot-chat-container-blur', | |
boxShadow: '--typebot-chat-container-box-shadow' | |
}, | |
hostBubbles: { | |
bgColor: '--typebot-host-bubble-bg-rgb', | |
color: '--typebot-host-bubble-color', | |
borderRadius: '--typebot-host-bubble-border-radius', | |
borderWidth: '--typebot-host-bubble-border-width', | |
borderColor: '--typebot-host-bubble-border-rgb', | |
borderOpacity: '--typebot-host-bubble-border-opacity', | |
opacity: '--typebot-host-bubble-opacity', | |
blur: '--typebot-host-bubble-blur', | |
boxShadow: '--typebot-host-bubble-box-shadow' | |
}, | |
guestBubbles: { | |
bgColor: '--typebot-guest-bubble-bg-rgb', | |
color: '--typebot-guest-bubble-color', | |
borderRadius: '--typebot-guest-bubble-border-radius', | |
borderWidth: '--typebot-guest-bubble-border-width', | |
borderColor: '--typebot-guest-bubble-border-rgb', | |
borderOpacity: '--typebot-guest-bubble-border-opacity', | |
opacity: '--typebot-guest-bubble-opacity', | |
blur: '--typebot-guest-bubble-blur', | |
boxShadow: '--typebot-guest-bubble-box-shadow' | |
}, | |
inputs: { | |
bgColor: '--typebot-input-bg-rgb', | |
color: '--typebot-input-color', | |
placeholderColor: '--typebot-input-placeholder-color', | |
borderRadius: '--typebot-input-border-radius', | |
borderWidth: '--typebot-input-border-width', | |
borderColor: '--typebot-input-border-rgb', | |
borderOpacity: '--typebot-input-border-opacity', | |
opacity: '--typebot-input-opacity', | |
blur: '--typebot-input-blur', | |
boxShadow: '--typebot-input-box-shadow' | |
}, | |
buttons: { | |
bgRgb: '--typebot-button-bg-rgb', | |
color: '--typebot-button-color', | |
borderRadius: '--typebot-button-border-radius', | |
borderWidth: '--typebot-button-border-width', | |
borderColor: '--typebot-button-border-rgb', | |
borderOpacity: '--typebot-button-border-opacity', | |
opacity: '--typebot-button-opacity', | |
blur: '--typebot-button-blur', | |
boxShadow: '--typebot-button-box-shadow' | |
}, | |
checkbox: { | |
bgRgb: '--typebot-checkbox-bg-rgb', | |
alphaRatio: '--selectable-alpha-ratio' | |
} | |
} | |
}; | |
const setCssVariablesValue = (theme, container, isPreview) => { | |
if (!theme) return; | |
const documentStyle = container?.style; | |
if (!documentStyle) return; | |
setGeneralTheme(theme.general, documentStyle, isPreview); | |
setChatTheme(theme.chat, theme.general?.background, documentStyle); | |
}; | |
const setGeneralTheme = (generalTheme, documentStyle, isPreview) => { | |
setGeneralBackground(generalTheme?.background, documentStyle); | |
documentStyle.setProperty(cssVariableNames.general.fontFamily, (typeof generalTheme?.font === 'string' ? generalTheme.font : generalTheme?.font?.family) ?? defaultFontFamily); | |
setProgressBar(generalTheme?.progressBar, documentStyle, isPreview); | |
}; | |
const setProgressBar = (progressBar, documentStyle, isPreview) => { | |
const position = progressBar?.position ?? defaultProgressBarPosition; | |
documentStyle.setProperty(cssVariableNames.general.progressBar.position, position === 'fixed' ? isPreview ? 'absolute' : 'fixed' : position); | |
documentStyle.setProperty(cssVariableNames.general.progressBar.color, progressBar?.color ?? defaultProgressBarColor); | |
documentStyle.setProperty(cssVariableNames.general.progressBar.colorRgb, hexToRgb(progressBar?.backgroundColor ?? defaultProgressBarBackgroundColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.general.progressBar.height, `${progressBar?.thickness ?? defaultProgressBarThickness}px`); | |
const placement = progressBar?.placement ?? defaultProgressBarPlacement; | |
documentStyle.setProperty(cssVariableNames.general.progressBar.top, placement === 'Top' ? '0' : 'auto'); | |
documentStyle.setProperty(cssVariableNames.general.progressBar.bottom, placement === 'Bottom' ? '0' : 'auto'); | |
}; | |
const setChatTheme = (chatTheme, generalBackground, documentStyle) => { | |
setChatContainer(chatTheme?.container, generalBackground, documentStyle, chatTheme?.roundness); | |
setHostBubbles(chatTheme?.hostBubbles, documentStyle, chatTheme?.roundness); | |
setGuestBubbles(chatTheme?.guestBubbles, documentStyle, chatTheme?.roundness); | |
setButtons(chatTheme?.buttons, documentStyle, chatTheme?.roundness); | |
setInputs(chatTheme?.inputs, documentStyle, chatTheme?.roundness); | |
setCheckbox(chatTheme?.container, generalBackground, documentStyle); | |
}; | |
const setChatContainer = (container, generalBackground, documentStyle, legacyRoundness) => { | |
const chatContainerBgColor = container?.backgroundColor ?? defaultContainerBackgroundColor; | |
const isBgDisabled = chatContainerBgColor === 'transparent' || isEmpty$2(chatContainerBgColor); | |
documentStyle.setProperty(cssVariableNames.chat.container.bgColor, isBgDisabled ? '0, 0, 0' : hexToRgb(chatContainerBgColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.container.color, hexToRgb(container?.color ?? (isChatContainerLight({ | |
chatContainer: container, | |
generalBackground | |
}) ? defaultLightTextColor : defaultDarkTextColor)).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.container.maxWidth, container?.maxWidth ?? defaultContainerMaxWidth); | |
documentStyle.setProperty(cssVariableNames.chat.container.maxHeight, container?.maxHeight ?? defaultContainerMaxHeight); | |
const opacity = isBgDisabled ? '1' : (container?.opacity ?? defaultOpacity).toString(); | |
documentStyle.setProperty(cssVariableNames.chat.container.opacity, isBgDisabled ? '0' : (container?.opacity ?? defaultOpacity).toString()); | |
documentStyle.setProperty(cssVariableNames.chat.container.blur, opacity === '1' || isBgDisabled ? '0xp' : `${container?.blur ?? defaultBlur}px`); | |
setShadow(container?.shadow, documentStyle, cssVariableNames.chat.container.boxShadow); | |
setBorderRadius(container?.border ?? { | |
roundeness: legacyRoundness ?? defaultRoundness | |
}, documentStyle, cssVariableNames.chat.container.borderRadius); | |
documentStyle.setProperty(cssVariableNames.chat.container.borderWidth, isDefined(container?.border?.thickness) ? `${container?.border?.thickness}px` : '0'); | |
documentStyle.setProperty(cssVariableNames.chat.container.borderOpacity, isDefined(container?.border?.opacity) ? container.border.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.container.borderColor, hexToRgb(container?.border?.color ?? '').join(', ')); | |
}; | |
const setHostBubbles = (hostBubbles, documentStyle, legacyRoundness) => { | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.bgColor, hexToRgb(hostBubbles?.backgroundColor ?? defaultHostBubblesBackgroundColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.color, hostBubbles?.color ?? defaultHostBubblesColor); | |
setBorderRadius(hostBubbles?.border ?? { | |
roundeness: legacyRoundness ?? defaultRoundness | |
}, documentStyle, cssVariableNames.chat.hostBubbles.borderRadius); | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.borderWidth, isDefined(hostBubbles?.border?.thickness) ? `${hostBubbles?.border?.thickness}px` : '0'); | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.borderColor, hexToRgb(hostBubbles?.border?.color ?? '').join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.opacity, hostBubbles?.backgroundColor === 'transparent' ? '0' : isDefined(hostBubbles?.opacity) ? hostBubbles.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.borderOpacity, isDefined(hostBubbles?.border?.opacity) ? hostBubbles.border.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.hostBubbles.blur, isDefined(hostBubbles?.blur) ? `${hostBubbles.blur ?? 0}px` : 'none'); | |
setShadow(hostBubbles?.shadow, documentStyle, cssVariableNames.chat.hostBubbles.boxShadow); | |
}; | |
const setGuestBubbles = (guestBubbles, documentStyle, legacyRoundness) => { | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.bgColor, hexToRgb(guestBubbles?.backgroundColor ?? defaultGuestBubblesBackgroundColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.color, guestBubbles?.color ?? defaultGuestBubblesColor); | |
setBorderRadius(guestBubbles?.border ?? { | |
roundeness: legacyRoundness ?? defaultRoundness | |
}, documentStyle, cssVariableNames.chat.guestBubbles.borderRadius); | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.borderWidth, isDefined(guestBubbles?.border?.thickness) ? `${guestBubbles?.border?.thickness}px` : '0'); | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.borderColor, hexToRgb(guestBubbles?.border?.color ?? '').join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.borderOpacity, isDefined(guestBubbles?.border?.opacity) ? guestBubbles.border.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.opacity, guestBubbles?.backgroundColor === 'transparent' ? '0' : isDefined(guestBubbles?.opacity) ? guestBubbles.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.guestBubbles.blur, isDefined(guestBubbles?.blur) ? `${guestBubbles.blur ?? 0}px` : 'none'); | |
setShadow(guestBubbles?.shadow, documentStyle, cssVariableNames.chat.guestBubbles.boxShadow); | |
}; | |
const setButtons = (buttons, documentStyle, legacyRoundness) => { | |
const bgColor = buttons?.backgroundColor ?? defaultButtonsBackgroundColor; | |
documentStyle.setProperty(cssVariableNames.chat.buttons.bgRgb, hexToRgb(bgColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.bgRgb, hexToRgb(bgColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.color, buttons?.color ?? defaultButtonsColor); | |
setBorderRadius(buttons?.border ?? { | |
roundeness: legacyRoundness ?? defaultRoundness | |
}, documentStyle, cssVariableNames.chat.buttons.borderRadius); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.borderWidth, isDefined(buttons?.border?.thickness) ? `${buttons?.border?.thickness}px` : `${defaultButtonsBorderThickness}px`); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.borderColor, hexToRgb(buttons?.border?.color ?? buttons?.backgroundColor ?? defaultButtonsBackgroundColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.borderOpacity, isDefined(buttons?.border?.opacity) ? buttons.border.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.opacity, buttons?.backgroundColor === 'transparent' ? '0' : isDefined(buttons?.opacity) ? buttons.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.buttons.blur, isDefined(buttons?.blur) ? `${buttons.blur ?? 0}px` : 'none'); | |
setShadow(buttons?.shadow, documentStyle, cssVariableNames.chat.buttons.boxShadow); | |
}; | |
const setInputs = (inputs, documentStyle, legacyRoundness) => { | |
documentStyle.setProperty(cssVariableNames.chat.inputs.bgColor, hexToRgb(inputs?.backgroundColor ?? defaultInputsBackgroundColor).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.color, inputs?.color ?? defaultInputsColor); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.placeholderColor, inputs?.placeholderColor ?? defaultInputsPlaceholderColor); | |
setBorderRadius(inputs?.border ?? { | |
roundeness: legacyRoundness ?? defaultRoundness | |
}, documentStyle, cssVariableNames.chat.inputs.borderRadius); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.borderWidth, isDefined(inputs?.border?.thickness) ? `${inputs?.border?.thickness}px` : '0'); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.borderColor, hexToRgb(inputs?.border?.color ?? '').join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.borderOpacity, isDefined(inputs?.border?.opacity) ? inputs.border.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.opacity, inputs?.backgroundColor === 'transparent' ? '0' : isDefined(inputs?.opacity) ? inputs.opacity.toString() : defaultOpacity.toString()); | |
documentStyle.setProperty(cssVariableNames.chat.inputs.blur, isDefined(inputs?.blur) ? `${inputs.blur ?? 0}px` : 'none'); | |
setShadow(inputs?.shadow ?? defaultInputsShadow, documentStyle, cssVariableNames.chat.inputs.boxShadow); | |
}; | |
const setCheckbox = (container, generalBackground, documentStyle) => { | |
const chatContainerBgColor = container?.backgroundColor ?? defaultContainerBackgroundColor; | |
const isChatBgTransparent = chatContainerBgColor === 'transparent' || isEmpty$2(chatContainerBgColor) || (container?.opacity ?? defaultOpacity) <= 0.2; | |
if (isChatBgTransparent) { | |
const bgType = generalBackground?.type ?? defaultBackgroundType; | |
documentStyle.setProperty(cssVariableNames.chat.checkbox.bgRgb, bgType === BackgroundType.IMAGE ? 'rgba(255, 255, 255, 0.75)' : hexToRgb((bgType === BackgroundType.COLOR ? generalBackground?.content : '#ffffff') ?? '#ffffff').join(', ')); | |
if (bgType === BackgroundType.IMAGE) { | |
documentStyle.setProperty(cssVariableNames.chat.checkbox.alphaRatio, '3'); | |
} else { | |
documentStyle.setProperty(cssVariableNames.chat.checkbox.alphaRatio, generalBackground?.content && isLight(generalBackground?.content) ? '1' : '2'); | |
} | |
} else { | |
documentStyle.setProperty(cssVariableNames.chat.checkbox.bgRgb, hexToRgb(chatContainerBgColor).concat(container?.opacity ?? 1).join(', ')); | |
documentStyle.setProperty(cssVariableNames.chat.checkbox.alphaRatio, isLight(chatContainerBgColor) ? '1' : '2'); | |
} | |
}; | |
const setGeneralBackground = (background, documentStyle) => { | |
documentStyle.setProperty(cssVariableNames.general.bgImage, null); | |
documentStyle.setProperty(cssVariableNames.general.bgColor, null); | |
documentStyle.setProperty((background?.type ?? defaultBackgroundType) === BackgroundType.IMAGE ? cssVariableNames.general.bgImage : cssVariableNames.general.bgColor, parseBackgroundValue({ | |
type: background?.type ?? defaultBackgroundType, | |
content: background?.content ?? defaultBackgroundColor | |
})); | |
}; | |
const parseBackgroundValue = ({ | |
type, | |
content | |
} = {}) => { | |
switch (type) { | |
case BackgroundType.NONE: | |
return 'transparent'; | |
case undefined: | |
case BackgroundType.COLOR: | |
return content ?? defaultBackgroundColor; | |
case BackgroundType.IMAGE: | |
return `url(${content})`; | |
} | |
}; | |
const setBorderRadius = (border, documentStyle, variableName) => { | |
switch (border?.roundeness ?? defaultRoundness) { | |
case 'none': | |
{ | |
documentStyle.setProperty(variableName, '0'); | |
break; | |
} | |
case 'medium': | |
{ | |
documentStyle.setProperty(variableName, '6px'); | |
break; | |
} | |
case 'large': | |
{ | |
documentStyle.setProperty(variableName, '20px'); | |
break; | |
} | |
case 'custom': | |
{ | |
documentStyle.setProperty(variableName, `${border.customRoundeness ?? 6}px`); | |
break; | |
} | |
} | |
}; | |
// Props taken from https://tailwindcss.com/docs/box-shadow | |
const setShadow = (shadow, documentStyle, variableName) => { | |
if (shadow === undefined) { | |
documentStyle.setProperty(variableName, '0 0 #0000'); | |
return; | |
} | |
switch (shadow) { | |
case 'none': | |
documentStyle.setProperty(variableName, '0 0 #0000'); | |
break; | |
case 'sm': | |
documentStyle.setProperty(variableName, '0 1px 2px 0 rgb(0 0 0 / 0.05)'); | |
break; | |
case 'md': | |
documentStyle.setProperty(variableName, '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'); | |
break; | |
case 'lg': | |
documentStyle.setProperty(variableName, '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)'); | |
break; | |
case 'xl': | |
documentStyle.setProperty(variableName, '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)'); | |
break; | |
case '2xl': | |
documentStyle.setProperty(variableName, '0 25px 50px -12px rgb(0 0 0 / 0.25)'); | |
break; | |
} | |
}; | |
var css_248z = "#lite-badge{background-color:#fff!important;border-radius:4px!important;border-width:1px!important;bottom:20px!important;color:#111827!important;display:flex!important;font-size:14px!important;font-weight:600!important;gap:8px!important;left:auto!important;line-height:20px!important;opacity:1!important;padding:4px 8px!important;position:absolute!important;right:auto!important;text-decoration:none!important;top:auto!important;transition:background-color .2s ease-in-out!important;visibility:visible!important;z-index:50!important}#lite-badge:hover{background-color:#f7f8ff!important}"; | |
const googleFontCdnBaseUrl = 'https://fonts.bunny.net/css2'; | |
const elementId = 'typebot-font'; | |
const injectFont = font => { | |
const existingFont = document.getElementById(elementId); | |
if (typeof font === 'string' || font.type === 'Google') { | |
const fontFamily = (typeof font === 'string' ? font : font.family) ?? defaultFontFamily; | |
if (existingFont?.getAttribute('href')?.includes(fontFamily)) return; | |
existingFont?.remove(); | |
const fontElement = document.createElement('link'); | |
fontElement.href = `${googleFontCdnBaseUrl}?family=${fontFamily}:ital,wght@0,300;0,400;0,600;1,300;1,400;1,600&display=swap`; | |
fontElement.rel = 'stylesheet'; | |
fontElement.id = elementId; | |
document.head.appendChild(fontElement); | |
return; | |
} | |
if (font.type === 'Custom') { | |
if (isNotEmpty(font.css)) { | |
if (existingFont?.innerHTML === font.css) return; | |
existingFont?.remove(); | |
const style = document.createElement('style'); | |
style.innerHTML = font.css; | |
style.id = elementId; | |
document.head.appendChild(style); | |
} | |
if (isNotEmpty(font.url)) { | |
if (existingFont?.getAttribute('href') === font.url) return; | |
existingFont?.remove(); | |
const fontElement = document.createElement('link'); | |
fontElement.href = font.url; | |
fontElement.rel = 'stylesheet'; | |
fontElement.id = elementId; | |
document.head.appendChild(fontElement); | |
} | |
} | |
}; | |
const _tmpl$$4 = /*#__PURE__*/template(`<div class="typebot-progress-bar-container"><div class="typebot-progress-bar">`); | |
const ProgressBar = props => (() => { | |
const _el$ = _tmpl$$4(), | |
_el$2 = _el$.firstChild; | |
createRenderEffect(() => `${props.value}%` != null ? _el$2.style.setProperty("width", `${props.value}%`) : _el$2.style.removeProperty("width")); | |
return _el$; | |
})(); | |
const _tmpl$$3 = /*#__PURE__*/template(`<style>`), | |
_tmpl$2$2 = /*#__PURE__*/template(`<div>`); | |
const Bot = props => { | |
const [initialChatReply, setInitialChatReply] = createSignal(); | |
const [customCss, setCustomCss] = createSignal(''); | |
const [isInitialized, setIsInitialized] = createSignal(false); | |
const [error, setError] = createSignal(); | |
const initializeBot = async () => { | |
if (props.font) injectFont(props.font); | |
setIsInitialized(true); | |
const urlParams = new URLSearchParams(location.search); | |
props.onInit?.(); | |
const prefilledVariables = {}; | |
urlParams.forEach((value, key) => { | |
prefilledVariables[key] = value; | |
}); | |
const typebotIdFromProps = typeof props.typebot === 'string' ? props.typebot : undefined; | |
const isPreview = typeof props.typebot !== 'string' || (props.isPreview ?? false); | |
const resultIdInStorage = getExistingResultIdFromStorage(typebotIdFromProps); | |
const { | |
data, | |
error | |
} = await startChatQuery({ | |
stripeRedirectStatus: urlParams.get('redirect_status') ?? undefined, | |
typebot: props.typebot, | |
apiHost: props.apiHost, | |
isPreview, | |
resultId: isNotEmpty(props.resultId) ? props.resultId : resultIdInStorage, | |
prefilledVariables: { | |
...prefilledVariables, | |
...props.prefilledVariables | |
}, | |
startFrom: props.startFrom, | |
sessionId: props.sessionId | |
}); | |
if (error instanceof HTTPError) { | |
if (isPreview) { | |
return setError(new Error(`An error occurred while loading the bot.`, { | |
cause: { | |
status: error.response.status, | |
body: await error.response.json() | |
} | |
})); | |
} | |
if (error.response.status === 400 || error.response.status === 403) return setError(new Error('This bot is now closed.')); | |
if (error.response.status === 404) return setError(new Error("The bot you're looking for doesn't exist.")); | |
return setError(new Error(`Error! Couldn't initiate the chat. (${error.response.statusText})`)); | |
} | |
if (error instanceof CorsError) { | |
return setError(new Error(error.message)); | |
} | |
if (!data) { | |
if (error) { | |
console.error(error); | |
if (isPreview) { | |
return setError(new Error(`Error! Could not reach server. Check your connection.`, { | |
cause: error | |
})); | |
} | |
} | |
return setError(new Error('Error! Could not reach server. Check your connection.')); | |
} | |
if (data.resultId && typebotIdFromProps && (data.typebot.settings.general?.rememberUser?.isEnabled ?? defaultSettings.general.rememberUser.isEnabled)) { | |
if (resultIdInStorage && resultIdInStorage !== data.resultId) wipeExistingChatStateInStorage(data.typebot.id); | |
const storage = data.typebot.settings.general?.rememberUser?.storage ?? defaultSettings.general.rememberUser.storage; | |
setResultInStorage(storage)(typebotIdFromProps, data.resultId); | |
const initialChatInStorage = getInitialChatReplyFromStorage(data.typebot.id); | |
if (initialChatInStorage) { | |
setInitialChatReply(initialChatInStorage); | |
} else { | |
setInitialChatReply(data); | |
setInitialChatReplyInStorage(data, { | |
typebotId: data.typebot.id, | |
storage | |
}); | |
} | |
props.onChatStatePersisted?.(true); | |
} else { | |
wipeExistingChatStateInStorage(data.typebot.id); | |
setInitialChatReply(data); | |
if (data.input?.id && props.onNewInputBlock) props.onNewInputBlock(data.input); | |
if (data.logs) props.onNewLogs?.(data.logs); | |
props.onChatStatePersisted?.(false); | |
} | |
setCustomCss(data.typebot.theme.customCss ?? ''); | |
}; | |
createEffect(() => { | |
if (isNotDefined(props.typebot) || isInitialized()) return; | |
initializeBot().then(); | |
}); | |
createEffect(() => { | |
if (isNotDefined(props.typebot) || typeof props.typebot === 'string') return; | |
setCustomCss(props.typebot.theme.customCss ?? ''); | |
if (props.typebot.theme.general?.progressBar?.isEnabled && initialChatReply() && !initialChatReply()?.typebot.theme.general?.progressBar?.isEnabled) { | |
setIsInitialized(false); | |
initializeBot().then(); | |
} | |
}); | |
onCleanup(() => { | |
setIsInitialized(false); | |
}); | |
return [(() => { | |
const _el$ = _tmpl$$3(); | |
insert(_el$, customCss); | |
return _el$; | |
})(), (() => { | |
const _el$2 = _tmpl$$3(); | |
insert(_el$2, css_248z); | |
return _el$2; | |
})(), createComponent(Show, { | |
get when() { | |
return error(); | |
}, | |
keyed: true, | |
children: error => createComponent(ErrorMessage, { | |
error: error | |
}) | |
}), createComponent(Show, { | |
get when() { | |
return initialChatReply(); | |
}, | |
keyed: true, | |
children: initialChatReply => createComponent(BotContent, { | |
get ["class"]() { | |
return props.class; | |
}, | |
get initialChatReply() { | |
return { | |
...initialChatReply, | |
typebot: { | |
...initialChatReply.typebot, | |
settings: typeof props.typebot === 'string' ? initialChatReply.typebot?.settings : props.typebot?.settings, | |
theme: typeof props.typebot === 'string' ? initialChatReply.typebot?.theme : props.typebot?.theme | |
} | |
}; | |
}, | |
get context() { | |
return { | |
apiHost: props.apiHost, | |
isPreview: typeof props.typebot !== 'string' || (props.isPreview ?? false), | |
resultId: initialChatReply.resultId, | |
sessionId: initialChatReply.sessionId, | |
typebot: initialChatReply.typebot, | |
storage: initialChatReply.typebot.settings.general?.rememberUser?.isEnabled && !(typeof props.typebot !== 'string' || (props.isPreview ?? false)) ? initialChatReply.typebot.settings.general?.rememberUser?.storage ?? defaultSettings.general.rememberUser.storage : undefined | |
}; | |
}, | |
get progressBarRef() { | |
return props.progressBarRef; | |
}, | |
get onNewInputBlock() { | |
return props.onNewInputBlock; | |
}, | |
get onNewLogs() { | |
return props.onNewLogs; | |
}, | |
get onAnswer() { | |
return props.onAnswer; | |
}, | |
get onEnd() { | |
return props.onEnd; | |
} | |
}) | |
})]; | |
}; | |
const BotContent = props => { | |
const [progressValue, setProgressValue] = persist(createSignal(props.initialChatReply.progress), { | |
storage: props.context.storage, | |
key: `typebot-${props.context.typebot.id}-progressValue` | |
}); | |
let botContainer; | |
const resizeObserver = new ResizeObserver(entries => { | |
return setIsMobile(entries[0].target.clientWidth < 400); | |
}); | |
onMount(() => { | |
if (!botContainer) return; | |
resizeObserver.observe(botContainer); | |
setBotContainerHeight(`${botContainer.clientHeight}px`); | |
}); | |
createEffect(() => { | |
injectFont(props.initialChatReply.typebot.theme.general?.font ?? { | |
type: defaultFontType, | |
family: defaultFontFamily | |
}); | |
if (!botContainer) return; | |
setCssVariablesValue(props.initialChatReply.typebot.theme, botContainer, props.context.isPreview); | |
}); | |
onCleanup(() => { | |
if (!botContainer) return; | |
resizeObserver.unobserve(botContainer); | |
}); | |
return (() => { | |
const _el$3 = _tmpl$2$2(); | |
const _ref$ = botContainer; | |
typeof _ref$ === "function" ? use(_ref$, _el$3) : botContainer = _el$3; | |
insert(_el$3, createComponent(Show, { | |
get when() { | |
return isDefined(progressValue()) && props.initialChatReply.typebot.theme.general?.progressBar?.isEnabled; | |
}, | |
get children() { | |
return createComponent(Show, { | |
get when() { | |
return props.progressBarRef && (props.initialChatReply.typebot.theme.general?.progressBar?.position ?? defaultProgressBarPosition) === 'fixed'; | |
}, | |
get fallback() { | |
return createComponent(ProgressBar, { | |
get value() { | |
return progressValue(); | |
} | |
}); | |
}, | |
get children() { | |
return createComponent(Portal, { | |
get mount() { | |
return props.progressBarRef; | |
}, | |
get children() { | |
return createComponent(ProgressBar, { | |
get value() { | |
return progressValue(); | |
} | |
}); | |
} | |
}); | |
} | |
}); | |
} | |
}), null); | |
insert(_el$3, createComponent(ConversationContainer, { | |
get context() { | |
return props.context; | |
}, | |
get initialChatReply() { | |
return props.initialChatReply; | |
}, | |
get onNewInputBlock() { | |
return props.onNewInputBlock; | |
}, | |
get onAnswer() { | |
return props.onAnswer; | |
}, | |
get onEnd() { | |
return props.onEnd; | |
}, | |
get onNewLogs() { | |
return props.onNewLogs; | |
}, | |
onProgressUpdate: setProgressValue | |
}), null); | |
insert(_el$3, createComponent(Show, { | |
get when() { | |
return props.initialChatReply.typebot.settings.general?.isBrandingEnabled; | |
}, | |
get children() { | |
return createComponent(LiteBadge, { | |
botContainer: botContainer | |
}); | |
} | |
}), null); | |
insert(_el$3, createComponent(Toaster, { | |
toaster: toaster, | |
children: toast$1 => createComponent(toast.Root, { | |
get children() { | |
return [createComponent(toast.Title, { | |
get children() { | |
return toast$1().title; | |
} | |
}), createComponent(toast.Description, { | |
get children() { | |
return toast$1().description; | |
} | |
}), createComponent(toast.CloseTrigger, { | |
"class": "absolute right-2 top-2", | |
get children() { | |
return createComponent(CloseIcon, { | |
"class": "w-4 h-4" | |
}); | |
} | |
})]; | |
} | |
}) | |
}), null); | |
createRenderEffect(() => className(_el$3, clsx$1('relative flex w-full h-full text-base overflow-hidden flex-col justify-center items-center typebot-container', props.class))); | |
return _el$3; | |
})(); | |
}; | |
const _tmpl$$2 = /*#__PURE__*/template(`<style>`), | |
_tmpl$2$1 = /*#__PURE__*/template(`<div>`), | |
_tmpl$3 = /*#__PURE__*/template(`<div part="bot">`); | |
const Bubble = props => { | |
const [bubbleProps, botProps] = splitProps(props, ['onOpen', 'onClose', 'previewMessage', 'onPreviewMessageClick', 'theme', 'autoShowDelay']); | |
const [isMounted, setIsMounted] = createSignal(true); | |
const [prefilledVariables, setPrefilledVariables] = createSignal(botProps.prefilledVariables); | |
const [isPreviewMessageDisplayed, setIsPreviewMessageDisplayed] = createSignal(false); | |
const [previewMessage, setPreviewMessage] = createSignal({ | |
message: bubbleProps.previewMessage?.message ?? '', | |
avatarUrl: bubbleProps.previewMessage?.avatarUrl | |
}); | |
const [isBotOpened, setIsBotOpened] = createSignal(false); | |
const [isBotStarted, setIsBotStarted] = createSignal(false); | |
const [buttonSize, setButtonSize] = createSignal(parseButtonSize(bubbleProps.theme?.button?.size ?? 'medium')); | |
createEffect(() => { | |
setButtonSize(parseButtonSize(bubbleProps.theme?.button?.size ?? 'medium')); | |
}); | |
let progressBarContainerRef; | |
onMount(() => { | |
window.addEventListener('message', processIncomingEvent); | |
const autoShowDelay = bubbleProps.autoShowDelay; | |
const previewMessageAutoShowDelay = bubbleProps.previewMessage?.autoShowDelay; | |
if (getBotOpenedStateFromStorage() || getPaymentInProgressInStorage()) openBot(); | |
if (isDefined(autoShowDelay)) { | |
setTimeout(() => { | |
openBot(); | |
}, autoShowDelay); | |
} | |
if (isDefined(previewMessageAutoShowDelay)) { | |
setTimeout(() => { | |
showMessage(); | |
}, previewMessageAutoShowDelay); | |
} | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
createEffect(() => { | |
if (!props.prefilledVariables) return; | |
setPrefilledVariables(existingPrefilledVariables => ({ | |
...existingPrefilledVariables, | |
...props.prefilledVariables | |
})); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'open') openBot(); | |
if (data.command === 'close') closeBot(); | |
if (data.command === 'toggle') toggleBot(); | |
if (data.command === 'showPreviewMessage') showMessage(data.message); | |
if (data.command === 'hidePreviewMessage') hideMessage(); | |
if (data.command === 'setPrefilledVariables') setPrefilledVariables(existingPrefilledVariables => ({ | |
...existingPrefilledVariables, | |
...data.variables | |
})); | |
if (data.command === 'unmount') unmount(); | |
}; | |
const openBot = () => { | |
if (!isBotStarted()) setIsBotStarted(true); | |
hideMessage(); | |
setIsBotOpened(true); | |
if (isBotOpened()) bubbleProps.onOpen?.(); | |
}; | |
const closeBot = () => { | |
setIsBotOpened(false); | |
removeBotOpenedStateInStorage(); | |
if (isBotOpened()) bubbleProps.onClose?.(); | |
}; | |
const toggleBot = () => { | |
isBotOpened() ? closeBot() : openBot(); | |
}; | |
const handlePreviewMessageClick = () => { | |
bubbleProps.onPreviewMessageClick?.(); | |
openBot(); | |
}; | |
const showMessage = previewMessage => { | |
if (previewMessage) setPreviewMessage(previewMessage); | |
if (isBotOpened()) return; | |
setIsPreviewMessageDisplayed(true); | |
}; | |
const hideMessage = () => { | |
setIsPreviewMessageDisplayed(false); | |
}; | |
const unmount = () => { | |
if (isBotOpened()) { | |
closeBot(); | |
setTimeout(() => { | |
setIsMounted(false); | |
}, 200); | |
} else setIsMounted(false); | |
}; | |
const handleOnChatStatePersisted = isPersisted => { | |
botProps.onChatStatePersisted?.(isPersisted); | |
if (isPersisted) setBotOpenedStateInStorage(); | |
}; | |
return createComponent(Show, { | |
get when() { | |
return isMounted(); | |
}, | |
get children() { | |
return createComponent(EnvironmentProvider, { | |
get value() { | |
return document.querySelector('typebot-bubble')?.shadowRoot; | |
}, | |
get children() { | |
return [(() => { | |
const _el$ = _tmpl$$2(); | |
insert(_el$, css_248z$1); | |
return _el$; | |
})(), createComponent(Show, { | |
get when() { | |
return isPreviewMessageDisplayed(); | |
}, | |
get children() { | |
return createComponent(PreviewMessage, mergeProps$2(previewMessage, { | |
get placement() { | |
return bubbleProps.theme?.placement; | |
}, | |
get previewMessageTheme() { | |
return bubbleProps.theme?.previewMessage; | |
}, | |
get buttonSize() { | |
return buttonSize(); | |
}, | |
onClick: handlePreviewMessageClick, | |
onCloseClick: hideMessage | |
})); | |
} | |
}), createComponent(BubbleButton, mergeProps$2(() => bubbleProps.theme?.button, { | |
get placement() { | |
return bubbleProps.theme?.placement; | |
}, | |
toggleBot: toggleBot, | |
get isBotOpened() { | |
return isBotOpened(); | |
}, | |
get buttonSize() { | |
return buttonSize(); | |
} | |
})), (() => { | |
const _el$2 = _tmpl$2$1(); | |
const _ref$ = progressBarContainerRef; | |
typeof _ref$ === "function" ? use(_ref$, _el$2) : progressBarContainerRef = _el$2; | |
return _el$2; | |
})(), (() => { | |
const _el$3 = _tmpl$3(); | |
_el$3.style.setProperty("transition", "transform 200ms cubic-bezier(0, 1.2, 1, 1), opacity 150ms ease-out"); | |
_el$3.style.setProperty("box-shadow", "rgb(0 0 0 / 16%) 0px 5px 40px"); | |
_el$3.style.setProperty("z-index", "42424242"); | |
insert(_el$3, createComponent(Show, { | |
get when() { | |
return isBotStarted(); | |
}, | |
get children() { | |
return createComponent(Bot, mergeProps$2(botProps, { | |
onChatStatePersisted: handleOnChatStatePersisted, | |
get prefilledVariables() { | |
return prefilledVariables(); | |
}, | |
"class": "rounded-lg", | |
progressBarRef: progressBarContainerRef | |
})); | |
} | |
})); | |
createRenderEffect(_p$ => { | |
const _v$ = `calc(100% - ${buttonSize()} - 32px)`, | |
_v$2 = props.theme?.chatWindow?.maxHeight ?? '704px', | |
_v$3 = props.theme?.chatWindow?.maxWidth ?? '400px', | |
_v$4 = props.theme?.placement === 'left' ? 'bottom left' : 'bottom right', | |
_v$5 = isBotOpened() ? 'scale3d(1, 1, 1)' : 'scale3d(0, 0, 1)', | |
_v$6 = bubbleProps.theme?.chatWindow?.backgroundColor, | |
_v$7 = `calc(${buttonSize()} + 32px)`, | |
_v$8 = 'fixed rounded-lg w-full' + (isBotOpened() ? ' opacity-1' : ' opacity-0 pointer-events-none') + (props.theme?.placement === 'left' ? ' left-5' : ' sm:right-5 right-0'); | |
_v$ !== _p$._v$ && ((_p$._v$ = _v$) != null ? _el$3.style.setProperty("height", _v$) : _el$3.style.removeProperty("height")); | |
_v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$3.style.setProperty("max-height", _v$2) : _el$3.style.removeProperty("max-height")); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$3.style.setProperty("max-width", _v$3) : _el$3.style.removeProperty("max-width")); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$3.style.setProperty("transform-origin", _v$4) : _el$3.style.removeProperty("transform-origin")); | |
_v$5 !== _p$._v$5 && ((_p$._v$5 = _v$5) != null ? _el$3.style.setProperty("transform", _v$5) : _el$3.style.removeProperty("transform")); | |
_v$6 !== _p$._v$6 && ((_p$._v$6 = _v$6) != null ? _el$3.style.setProperty("background-color", _v$6) : _el$3.style.removeProperty("background-color")); | |
_v$7 !== _p$._v$7 && ((_p$._v$7 = _v$7) != null ? _el$3.style.setProperty("bottom", _v$7) : _el$3.style.removeProperty("bottom")); | |
_v$8 !== _p$._v$8 && className(_el$3, _p$._v$8 = _v$8); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined, | |
_v$5: undefined, | |
_v$6: undefined, | |
_v$7: undefined, | |
_v$8: undefined | |
}); | |
return _el$3; | |
})()]; | |
} | |
}); | |
} | |
}); | |
}; | |
const parseButtonSize = size => size === 'medium' ? '48px' : size === 'large' ? '64px' : size ? size : '48px'; | |
const _tmpl$$1 = /*#__PURE__*/template(`<style>`), | |
_tmpl$2 = /*#__PURE__*/template(`<div class="relative" aria-labelledby="modal-title" role="dialog" aria-modal="true"><style></style><div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity animate-fade-in" part="overlay"></div><div class="fixed inset-0 z-10 overflow-y-auto"><div class="flex min-h-full items-center justify-center p-4 text-center sm:p-0"><div>`); | |
const Popup = props => { | |
const [popupProps, botProps] = splitProps(props, ['onOpen', 'onClose', 'autoShowDelay', 'theme', 'isOpen', 'defaultOpen']); | |
const [prefilledVariables, setPrefilledVariables] = createSignal(botProps.prefilledVariables); | |
const [isBotOpened, setIsBotOpened] = createSignal(popupProps.isOpen ?? false); | |
onMount(() => { | |
if (popupProps.defaultOpen || getPaymentInProgressInStorage() || getBotOpenedStateFromStorage()) openBot(); | |
window.addEventListener('message', processIncomingEvent); | |
const autoShowDelay = popupProps.autoShowDelay; | |
if (isDefined(autoShowDelay)) { | |
setTimeout(() => { | |
openBot(); | |
}, autoShowDelay); | |
} | |
}); | |
onCleanup(() => { | |
window.removeEventListener('message', processIncomingEvent); | |
}); | |
createEffect(() => { | |
if (isNotDefined(props.isOpen) || props.isOpen === isBotOpened()) return; | |
toggleBot(); | |
}); | |
createEffect(() => { | |
if (!props.prefilledVariables) return; | |
setPrefilledVariables(existingPrefilledVariables => ({ | |
...existingPrefilledVariables, | |
...props.prefilledVariables | |
})); | |
}); | |
const stopPropagation = event => { | |
event.stopPropagation(); | |
}; | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
if (data.command === 'open') openBot(); | |
if (data.command === 'close') closeBot(); | |
if (data.command === 'toggle') toggleBot(); | |
if (data.command === 'setPrefilledVariables') setPrefilledVariables(existingPrefilledVariables => ({ | |
...existingPrefilledVariables, | |
...data.variables | |
})); | |
}; | |
const openBot = () => { | |
setIsBotOpened(true); | |
popupProps.onOpen?.(); | |
document.body.style.setProperty('overflow', 'hidden', 'important'); | |
document.addEventListener('pointerdown', closeBot); | |
}; | |
const closeBot = () => { | |
setIsBotOpened(false); | |
popupProps.onClose?.(); | |
document.body.style.overflow = 'auto'; | |
document.removeEventListener('pointerdown', closeBot); | |
removeBotOpenedStateInStorage(); | |
}; | |
const toggleBot = () => { | |
isBotOpened() ? closeBot() : openBot(); | |
}; | |
const handleOnChatStatePersisted = isPersisted => { | |
botProps.onChatStatePersisted?.(isPersisted); | |
if (isPersisted) setBotOpenedStateInStorage(); | |
}; | |
return createComponent(Show, { | |
get when() { | |
return isBotOpened(); | |
}, | |
get children() { | |
return createComponent(EnvironmentProvider, { | |
get value() { | |
return document.querySelector('typebot-popup')?.shadowRoot; | |
}, | |
get children() { | |
return [(() => { | |
const _el$ = _tmpl$$1(); | |
insert(_el$, css_248z$1); | |
return _el$; | |
})(), (() => { | |
const _el$2 = _tmpl$2(), | |
_el$3 = _el$2.firstChild, | |
_el$4 = _el$3.nextSibling, | |
_el$5 = _el$4.nextSibling, | |
_el$6 = _el$5.firstChild, | |
_el$7 = _el$6.firstChild; | |
insert(_el$3, css_248z$1); | |
_el$7.addEventListener("pointerdown", stopPropagation); | |
insert(_el$7, createComponent(Bot, mergeProps$2(botProps, { | |
get prefilledVariables() { | |
return prefilledVariables(); | |
}, | |
onChatStatePersisted: handleOnChatStatePersisted | |
}))); | |
createRenderEffect(_p$ => { | |
const _v$ = props.theme?.zIndex ?? 42424242, | |
_v$2 = 'relative h-[80vh] transform overflow-hidden rounded-lg text-left transition-all sm:my-8 w-full max-w-lg' + (props.theme?.backgroundColor ? ' shadow-xl' : ''), | |
_v$3 = props.theme?.backgroundColor ?? 'transparent', | |
_v$4 = props.theme?.width ?? '512px'; | |
_v$ !== _p$._v$ && ((_p$._v$ = _v$) != null ? _el$2.style.setProperty("z-index", _v$) : _el$2.style.removeProperty("z-index")); | |
_v$2 !== _p$._v$2 && className(_el$7, _p$._v$2 = _v$2); | |
_v$3 !== _p$._v$3 && ((_p$._v$3 = _v$3) != null ? _el$7.style.setProperty("background-color", _v$3) : _el$7.style.removeProperty("background-color")); | |
_v$4 !== _p$._v$4 && ((_p$._v$4 = _v$4) != null ? _el$7.style.setProperty("max-width", _v$4) : _el$7.style.removeProperty("max-width")); | |
return _p$; | |
}, { | |
_v$: undefined, | |
_v$2: undefined, | |
_v$3: undefined, | |
_v$4: undefined | |
}); | |
return _el$2; | |
})()]; | |
} | |
}); | |
} | |
}); | |
}; | |
const _tmpl$ = /*#__PURE__*/template(`<style> | |
:host { | |
display: block; | |
width: 100%; | |
height: 100%; | |
overflow-y: hidden; | |
} | |
`); | |
const Standard = (props, { | |
element | |
}) => { | |
const [isBotDisplayed, setIsBotDisplayed] = createSignal(false); | |
const launchBot = () => { | |
setIsBotDisplayed(true); | |
}; | |
const botLauncherObserver = new IntersectionObserver(intersections => { | |
if (intersections.some(intersection => intersection.isIntersecting)) launchBot(); | |
}); | |
onMount(() => { | |
window.addEventListener('message', processIncomingEvent); | |
botLauncherObserver.observe(element); | |
}); | |
const processIncomingEvent = event => { | |
const { | |
data | |
} = event; | |
if (!data.isFromTypebot) return; | |
}; | |
onCleanup(() => { | |
botLauncherObserver.disconnect(); | |
}); | |
return createComponent(EnvironmentProvider, { | |
get value() { | |
return document.querySelector('typebot-standard')?.shadowRoot; | |
}, | |
get children() { | |
return [(() => { | |
const _el$ = _tmpl$(), | |
_el$2 = _el$.firstChild; | |
insert(_el$, css_248z$1, _el$2); | |
return _el$; | |
})(), createComponent(Show, { | |
get when() { | |
return isBotDisplayed(); | |
}, | |
get children() { | |
return createComponent(Bot, props); | |
} | |
})]; | |
} | |
}); | |
}; | |
const registerWebComponents = () => { | |
if (typeof window === 'undefined') return; | |
// @ts-expect-error element incorect type | |
customElement('typebot-standard', defaultBotProps, Standard); | |
customElement('typebot-bubble', defaultBubbleProps, Bubble); | |
customElement('typebot-popup', defaultPopupProps, Popup); | |
}; | |
const close = () => { | |
const message = { | |
isFromTypebot: true, | |
command: 'close' | |
}; | |
window.postMessage(message); | |
}; | |
const hidePreviewMessage = () => { | |
const message = { | |
isFromTypebot: true, | |
command: 'hidePreviewMessage' | |
}; | |
window.postMessage(message); | |
}; | |
const open = () => { | |
const message = { | |
isFromTypebot: true, | |
command: 'open' | |
}; | |
window.postMessage(message); | |
}; | |
const setPrefilledVariables = variables => { | |
const message = { | |
isFromTypebot: true, | |
command: 'setPrefilledVariables', | |
variables | |
}; | |
window.postMessage(message); | |
}; | |
const showPreviewMessage = proactiveMessage => { | |
const message = { | |
isFromTypebot: true, | |
command: 'showPreviewMessage', | |
message: proactiveMessage | |
}; | |
window.postMessage(message); | |
}; | |
const toggle = () => { | |
const message = { | |
isFromTypebot: true, | |
command: 'toggle' | |
}; | |
window.postMessage(message); | |
}; | |
const setInputValue = value => { | |
const message = { | |
isFromTypebot: true, | |
command: 'setInputValue', | |
value | |
}; | |
window.postMessage(message); | |
}; | |
const unmount = () => { | |
const message = { | |
isFromTypebot: true, | |
command: 'unmount' | |
}; | |
window.postMessage(message); | |
}; | |
const initStandard = props => { | |
const standardElement = props.id ? document.getElementById(props.id) : document.querySelector('typebot-standard'); | |
if (!standardElement) throw new Error('<typebot-standard> element not found.'); | |
Object.assign(standardElement, props); | |
}; | |
const initPopup = props => { | |
const popupElement = document.createElement('typebot-popup'); | |
Object.assign(popupElement, props); | |
document.body.prepend(popupElement); | |
}; | |
const initBubble = props => { | |
const bubbleElement = document.createElement('typebot-bubble'); | |
Object.assign(bubbleElement, props); | |
document.body.prepend(bubbleElement); | |
}; | |
const parseTypebot = () => ({ | |
initStandard, | |
initPopup, | |
initBubble, | |
close, | |
hidePreviewMessage, | |
open, | |
setPrefilledVariables, | |
showPreviewMessage, | |
toggle, | |
setInputValue, | |
unmount | |
}); | |
const injectTypebotInWindow = typebot => { | |
if (typeof window === 'undefined') return; | |
window.Typebot = { | |
...typebot | |
}; | |
}; | |
registerWebComponents(); | |
const typebot = parseTypebot(); | |
injectTypebotInWindow(typebot); | |
export { typebot as default }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment