PageSourceSearch

https://toeta.objektiiv.ee/assets/@symfony/ux-live-component/live_controller-ZPUcqp6.js

js objektiiv.ee collected 2026-09-24 20:44:24 UTC 122,885 bytes, 3,190 lines download raw bytes

1import { Controller } from '@hotwired/stimulus';
2
3class BackendRequest {
4    constructor(promise, actions, updateModels) {
5        this.isResolved = false;
6        this.promise = promise;
7        this.promise.then((response) => {
8            this.isResolved = true;
9            return response;
10        });
11        this.actions = actions;
12        this.updatedModels = updateModels;
13    }
14    containsOneOfActions(targetedActions) {
15        return this.actions.filter((action) => targetedActions.includes(action)).length > 0;
16    }
17    areAnyModelsUpdated(targetedModels) {
18        return this.updatedModels.filter((model) => targetedModels.includes(model)).length > 0;
19    }
20}
21
22class RequestBuilder {
23    constructor(url, method = 'post') {
24        this.url = url;
25        this.method = method;
26    }
27    buildRequest(props, actions, updated, children, updatedPropsFromParent, files) {
28        const splitUrl = this.url.split('?');
29        let [url] = splitUrl;
30        const [, queryString] = splitUrl;
31        const params = new URLSearchParams(queryString || '');
32        const fetchOptions = {};
33        fetchOptions.headers = {
34            Accept: 'application/vnd.live-component+html',
35            'X-Requested-With': 'XMLHttpRequest',
36        };
37        const totalFiles = Object.entries(files).reduce((total, current) => total + current.length, 0);
38        const hasFingerprints = Object.keys(children).length > 0;
39        if (actions.length === 0 &&
40            totalFiles === 0 &&
41            this.method === 'get' &&
42            this.willDataFitInUrl(JSON.stringify(props), JSON.stringify(updated), params, JSON.stringify(children), JSON.stringify(updatedPropsFromParent))) {
43            params.set('props', JSON.stringify(props));
44            params.set('updated', JSON.stringify(updated));
45            if (Object.keys(updatedPropsFromParent).length > 0) {
46                params.set('propsFromParent', JSON.stringify(updatedPropsFromParent));
47            }
48            if (hasFingerprints) {
49                params.set('children', JSON.stringify(children));
50            }
51            fetchOptions.method = 'GET';
52        }
53        else {
54            fetchOptions.method = 'POST';
55            const requestData = { props, updated };
56            if (Object.keys(updatedPropsFromParent).length > 0) {
57                requestData.propsFromParent = updatedPropsFromParent;
58            }
59            if (hasFingerprints) {
60                requestData.children = children;
61            }
62            if (actions.length > 0) {
63                if (actions.length === 1) {
64                    requestData.args = actions[0].args;
65                    url += `/${encodeURIComponent(actions[0].name)}`;
66                }
67                else {
68                    url += '/_batch';
69                    requestData.actions = actions;
70                }
71            }
72            const formData = new FormData();
73            formData.append('data', JSON.stringify(requestData));
74            for (const [key, value] of Object.entries(files)) {
75                const length = value.length;
76                for (let i = 0; i < length; ++i) {
77                    formData.append(key, value[i]);
78                }
79            }
80            fetchOptions.body = formData;
81        }
82        const paramsString = params.toString();
83        return {
84            url: `${url}${paramsString.length > 0 ? `?${paramsString}` : ''}`,
85            fetchOptions,
86        };
87    }
88    willDataFitInUrl(propsJson, updatedJson, params, childrenJson, propsFromParentJson) {
89        const urlEncodedJsonData = new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString();
90        return (urlEncodedJsonData + params.toString()).length < 1500;
91    }
92}
93
94class Backend {
95    constructor(url, method = 'post') {
96        this.requestBuilder = new RequestBuilder(url, method);
97    }
98    makeRequest(props, actions, updated, children, updatedPropsFromParent, files) {
99        const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files);
100        return new BackendRequest(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated));
101    }
102}
103
104class BackendResponse {
105    constructor(response) {
106        this.response = response;
107    }
108    async getBody() {
109        if (!this.body) {
110            this.body = await this.response.text();
111        }
112        return this.body;
113    }
114}
115
116function getElementAsTagText(element) {
117    return element.innerHTML
118        ? element.outerHTML.slice(0, element.outerHTML.indexOf(element.innerHTML))
119        : element.outerHTML;
120}
121
122let componentMapByElement = new WeakMap();
123let componentMapByComponent = new Map();
124const registerComponent = (component) => {
125    componentMapByElement.set(component.element, component);
126    componentMapByComponent.set(component, component.name);
127};
128const unregisterComponent = (component) => {
129    componentMapByElement.delete(component.element);
130    componentMapByComponent.delete(component);
131};
132const getComponent = (element) => new Promise((resolve, reject) => {
133    let count = 0;
134    const maxCount = 10;
135    const interval = setInterval(() => {
136        const component = componentMapByElement.get(element);
137        if (component) {
138            clearInterval(interval);
139            resolve(component);
140        }
141        count++;
142        if (count > maxCount) {
143            clearInterval(interval);
144            reject(new Error(`Component not found for element ${getElementAsTagText(element)}`));
145        }
146    }, 5);
147});
148const findComponents = (currentComponent, onlyParents, onlyMatchName) => {
149    const components = [];
150    componentMapByComponent.forEach((componentName, component) => {
151        if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) {
152            return;
153        }
154        if (onlyMatchName && componentName !== onlyMatchName) {
155            return;
156        }
157        components.push(component);
158    });
159    return components;
160};
161const findChildren = (currentComponent) => {
162    const children = [];
163    componentMapByComponent.forEach((componentName, component) => {
164        if (currentComponent === component) {
165            return;
166        }
167        if (!currentComponent.element.contains(component.element)) {
168            return;
169        }
170        let foundChildComponent = false;
171        componentMapByComponent.forEach((childComponentName, childComponent) => {
172            if (foundChildComponent) {
173                return;
174            }
175            if (childComponent === component) {
176                return;
177            }
178            if (childComponent.element.contains(component.element)) {
179                foundChildComponent = true;
180            }
181        });
182        children.push(component);
183    });
184    return children;
185};
186const findParent = (currentComponent) => {
187    let parentElement = currentComponent.element.parentElement;
188    while (parentElement) {
189        const component = componentMapByElement.get(parentElement);
190        if (component) {
191            return component;
192        }
193        parentElement = parentElement.parentElement;
194    }
195    return null;
196};
197
198class HookManager {
199    constructor() {
200        this.hooks = new Map();
201    }
202    register(hookName, callback) {
203        const hooks = this.hooks.get(hookName) || [];
204        hooks.push(callback);
205        this.hooks.set(hookName, hooks);
206    }
207    unregister(hookName, callback) {
208        const hooks = this.hooks.get(hookName) || [];
209        const index = hooks.indexOf(callback);
210        if (index === -1) {
211            return;
212        }
213        hooks.splice(index, 1);
214        this.hooks.set(hookName, hooks);
215    }
216    triggerHook(hookName, ...args) {
217        const hooks = this.hooks.get(hookName) || [];
218        hooks.forEach((callback) => callback(...args));
219    }
220}
221
222class ChangingItemsTracker {
223    constructor() {
224        this.changedItems = new Map();
225        this.removedItems = new Map();
226    }
227    setItem(itemName, newValue, previousValue) {
228        if (this.removedItems.has(itemName)) {
229            const removedRecord = this.removedItems.get(itemName);
230            this.removedItems.delete(itemName);
231            if (removedRecord.original === newValue) {
232                return;
233            }
234        }
235        if (this.changedItems.has(itemName)) {
236            const originalRecord = this.changedItems.get(itemName);
237            if (originalRecord.original === newValue) {
238                this.changedItems.delete(itemName);
239                return;
240            }
241            this.changedItems.set(itemName, { original: originalRecord.original, new: newValue });
242            return;
243        }
244        this.changedItems.set(itemName, { original: previousValue, new: newValue });
245    }
246    removeItem(itemName, currentValue) {
247        let trueOriginalValue = currentValue;
248        if (this.changedItems.has(itemName)) {
249            const originalRecord = this.changedItems.get(itemName);
250            trueOriginalValue = originalRecord.original;
251            this.changedItems.delete(itemName);
252            if (trueOriginalValue === null) {
253                return;
254            }
255        }
256        if (!this.removedItems.has(itemName)) {
257            this.removedItems.set(itemName, { original: trueOriginalValue });
258        }
259    }
260    getChangedItems() {
261        return Array.from(this.changedItems, ([name, { new: value }]) => ({ name, value }));
262    }
263    getRemovedItems() {
264        return Array.from(this.removedItems.keys());
265    }
266    isEmpty() {
267        return this.changedItems.size === 0 && this.removedItems.size === 0;
268    }
269}
270
271class ElementChanges {
272    constructor() {
273        this.addedClasses = new Set();
274        this.removedClasses = new Set();
275        this.styleChanges = new ChangingItemsTracker();
276        this.attributeChanges = new ChangingItemsTracker();
277    }
278    addClass(className) {
279        if (!this.removedClasses.delete(className)) {
280            this.addedClasses.add(className);
281        }
282    }
283    removeClass(className) {
284        if (!this.addedClasses.delete(className)) {
285            this.removedClasses.add(className);
286        }
287    }
288    addStyle(styleName, newValue, originalValue) {
289        this.styleChanges.setItem(styleName, newValue, originalValue);
290    }
291    removeStyle(styleName, originalValue) {
292        this.styleChanges.removeItem(styleName, originalValue);
293    }
294    addAttribute(attributeName, newValue, originalValue) {
295        this.attributeChanges.setItem(attributeName, newValue, originalValue);
296    }
297    removeAttribute(attributeName, originalValue) {
298        this.attributeChanges.removeItem(attributeName, originalValue);
299    }
300    getAddedClasses() {
301        return [...this.addedClasses];
302    }
303    getRemovedClasses() {
304        return [...this.removedClasses];
305    }
306    getChangedStyles() {
307        return this.styleChanges.getChangedItems();
308    }
309    getRemovedStyles() {
310        return this.styleChanges.getRemovedItems();
311    }
312    getChangedAttributes() {
313        return this.attributeChanges.getChangedItems();
314    }
315    getRemovedAttributes() {
316        return this.attributeChanges.getRemovedItems();
317    }
318    applyToElement(element) {
319        element.classList.add(...this.addedClasses);
320        element.classList.remove(...this.removedClasses);
321        this.styleChanges.getChangedItems().forEach((change) => {
322            element.style.setProperty(change.name, change.value);
323            return;
324        });
325        this.styleChanges.getRemovedItems().forEach((styleName) => {
326            element.style.removeProperty(styleName);
327        });
328        this.attributeChanges.getChangedItems().forEach((change) => {
329            element.setAttribute(change.name, change.value);
330        });
331        this.attributeChanges.getRemovedItems().forEach((attributeName) => {
332            element.removeAttribute(attributeName);
333        });
334    }
335    isEmpty() {
336        return (this.addedClasses.size === 0 &&
337            this.removedClasses.size === 0 &&
338            this.styleChanges.isEmpty() &&
339            this.attributeChanges.isEmpty());
340    }
341}
342
343class ExternalMutationTracker {
344    constructor(element, shouldTrackChangeCallback) {
345        this.changedElements = new WeakMap();
346        this.changedElementsCount = 0;
347        this.addedElements = [];
348        this.removedElements = [];
349        this.isStarted = false;
350        this.element = element;
351        this.shouldTrackChangeCallback = shouldTrackChangeCallback;
352        this.mutationObserver = new MutationObserver(this.onMutations.bind(this));
353    }
354    start() {
355        if (this.isStarted) {
356            return;
357        }
358        this.mutationObserver.observe(this.element, {
359            childList: true,
360            subtree: true,
361            attributes: true,
362            attributeOldValue: true,
363        });
364        this.isStarted = true;
365    }
366    stop() {
367        if (this.isStarted) {
368            this.mutationObserver.disconnect();
369            this.isStarted = false;
370        }
371    }
372    getChangedElement(element) {
373        return this.changedElements.has(element) ? this.changedElements.get(element) : null;
374    }
375    getAddedElements() {
376        return this.addedElements;
377    }
378    wasElementAdded(element) {
379        return this.addedElements.includes(element);
380    }
381    handlePendingChanges() {
382        this.onMutations(this.mutationObserver.takeRecords());
383    }
384    onMutations(mutations) {
385        const handledAttributeMutations = new WeakMap();
386        for (const mutation of mutations) {
387            const element = mutation.target;
388            if (!this.shouldTrackChangeCallback(element)) {
389                continue;
390            }
391            if (this.isElementAddedByTranslation(element)) {
392                continue;
393            }
394            let isChangeInAddedElement = false;
395            for (const addedElement of this.addedElements) {
396                if (addedElement.contains(element)) {
397                    isChangeInAddedElement = true;
398                    break;
399                }
400            }
401            if (isChangeInAddedElement) {
402                continue;
403            }
404            switch (mutation.type) {
405                case 'childList':
406                    this.handleChildListMutation(mutation);
407                    break;
408                case 'attributes':
409                    if (!handledAttributeMutations.has(element)) {
410                        handledAttributeMutations.set(element, []);
411                    }
412                    if (!handledAttributeMutations.get(element).includes(mutation.attributeName)) {
413                        this.handleAttributeMutation(mutation);
414                        handledAttributeMutations.set(element, [
415                            ...handledAttributeMutations.get(element),
416                            mutation.attributeName,
417                        ]);
418                    }
419                    break;
420            }
421        }
422    }
423    handleChildListMutation(mutation) {
424        mutation.addedNodes.forEach((node) => {
425            if (!(node instanceof Element)) {
426                return;
427            }
428            if (this.removedElements.includes(node)) {
429                this.removedElements.splice(this.removedElements.indexOf(node), 1);
430                return;
431            }
432            if (this.isElementAddedByTranslation(node)) {
433                return;
434            }
435            this.addedElements.push(node);
436        });
437        mutation.removedNodes.forEach((node) => {
438            if (!(node instanceof Element)) {
439                return;
440            }
441            if (this.addedElements.includes(node)) {
442                this.addedElements.splice(this.addedElements.indexOf(node), 1);
443                return;
444            }
445            this.removedElements.push(node);
446        });
447    }
448    handleAttributeMutation(mutation) {
449        const element = mutation.target;
450        if (!this.changedElements.has(element)) {
451            this.changedElements.set(element, new ElementChanges());
452            this.changedElementsCount++;
453        }
454        const changedElement = this.changedElements.get(element);
455        switch (mutation.attributeName) {
456            case 'class':
457                this.handleClassAttributeMutation(mutation, changedElement);
458                break;
459            case 'style':
460                this.handleStyleAttributeMutation(mutation, changedElement);
461                break;
462            default:
463                this.handleGenericAttributeMutation(mutation, changedElement);
464        }
465        if (changedElement.isEmpty()) {
466            this.changedElements.delete(element);
467            this.changedElementsCount--;
468        }
469    }
470    handleClassAttributeMutation(mutation, elementChanges) {
471        const element = mutation.target;
472        const previousValue = mutation.oldValue || '';
473        const previousValues = previousValue.match(/(\S+)/gu) || [];
474        const newValues = [].slice.call(element.classList);
475        const addedValues = newValues.filter((value) => !previousValues.includes(value));
476        const removedValues = previousValues.filter((value) => !newValues.includes(value));
477        addedValues.forEach((value) => {
478            elementChanges.addClass(value);
479        });
480        removedValues.forEach((value) => {
481            elementChanges.removeClass(value);
482        });
483    }
484    handleStyleAttributeMutation(mutation, elementChanges) {
485        const element = mutation.target;
486        const previousValue = mutation.oldValue || '';
487        const previousStyles = this.extractStyles(previousValue);
488        const newValue = element.getAttribute('style') || '';
489        const newStyles = this.extractStyles(newValue);
490        const addedOrChangedStyles = Object.keys(newStyles).filter((key) => previousStyles[key] === undefined || previousStyles[key] !== newStyles[key]);
491        const removedStyles = Object.keys(previousStyles).filter((key) => !newStyles[key]);
492        addedOrChangedStyles.forEach((style) => {
493            elementChanges.addStyle(style, newStyles[style], previousStyles[style] === undefined ? null : previousStyles[style]);
494        });
495        removedStyles.forEach((style) => {
496            elementChanges.removeStyle(style, previousStyles[style]);
497        });
498    }
499    handleGenericAttributeMutation(mutation, elementChanges) {
500        const attributeName = mutation.attributeName;
501        const element = mutation.target;
502        let oldValue = mutation.oldValue;
503        let newValue = element.getAttribute(attributeName);
504        if (oldValue === attributeName) {
505            oldValue = '';
506        }
507        if (newValue === attributeName) {
508            newValue = '';
509        }
510        if (!element.hasAttribute(attributeName)) {
511            if (oldValue === null) {
512                return;
513            }
514            elementChanges.removeAttribute(attributeName, mutation.oldValue);
515            return;
516        }
517        if (newValue === oldValue) {
518            return;
519        }
520        elementChanges.addAttribute(attributeName, element.getAttribute(attributeName), mutation.oldValue);
521    }
522    extractStyles(styles) {
523        const styleObject = {};
524        styles.split(';').forEach((style) => {
525            const parts = style.split(':');
526            if (parts.length === 1) {
527                return;
528            }
529            const property = parts[0].trim();
530            styleObject[property] = parts.slice(1).join(':').trim();
531        });
532        return styleObject;
533    }
534    isElementAddedByTranslation(element) {
535        return element.tagName === 'FONT' && element.getAttribute('style') === 'vertical-align: inherit;';
536    }
537}
538
539function parseDirectives(content) {
540    const directives = [];
541    if (!content) {
542        return directives;
543    }
544    let currentActionName = '';
545    let currentArgumentValue = '';
546    let currentArguments = [];
547    let currentModifiers = [];
548    let state = 'action';
549    const getLastActionName = () => {
550        if (currentActionName) {
551            return currentActionName;
552        }
553        if (directives.length === 0) {
554            throw new Error('Could not find any directives');
555        }
556        return directives[directives.length - 1].action;
557    };
558    const pushInstruction = () => {
559        directives.push({
560            action: currentActionName,
561            args: currentArguments,
562            modifiers: currentModifiers,
563            getString: () => {
564                return content;
565            },
566        });
567        currentActionName = '';
568        currentArgumentValue = '';
569        currentArguments = [];
570        currentModifiers = [];
571        state = 'action';
572    };
573    const pushArgument = () => {
574        currentArguments.push(currentArgumentValue.trim());
575        currentArgumentValue = '';
576    };
577    const pushModifier = () => {
578        if (currentArguments.length > 1) {
579            throw new Error(`The modifier "${currentActionName}()" does not support multiple arguments.`);
580        }
581        currentModifiers.push({
582            name: currentActionName,
583            value: currentArguments.length > 0 ? currentArguments[0] : null,
584        });
585        currentActionName = '';
586        currentArguments = [];
587        state = 'action';
588    };
589    for (let i = 0; i < content.length; i++) {
590        const char = content[i];
591        switch (state) {
592            case 'action':
593                if (char === '(') {
594                    state = 'arguments';
595                    break;
596                }
597                if (char === ' ') {
598                    if (currentActionName) {
599                        pushInstruction();
600                    }
601                    break;
602                }
603                if (char === '|') {
604                    pushModifier();
605                    break;
606                }
607                currentActionName += char;
608                break;
609            case 'arguments':
610                if (char === ')') {
611                    pushArgument();
612                    state = 'after_arguments';
613                    break;
614                }
615                if (char === ',') {
616                    pushArgument();
617                    break;
618                }
619                currentArgumentValue += char;
620                break;
621            case 'after_arguments':
622                if (char === '|') {
623                    pushModifier();
624                    break;
625                }
626                if (char !== ' ') {
627                    throw new Error(`Missing space after ${getLastActionName()}()`);
628                }
629                pushInstruction();
630                break;
631        }
632    }
633    switch (state) {
634        case 'action':
635        case 'after_arguments':
636            if (currentActionName) {
637                pushInstruction();
638            }
639            break;
640        default:
641            throw new Error(`Did you forget to add a closing ")" after "${currentActionName}"?`);
642    }
643    return directives;
644}
645
646function combineSpacedArray(parts) {
647    const finalParts = [];
648    parts.forEach((part) => {
649        finalParts.push(...trimAll(part).split(' '));
650    });
651    return finalParts;
652}
653function trimAll(str) {
654    return str.replace(/[\s]+/g, ' ').trim();
655}
656function normalizeModelName(model) {
657    return (model
658        .replace(/\[]$/, '')
659        .split('[')
660        .map((s) => s.replace(']', ''))
661        .join('.'));
662}
663
664function getValueFromElement(element, valueStore) {
665    if (element instanceof HTMLInputElement) {
666        if (element.type === 'checkbox') {
667            const modelNameData = getModelDirectiveFromElement(element, false);
668            if (modelNameData !== null) {
669                const modelValue = valueStore.get(modelNameData.action);
670                if (Array.isArray(modelValue)) {
671                    return getMultipleCheckboxValue(element, modelValue);
672                }
673                if (Object(modelValue) === modelValue) {
674                    return getMultipleCheckboxValue(element, Object.values(modelValue));
675                }
676            }
677            if (element.hasAttribute('value')) {
678                return element.checked ? element.getAttribute('value') : null;
679            }
680            return element.checked;
681        }
682        return inputValue(element);
683    }
684    if (element instanceof HTMLSelectElement) {
685        if (element.multiple) {
686            return Array.from(element.selectedOptions).map((el) => el.value);
687        }
688        return element.value;
689    }
690    if (element.dataset.value) {
691        return element.dataset.value;
692    }
693    if ('value' in element) {
694        return element.value;
695    }
696    if (element.hasAttribute('value')) {
697        return element.getAttribute('value');
698    }
699    return null;
700}
701function setValueOnElement(element, value) {
702    if (element instanceof HTMLInputElement) {
703        if (element.type === 'file') {
704            return;
705        }
706        if (element.type === 'radio') {
707            element.checked = element.value == value;
708            return;
709        }
710        if (element.type === 'checkbox') {
711            if (Array.isArray(value)) {
712                element.checked = value.some((val) => val == element.value);
713            }
714            else if (element.hasAttribute('value')) {
715                element.checked = element.value == value;
716            }
717            else {
718                element.checked = value;
719            }
720            return;
721        }
722    }
723    if (element instanceof HTMLSelectElement) {
724        const arrayWrappedValue = [].concat(value).map((value) => {
725            return `${value}`;
726        });
727        Array.from(element.options).forEach((option) => {
728            option.selected = arrayWrappedValue.includes(option.value);
729        });
730        return;
731    }
732    value = value === undefined ? '' : value;
733    element.value = value;
734}
735function getAllModelDirectiveFromElements(element) {
736    if (!element.dataset.model) {
737        return [];
738    }
739    const directives = parseDirectives(element.dataset.model);
740    directives.forEach((directive) => {
741        if (directive.args.length > 0) {
742            throw new Error(`The data-model="${element.dataset.model}" format is invalid: it does not support passing arguments to the model.`);
743        }
744        directive.action = normalizeModelName(directive.action);
745    });
746    return directives;
747}
748function getModelDirectiveFromElement(element, throwOnMissing = true) {
749    const dataModelDirectives = getAllModelDirectiveFromElements(element);
750    if (dataModelDirectives.length > 0) {
751        return dataModelDirectives[0];
752    }
753    if (element.getAttribute('name')) {
754        const formElement = element.closest('form');
755        if (formElement && 'model' in formElement.dataset) {
756            const directives = parseDirectives(formElement.dataset.model || '*');
757            const directive = directives[0];
758            if (directive.args.length > 0) {
759                throw new Error(`The data-model="${formElement.dataset.model}" format is invalid: it does not support passing arguments to the model.`);
760            }
761            directive.action = normalizeModelName(element.getAttribute('name'));
762            return directive;
763        }
764    }
765    if (!throwOnMissing) {
766        return null;
767    }
768    throw new Error(`Cannot determine the model name for "${getElementAsTagText(element)}": the element must either have a "data-model" (or "name" attribute living inside a <form data-model="*">).`);
769}
770function elementBelongsToThisComponent(element, component) {
771    if (component.element === element) {
772        return true;
773    }
774    if (!component.element.contains(element)) {
775        return false;
776    }
777    const closestLiveComponent = element.closest('[data-controller~="live"]');
778    return closestLiveComponent === component.element;
779}
780function cloneHTMLElement(element) {
781    const newElement = element.cloneNode(true);
782    if (!(newElement instanceof HTMLElement)) {
783        throw new Error('Could not clone element');
784    }
785    return newElement;
786}
787function htmlToElement(html) {
788    const template = document.createElement('template');
789    html = html.trim();
790    template.innerHTML = html;
791    if (template.content.childElementCount > 1) {
792        throw new Error(`Component HTML contains ${template.content.childElementCount} elements, but only 1 root element is allowed.`);
793    }
794    const child = template.content.firstElementChild;
795    if (!child) {
796        throw new Error('Child not found');
797    }
798    if (!(child instanceof HTMLElement)) {
799        throw new Error(`Created element is not an HTMLElement: ${html.trim()}`);
800    }
801    return child;
802}
803const getMultipleCheckboxValue = (element, currentValues) => {
804    const finalValues = [...currentValues];
805    const value = inputValue(element);
806    const index = currentValues.indexOf(value);
807    if (element.checked) {
808        if (index === -1) {
809            finalValues.push(value);
810        }
811        return finalValues;
812    }
813    if (index > -1) {
814        finalValues.splice(index, 1);
815    }
816    return finalValues;
817};
818const inputValue = (element) => element.dataset.value ? element.dataset.value : element.value;
819
820// base IIFE to define idiomorph
821var Idiomorph = (function () {
822
823        //=============================================================================
824        // AND NOW IT BEGINS...
825        //=============================================================================
826        let EMPTY_SET = new Set();
827
828        // default configuration values, updatable by users now
829        let defaults = {
830            morphStyle: "outerHTML",
831            callbacks : {
832                beforeNodeAdded: noOp,
833                afterNodeAdded: noOp,
834                beforeNodeMorphed: noOp,
835                afterNodeMorphed: noOp,
836                beforeNodeRemoved: noOp,
837                afterNodeRemoved: noOp,
838                beforeAttributeUpdated: noOp,
839
840            },
841            head: {
842                style: 'merge',
843                shouldPreserve: function (elt) {
844                    return elt.getAttribute("im-preserve") === "true";
845                },
846                shouldReAppend: function (elt) {
847                    return elt.getAttribute("im-re-append") === "true";
848                },
849                shouldRemove: noOp,
850                afterHeadMorphed: noOp,
851            }
852        };
853
854        //=============================================================================
855        // Core Morphing Algorithm - morph, morphNormalizedContent, morphOldNodeTo, morphChildren
856        //=============================================================================
857        function morph(oldNode, newContent, config = {}) {
858
859            if (oldNode instanceof Document) {
860                oldNode = oldNode.documentElement;
861            }
862
863            if (typeof newContent === 'string') {
864                newContent = parseContent(newContent);
865            }
866
867            let normalizedContent = normalizeContent(newContent);
868
869            let ctx = createMorphContext(oldNode, normalizedContent, config);
870
871            return morphNormalizedContent(oldNode, normalizedContent, ctx);
872        }
873
874        function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
875            if (ctx.head.block) {
876                let oldHead = oldNode.querySelector('head');
877                let newHead = normalizedNewContent.querySelector('head');
878                if (oldHead && newHead) {
879                    let promises = handleHeadElement(newHead, oldHead, ctx);
880                    // when head promises resolve, call morph again, ignoring the head tag
881                    Promise.all(promises).then(function () {
882                        morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
883                            head: {
884                                block: false,
885                                ignore: true
886                            }
887                        }));
888                    });
889                    return;
890                }
891            }
892
893            if (ctx.morphStyle === "innerHTML") {
894
895                // innerHTML, so we are only updating the children
896                morphChildren(normalizedNewContent, oldNode, ctx);
897                return oldNode.children;
898
899            } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
900                // otherwise find the best element match in the new content, morph that, and merge its siblings
901                // into either side of the best match
902                let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
903
904                // stash the siblings that will need to be inserted on either side of the best match
905                let previousSibling = bestMatch?.previousSibling;
906                let nextSibling = bestMatch?.nextSibling;
907
908                // morph it
909                let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
910
911                if (bestMatch) {
912                    // if there was a best match, merge the siblings in too and return the
913                    // whole bunch
914                    return insertSiblings(previousSibling, morphedNode, nextSibling);
915                } else {
916                    // otherwise nothing was added to the DOM
917                    return []
918                }
919            } else {
920                throw "Do not understand how to morph style " + ctx.morphStyle;
921            }
922        }
923
924
925        /**
926         * @param possibleActiveElement
927         * @param ctx
928         * @returns {boolean}
929         */
930        function ignoreValueOfActiveElement(possibleActiveElement, ctx) {
931            return ctx.ignoreActiveValue && possibleActiveElement === document.activeElement;
932        }
933
934        /**
935         * @param oldNode root node to merge content into
936         * @param newContent new content to merge
937         * @param ctx the merge context
938         * @returns {Element} the element that ended up in the DOM
939         */
940        function morphOldNodeTo(oldNode, newContent, ctx) {
941            if (ctx.ignoreActive && oldNode === document.activeElement) ; else if (newContent == null) {
942                if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
943
944                oldNode.remove();
945                ctx.callbacks.afterNodeRemoved(oldNode);
946                return null;
947            } else if (!isSoftMatch(oldNode, newContent)) {
948                if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
949                if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;
950
951                oldNode.parentElement.replaceChild(newContent, oldNode);
952                ctx.callbacks.afterNodeAdded(newContent);
953                ctx.callbacks.afterNodeRemoved(oldNode);
954                return newContent;
955            } else {
956                if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) return oldNode;
957
958                if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== "morph") {
959                    handleHeadElement(newContent, oldNode, ctx);
960                } else {
961                    syncNodeFrom(newContent, oldNode, ctx);
962                    if (!ignoreValueOfActiveElement(oldNode, ctx)) {
963                        morphChildren(newContent, oldNode, ctx);
964                    }
965                }
966                ctx.callbacks.afterNodeMorphed(oldNode, newContent);
967                return oldNode;
968            }
969        }
970
971        /**
972         * This is the core algorithm for matching up children.  The idea is to use id sets to try to match up
973         * nodes as faithfully as possible.  We greedily match, which allows us to keep the algorithm fast, but
974         * by using id sets, we are able to better match up with content deeper in the DOM.
975         *
976         * Basic algorithm is, for each node in the new content:
977         *
978         * - if we have reached the end of the old parent, append the new content
979         * - if the new content has an id set match with the current insertion point, morph
980         * - search for an id set match
981         * - if id set match found, morph
982         * - otherwise search for a "soft" match
983         * - if a soft match is found, morph
984         * - otherwise, prepend the new node before the current insertion point
985         *
986         * The two search algorithms terminate if competing node matches appear to outweigh what can be achieved
987         * with the current node.  See findIdSetMatch() and findSoftMatch() for details.
988         *
989         * @param {Element} newParent the parent element of the new content
990         * @param {Element } oldParent the old content that we are merging the new content into
991         * @param ctx the merge context
992         */
993        function morphChildren(newParent, oldParent, ctx) {
994
995            let nextNewChild = newParent.firstChild;
996            let insertionPoint = oldParent.firstChild;
997            let newChild;
998
999            // run through all the new content
1000            while (nextNewChild) {
1001
1002                newChild = nextNewChild;
1003                nextNewChild = newChild.nextSibling;
1004
1005                // if we are at the end of the exiting parent's children, just append
1006                if (insertionPoint == null) {
1007                    if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1008
1009                    oldParent.appendChild(newChild);
1010                    ctx.callbacks.afterNodeAdded(newChild);
1011                    removeIdsFromConsideration(ctx, newChild);
1012                    continue;
1013                }
1014
1015                // if the current node has an id set match then morph
1016                if (isIdSetMatch(newChild, insertionPoint, ctx)) {
1017                    morphOldNodeTo(insertionPoint, newChild, ctx);
1018                    insertionPoint = insertionPoint.nextSibling;
1019                    removeIdsFromConsideration(ctx, newChild);
1020                    continue;
1021                }
1022
1023                // otherwise search forward in the existing old children for an id set match
1024                let idSetMatch = findIdSetMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1025
1026                // if we found a potential match, remove the nodes until that point and morph
1027                if (idSetMatch) {
1028                    insertionPoint = removeNodesBetween(insertionPoint, idSetMatch, ctx);
1029                    morphOldNodeTo(idSetMatch, newChild, ctx);
1030                    removeIdsFromConsideration(ctx, newChild);
1031                    continue;
1032                }
1033
1034                // no id set match found, so scan forward for a soft match for the current node
1035                let softMatch = findSoftMatch(newParent, oldParent, newChild, insertionPoint, ctx);
1036
1037                // if we found a soft match for the current node, morph
1038                if (softMatch) {
1039                    insertionPoint = removeNodesBetween(insertionPoint, softMatch, ctx);
1040                    morphOldNodeTo(softMatch, newChild, ctx);
1041                    removeIdsFromConsideration(ctx, newChild);
1042                    continue;
1043                }
1044
1045                // abandon all hope of morphing, just insert the new child before the insertion point
1046                // and move on
1047                if (ctx.callbacks.beforeNodeAdded(newChild) === false) return;
1048
1049                oldParent.insertBefore(newChild, insertionPoint);
1050                ctx.callbacks.afterNodeAdded(newChild);
1051                removeIdsFromConsideration(ctx, newChild);
1052            }
1053
1054            // remove any remaining old nodes that didn't match up with 
1054new content
1055            while (insertionPoint !== null) {
1056
1057                let tempNode = insertionPoint;
1058                insertionPoint = insertionPoint.nextSibling;
1059                removeNode(tempNode, ctx);
1060            }
1061        }
1062
1063        //=============================================================================
1064        // Attribute Syncing Code
1065        //=============================================================================
1066
1067        /**
1068         * @param attr {String} the attribute to be mutated
1069         * @param to {Element} the element that is going to be updated
1070         * @param updateType {("update"|"remove")}
1071         * @param ctx the merge context
1072         * @returns {boolean} true if the attribute should be ignored, false otherwise
1073         */
1074        function ignoreAttribute(attr, to, updateType, ctx) {
1075            if(attr === 'value' && ctx.ignoreActiveValue && to === document.activeElement){
1076                return true;
1077            }
1078            return ctx.callbacks.beforeAttributeUpdated(attr, to, updateType) === false;
1079        }
1080
1081        /**
1082         * syncs a given node with another node, copying over all attributes and
1083         * inner element state from the 'from' node to the 'to' node
1084         *
1085         * @param {Element} from the element to copy attributes & state from
1086         * @param {Element} to the element to copy attributes & state to
1087         * @param ctx the merge context
1088         */
1089        function syncNodeFrom(from, to, ctx) {
1090            let type = from.nodeType;
1091
1092            // if is an element type, sync the attributes from the
1093            // new node into the new node
1094            if (type === 1 /* element type */) {
1095                const fromAttributes = from.attributes;
1096                const toAttributes = to.attributes;
1097                for (const fromAttribute of fromAttributes) {
1098                    if (ignoreAttribute(fromAttribute.name, to, 'update', ctx)) {
1099                        continue;
1100                    }
1101                    if (to.getAttribute(fromAttribute.name) !== fromAttribute.value) {
1102                        to.setAttribute(fromAttribute.name, fromAttribute.value);
1103                    }
1104                }
1105                // iterate backwards to avoid skipping over items when a delete occurs
1106                for (let i = toAttributes.length - 1; 0 <= i; i--) {
1107                    const toAttribute = toAttributes[i];
1108                    if (ignoreAttribute(toAttribute.name, to, 'remove', ctx)) {
1109                        continue;
1110                    }
1111                    if (!from.hasAttribute(toAttribute.name)) {
1112                        to.removeAttribute(toAttribute.name);
1113                    }
1114                }
1115            }
1116
1117            // sync text nodes
1118            if (type === 8 /* comment */ || type === 3 /* text */) {
1119                if (to.nodeValue !== from.nodeValue) {
1120                    to.nodeValue = from.nodeValue;
1121                }
1122            }
1123
1124            if (!ignoreValueOfActiveElement(to, ctx)) {
1125                // sync input values
1126                syncInputValue(from, to, ctx);
1127            }
1128        }
1129
1130        /**
1131         * @param from {Element} element to sync the value from
1132         * @param to {Element} element to sync the value to
1133         * @param attributeName {String} the attribute name
1134         * @param ctx the merge context
1135         */
1136        function syncBooleanAttribute(from, to, attributeName, ctx) {
1137            if (from[attributeName] !== to[attributeName]) {
1138                let ignoreUpdate = ignoreAttribute(attributeName, to, 'update', ctx);
1139                if (!ignoreUpdate) {
1140                    to[attributeName] = from[attributeName];
1141                }
1142                if (from[attributeName]) {
1143                    if (!ignoreUpdate) {
1144                        to.setAttribute(attributeName, from[attributeName]);
1145                    }
1146                } else {
1147                    if (!ignoreAttribute(attributeName, to, 'remove', ctx)) {
1148                        to.removeAttribute(attributeName);
1149                    }
1150                }
1151            }
1152        }
1153
1154        /**
1155         * NB: many bothans died to bring us information:
1156         *
1157         *  https://github.com/patrick-steele-idem/morphdom/blob/master/src/specialElHandlers.js
1158         *  https://github.com/choojs/nanomorph/blob/master/lib/morph.jsL113
1159         *
1160         * @param from {Element} the element to sync the input value from
1161         * @param to {Element} the element to sync the input value to
1162         * @param ctx the merge context
1163         */
1164        function syncInputValue(from, to, ctx) {
1165            if (from instanceof HTMLInputElement &&
1166                to instanceof HTMLInputElement &&
1167                from.type !== 'file') {
1168
1169                let fromValue = from.value;
1170                let toValue = to.value;
1171
1172                // sync boolean attributes
1173                syncBooleanAttribute(from, to, 'checked', ctx);
1174                syncBooleanAttribute(from, to, 'disabled', ctx);
1175
1176                if (!from.hasAttribute('value')) {
1177                    if (!ignoreAttribute('value', to, 'remove', ctx)) {
1178                        to.value = '';
1179                        to.removeAttribute('value');
1180                    }
1181                } else if (fromValue !== toValue) {
1182                    if (!ignoreAttribute('value', to, 'update', ctx)) {
1183                        to.setAttribute('value', fromValue);
1184                        to.value = fromValue;
1185                    }
1186                }
1187            } else if (from instanceof HTMLOptionElement) {
1188                syncBooleanAttribute(from, to, 'selected', ctx);
1189            } else if (from instanceof HTMLTextAreaElement && to instanceof HTMLTextAreaElement) {
1190                let fromValue = from.value;
1191                let toValue = to.value;
1192                if (ignoreAttribute('value', to, 'update', ctx)) {
1193                    return;
1194                }
1195                if (fromValue !== toValue) {
1196                    to.value = fromValue;
1197                }
1198                if (to.firstChild && to.firstChild.nodeValue !== fromValue) {
1199                    to.firstChild.nodeValue = fromValue;
1200                }
1201            }
1202        }
1203
1204        //=============================================================================
1205        // the HEAD tag can be handled specially, either w/ a 'merge' or 'append' style
1206        //=============================================================================
1207        function handleHeadElement(newHeadTag, currentHead, ctx) {
1208
1209            let added = [];
1210            let removed = [];
1211            let preserved = [];
1212            let nodesToAppend = [];
1213
1214            let headMergeStyle = ctx.head.style;
1215
1216            // put all new head elements into a Map, by their outerHTML
1217            let srcToNewHeadNodes = new Map();
1218            for (const newHeadChild of newHeadTag.children) {
1219                srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
1220            }
1221
1222            // for each elt in the current head
1223            for (const currentHeadElt of currentHead.children) {
1224
1225                // If the current head element is in the map
1226                let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
1227                let isReAppended = ctx.head.shouldReAppend(currentHeadElt);
1228                let isPreserved = ctx.head.shouldPreserve(currentHeadElt);
1229                if (inNewContent || isPreserved) {
1230                    if (isReAppended) {
1231                        // remove the current version and let the new version replace it and re-execute
1232                        removed.push(currentHeadElt);
1233                    } else {
1234                        // this element already exists and should not be re-appended, so remove it from
1235                        // the new content map, preserving it in the DOM
1236                        srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
1237                        preserved.push(currentHeadElt);
1238                    }
1239                } else {
1240                    if (headMergeStyle === "append") {
1241                        // we are appending and this existing element is not new content
1242                        // so if and only if it is marked for re-append do we do anything
1243                        if (isReAppended) {
1244                            removed.push(currentHeadElt);
1245                            nodesToAppend.push(currentHeadElt);
1246                        }
1247                    } else {
1248                        // if this is a merge, we remove this content since it is not in the new head
1249                        if (ctx.head.shouldRemove(currentHeadElt) !== false) {
1250                            removed.push(currentHeadElt);
1251                        }
1252                    }
1253                }
1254            }
1255
1256            // Push the remaining new head elements in the Map into the
1257            // nodes to append to the head tag
1258            nodesToAppend.push(...srcToNewHeadNodes.values());
1259
1260            let promises = [];
1261            for (const newNode of nodesToAppend) {
1262                let newElt = document.createRange().createContextualFragment(newNode.outerHTML).firstChild;
1263                if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
1264                    if (newElt.href || newElt.src) {
1265                        let resolve = null;
1266                        let promise = new Promise(function (_resolve) {
1267                            resolve = _resolve;
1268                        });
1269                        newElt.addEventListener('load', function () {
1270                            resolve();
1271                        });
1272                        promises.push(promise);
1273                    }
1274                    currentHead.appendChild(newElt);
1275                    ctx.callbacks.afterNodeAdded(newElt);
1276                    added.push(newElt);
1277                }
1278            }
1279
1280            // remove all removed elements, after we have appended the new elements to avoid
1281            // additional network requests for things like style sheets
1282            for (const removedElement of removed) {
1283                if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {
1284                    currentHead.removeChild(removedElement);
1285                    ctx.callbacks.afterNodeRemoved(removedElement);
1286                }
1287            }
1288
1289            ctx.head.afterHeadMorphed(currentHead, {added: added, kept: preserved, removed: removed});
1290            return promises;
1291        }
1292
1293        function noOp() {
1294        }
1295
1296        /*
1297          Deep merges the config object and the Idiomoroph.defaults object to
1298          produce a final configuration object
1299         */
1300        function mergeDefaults(config) {
1301            let finalConfig = {};
1302            // copy top level stuff into final config
1303            Object.assign(finalConfig, defaults);
1304            Object.assign(finalConfig, config);
1305
1306            // copy callbacks into final config (do this to deep merge the callbacks)
1307            finalConfig.callbacks = {};
1308            Object.assign(finalConfig.callbacks, defaults.callbacks);
1309            Object.assign(finalConfig.callbacks, config.callbacks);
1310
1311            // copy head config into final config  (do this to deep merge the head)
1312            finalConfig.head = {};
1313            Object.assign(finalConfig.head, defaults.head);
1314            Object.assign(finalConfig.head, config.head);
1315            return finalConfig;
1316        }
1317
1318        function createMorphContext(oldNode, newContent, config) {
1319            config = mergeDefaults(config);
1320            return {
1321                target: oldNode,
1322                newContent: newContent,
1323                config: config,
1324                morphStyle: config.morphStyle,
1325                ignoreActive: config.ignoreActive,
1326                ignoreActiveValue: config.ignoreActiveValue,
1327                idMap: createIdMap(oldNode, newContent),
1328                deadIds: new Set(),
1329                callbacks: config.callbacks,
1330                head: config.head
1331            }
1332        }
1333
1334        function isIdSetMatch(node1, node2, ctx) {
1335            if (node1 == null || node2 == null) {
1336                return false;
1337            }
1338            if (node1.nodeType === node2.nodeType && node1.tagName === node2.tagName) {
1339                if (node1.id !== "" && node1.id === node2.id) {
1340                    return true;
1341                } else {
1342                    return getIdIntersectionCount(ctx, node1, node2) > 0;
1343                }
1344            }
1345            return false;
1346        }
1347
1348        function isSoftMatch(node1, node2) {
1349            if (node1 == null || node2 == null) {
1350                return false;
1351            }
1352            return node1.nodeType === node2.nodeType && node1.tagName === node2.tagName
1353        }
1354
1355        function removeNodesBetween(startInclusive, endExclusive, ctx) {
1356            while (startInclusive !== endExclusive) {
1357                let tempNode = startInclusive;
1358                startInclusive = startInclusive.nextSibling;
1359                removeNode(tempNode, ctx);
1360            }
1361            removeIdsFromConsideration(ctx, endExclusive);
1362            return endExclusive.nextSibling;
1363        }
1364
1365        //=============================================================================
1366        // Scans forward from the insertionPoint in the old parent looking for a potential id match
1367        // for the newChild.  We stop if we find a potential id match for the new child OR
1368        // if the number of potential id matches we are discarding is greater than the
1369        // potential id matches for the new child
1370        //=============================================================================
1371        function findIdSetMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
1372
1373            // max id matches we are willing to discard in our search
1374            let newChildPotentialIdCount = getIdIntersectionCount(ctx, newChild, oldParent);
1375
1376            let potentialMatch = null;
1377
1378            // only search forward if there is a possibility of an id match
1379            if (newChildPotentialIdCount > 0) {
1380                let potentialMatch = insertionPoint;
1381                // if there is a possibility of an id match, scan forward
1382                // keep track of the potential id match count we are discarding (the
1383                // newChildPotentialIdCount must be greater than this to make it likely
1384                // worth it)
1385                let otherMatchCount = 0;
1386                while (potentialMatch != null) {
1387
1388                    // If we have an id match, return the current potential match
1389                    if (isIdSetMatch(newChild, potentialMatch, ctx)) {
1390                        return potentialMatch;
1391                    }
1392
1393                    // computer the other potential matches of this new content
1394                    otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
1395                    if (otherMatchCount > newChildPotentialIdCount) {
1396                        // if we have more potential id matches in _other_ content, we
1397                        // do not have a good candidate for an id match, so return null
1398                        return null;
1399                    }
1400
1401                    // advanced to the next old content child
1402                    potentialMatch = potentialMatch.nextSibling;
1403                }
1404            }
1405            return potentialMatch;
1406        }
1407
1408        //=============================================================================
1409        // Scans forward from the insertionPoint in the old parent looking for a potential soft match
1410        // for the newChild.  We stop if we find a potential soft match for the new child OR
1411        // if we find a potential id match in the old parents children OR if we find two
1412        // potential soft matches for the next two pieces of new content
1413        //=============================================================================
1414        function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
1415
1416            let potentialSoftMatch = insertionPoint;
1417            let nextSibling = newChild.nextSibling;
1418            let siblingSoftMatchCount = 0;
1419
1420            while (potentialSoftMatch != null) {
1421
1422                if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
1423                    // the current potential soft match has a potential id set match with the remaining new
1424                    // content so bail out of looking
1425                    return null;
1426                }
1427
1428                // if we have a soft match with the current node, return it
1429                if (isSoftMatch(newChild, potentialSoftMatch)) {
1430                    return potentialSoftMatch;
1431                }
1432
1433                if (isSoftMatch(nextSibling, potentialSoftMatch)) {
1434                    // the next new node has a soft match with this node, so
1435                    // increment the count of future soft matches
1436                    siblingSoftMatchCount++;
1437                    nextSibling = nextSibling.nextSibling;
1438
1439                    // If there are two future soft matches, bail to allow the siblings to soft match
1440                    // so that we don't consume future soft matches for the sake of the current node
1441                    if (siblingSoftMatchCount >= 2) {
1442                        return null;
1443                    }
1444                }
1445
1446                // advanced to the next old content child
1447                potentialSoftMatch = potentialSoftMatch.nextSibling;
1448            }
1449
1450            return potentialSoftMatch;
1451        }
1452
1453        function parseContent(newContent) {
1454            let parser = new DOMParser();
1455
1456            // remove svgs to avoid false-positive matches on head, etc.
1457            let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
1458
1459            // if the newContent contains a html, head or body tag, we can simply parse it w/
1459o wrapping
1460            if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
1461                let content = parser.parseFromString(newContent, "text/html");
1462                // if it is a full HTML document, return the document itself as the parent container
1463                if (contentWithSvgsRemoved.match(/<\/html>/)) {
1464                    content.generatedByIdiomorph = true;
1465                    return content;
1466                } else {
1467                    // otherwise return the html element as the parent container
1468                    let htmlElement = content.firstChild;
1469                    if (htmlElement) {
1470                        htmlElement.generatedByIdiomorph = true;
1471                        return htmlElement;
1472                    } else {
1473                        return null;
1474                    }
1475                }
1476            } else {
1477                // if it is partial HTML, wrap it in a template tag to provide a parent element and also to help
1478                // deal with touchy tags like tr, tbody, etc.
1479                let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
1480                let content = responseDoc.body.querySelector('template').content;
1481                content.generatedByIdiomorph = true;
1482                return content
1483            }
1484        }
1485
1486        function normalizeContent(newContent) {
1487            if (newContent == null) {
1488                // noinspection UnnecessaryLocalVariableJS
1489                const dummyParent = document.createElement('div');
1490                return dummyParent;
1491            } else if (newContent.generatedByIdiomorph) {
1492                // the template tag created by idiomorph parsing can serve as a dummy parent
1493                return newContent;
1494            } else if (newContent instanceof Node) {
1495                // a single node is added as a child to a dummy parent
1496                const dummyParent = document.createElement('div');
1497                dummyParent.append(newContent);
1498                return dummyParent;
1499            } else {
1500                // all nodes in the array or HTMLElement collection are consolidated under
1501                // a single dummy parent element
1502                const dummyParent = document.createElement('div');
1503                for (const elt of [...newContent]) {
1504                    dummyParent.append(elt);
1505                }
1506                return dummyParent;
1507            }
1508        }
1509
1510        function insertSiblings(previousSibling, morphedNode, nextSibling) {
1511            let stack = [];
1512            let added = [];
1513            while (previousSibling != null) {
1514                stack.push(previousSibling);
1515                previousSibling = previousSibling.previousSibling;
1516            }
1517            while (stack.length > 0) {
1518                let node = stack.pop();
1519                added.push(node); // push added preceding siblings on in order and insert
1520                morphedNode.parentElement.insertBefore(node, morphedNode);
1521            }
1522            added.push(morphedNode);
1523            while (nextSibling != null) {
1524                stack.push(nextSibling);
1525                added.push(nextSibling); // here we are going in order, so push on as we scan, rather than add
1526                nextSibling = nextSibling.nextSibling;
1527            }
1528            while (stack.length > 0) {
1529                morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
1530            }
1531            return added;
1532        }
1533
1534        function findBestNodeMatch(newContent, oldNode, ctx) {
1535            let currentElement;
1536            currentElement = newContent.firstChild;
1537            let bestElement = currentElement;
1538            let score = 0;
1539            while (currentElement) {
1540                let newScore = scoreElement(currentElement, oldNode, ctx);
1541                if (newScore > score) {
1542                    bestElement = currentElement;
1543                    score = newScore;
1544                }
1545                currentElement = currentElement.nextSibling;
1546            }
1547            return bestElement;
1548        }
1549
1550        function scoreElement(node1, node2, ctx) {
1551            if (isSoftMatch(node1, node2)) {
1552                return .5 + getIdIntersectionCount(ctx, node1, node2);
1553            }
1554            return 0;
1555        }
1556
1557        function removeNode(tempNode, ctx) {
1558            removeIdsFromConsideration(ctx, tempNode);
1559            if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
1560
1561            tempNode.remove();
1562            ctx.callbacks.afterNodeRemoved(tempNode);
1563        }
1564
1565        //=============================================================================
1566        // ID Set Functions
1567        //=============================================================================
1568
1569        function isIdInConsideration(ctx, id) {
1570            return !ctx.deadIds.has(id);
1571        }
1572
1573        function idIsWithinNode(ctx, id, targetNode) {
1574            let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
1575            return idSet.has(id);
1576        }
1577
1578        function removeIdsFromConsideration(ctx, node) {
1579            let idSet = ctx.idMap.get(node) || EMPTY_SET;
1580            for (const id of idSet) {
1581                ctx.deadIds.add(id);
1582            }
1583        }
1584
1585        function getIdIntersectionCount(ctx, node1, node2) {
1586            let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
1587            let matchCount = 0;
1588            for (const id of sourceSet) {
1589                // a potential match is an id in the source and potentialIdsSet, but
1590                // that has not already been merged into the DOM
1591                if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
1592                    ++matchCount;
1593                }
1594            }
1595            return matchCount;
1596        }
1597
1598        /**
1599         * A bottom up algorithm that finds all elements with ids inside of the node
1600         * argument and populates id sets for those nodes and all their parents, generating
1601         * a set of ids contained within all nodes for the entire hierarchy in the DOM
1602         *
1603         * @param node {Element}
1604         * @param {Map<Node, Set<String>>} idMap
1605         */
1606        function populateIdMapForNode(node, idMap) {
1607            let nodeParent = node.parentElement;
1608            // find all elements with an id property
1609            let idElements = node.querySelectorAll('[id]');
1610            for (const elt of idElements) {
1611                let current = elt;
1612                // walk up the parent hierarchy of that element, adding the id
1613                // of element to the parent's id set
1614                while (current !== nodeParent && current != null) {
1615                    let idSet = idMap.get(current);
1616                    // if the id set doesn't exist, create it and insert it in the  map
1617                    if (idSet == null) {
1618                        idSet = new Set();
1619                        idMap.set(current, idSet);
1620                    }
1621                    idSet.add(elt.id);
1622                    current = current.parentElement;
1623                }
1624            }
1625        }
1626
1627        /**
1628         * This function computes a map of nodes to all ids contained within that node (inclusive of the
1629         * node).  This map can be used to ask if two nodes have intersecting sets of ids, which allows
1630         * for a looser definition of "matching" than tradition id matching, and allows child nodes
1631         * to contribute to a parent nodes matching.
1632         *
1633         * @param {Element} oldContent  the old content that will be morphed
1634         * @param {Element} newContent  the new content to morph to
1635         * @returns {Map<Node, Set<String>>} a map of nodes to id sets for the
1636         */
1637        function createIdMap(oldContent, newContent) {
1638            let idMap = new Map();
1639            populateIdMapForNode(oldContent, idMap);
1640            populateIdMapForNode(newContent, idMap);
1641            return idMap;
1642        }
1643
1644        //=============================================================================
1645        // This is what ends up becoming the Idiomorph global object
1646        //=============================================================================
1647        return {
1648            morph,
1649            defaults
1650        }
1651    })();
1652
1653function normalizeAttributesForComparison(element) {
1654    const isFileInput = element instanceof HTMLInputElement && element.type === 'file';
1655    if (!isFileInput) {
1656        if ('value' in element) {
1657            element.setAttribute('value', element.value);
1658        }
1659        else if (element.hasAttribute('value')) {
1660            element.setAttribute('value', '');
1661        }
1662    }
1663    Array.from(element.children).forEach((child) => {
1664        normalizeAttributesForComparison(child);
1665    });
1666}
1667
1668const syncAttributes = (fromEl, toEl) => {
1669    for (let i = 0; i < fromEl.attributes.length; i++) {
1670        const attr = fromEl.attributes[i];
1671        toEl.setAttribute(attr.name, attr.value);
1672    }
1673};
1674function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker) {
1675    const originalElementIdsToSwapAfter = [];
1676    const originalElementsToPreserve = new Map();
1677    const markElementAsNeedingPostMorphSwap = (id, replaceWithClone) => {
1678        const oldElement = originalElementsToPreserve.get(id);
1679        if (!(oldElement instanceof HTMLElement)) {
1680            throw new Error(`Original element with id ${id} not found`);
1681        }
1682        originalElementIdsToSwapAfter.push(id);
1683        if (!replaceWithClone) {
1684            return null;
1685        }
1686        const clonedOldElement = cloneHTMLElement(oldElement);
1687        oldElement.replaceWith(clonedOldElement);
1688        return clonedOldElement;
1689    };
1690    rootToElement.querySelectorAll('[data-live-preserve]').forEach((newElement) => {
1691        const id = newElement.id;
1692        if (!id) {
1693            throw new Error('The data-live-preserve attribute requires an id attribute to be set on the element');
1694        }
1695        const oldElement = rootFromElement.querySelector(`#${id}`);
1696        if (!(oldElement instanceof HTMLElement)) {
1697            throw new Error(`The element with id "${id}" was not found in the original HTML`);
1698        }
1699        newElement.removeAttribute('data-live-preserve');
1700        originalElementsToPreserve.set(id, oldElement);
1701        syncAttributes(newElement, oldElement);
1702    });
1703    Idiomorph.morph(rootFromElement, rootToElement, {
1704        callbacks: {
1705            beforeNodeMorphed: (fromEl, toEl) => {
1706                if (!(fromEl instanceof Element) || !(toEl instanceof Element)) {
1707                    return true;
1708                }
1709                if (fromEl === rootFromElement) {
1710                    return true;
1711                }
1712                if (fromEl.id && originalElementsToPreserve.has(fromEl.id)) {
1713                    if (fromEl.id === toEl.id) {
1714                        return false;
1715                    }
1716                    const clonedFromEl = markElementAsNeedingPostMorphSwap(fromEl.id, true);
1717                    if (!clonedFromEl) {
1718                        throw new Error('missing clone');
1719                    }
1720                    Idiomorph.morph(clonedFromEl, toEl);
1721                    return false;
1722                }
1723                if (fromEl instanceof HTMLElement && toEl instanceof HTMLElement) {
1724                    if (typeof fromEl.__x !== 'undefined') {
1725                        if (!window.Alpine) {
1726                            throw new Error('Unable to access Alpine.js though the global window.Alpine variable. Please make sure Alpine.js is loaded before Symfony UX LiveComponent.');
1727                        }
1728                        if (typeof window.Alpine.morph !== 'function') {
1729                            throw new Error('Unable to access Alpine.js morph function. Please make sure the Alpine.js Morph plugin is installed and loaded, see https://alpinejs.dev/plugins/morph for more information.');
1730                        }
1731                        window.Alpine.morph(fromEl.__x, toEl);
1732                    }
1733                    if (externalMutationTracker.wasElementAdded(fromEl)) {
1734                        fromEl.insertAdjacentElement('afterend', toEl);
1735                        return false;
1736                    }
1737                    if (modifiedFieldElements.includes(fromEl)) {
1738                        setValueOnElement(toEl, getElementValue(fromEl));
1739                    }
1740                    if (fromEl === document.activeElement &&
1741                        fromEl !== document.body &&
1742                        null !== getModelDirectiveFromElement(fromEl, false)) {
1743                        setValueOnElement(toEl, getElementValue(fromEl));
1744                    }
1745                    const elementChanges = externalMutationTracker.getChangedElement(fromEl);
1746                    if (elementChanges) {
1747                        elementChanges.applyToElement(toEl);
1748                    }
1749                    if (fromEl.nodeName.toUpperCase() !== 'OPTION' && fromEl.isEqualNode(toEl)) {
1750                        const normalizedFromEl = cloneHTMLElement(fromEl);
1751                        normalizeAttributesForComparison(normalizedFromEl);
1752                        const normalizedToEl = cloneHTMLElement(toEl);
1753                        normalizeAttributesForComparison(normalizedToEl);
1754                        if (normalizedFromEl.isEqualNode(normalizedToEl)) {
1755                            return false;
1756                        }
1757                    }
1758                }
1759                if (fromEl.hasAttribute('data-skip-morph') || (fromEl.id && fromEl.id !== toEl.id)) {
1760                    fromEl.innerHTML = toEl.innerHTML;
1761                    return true;
1762                }
1763                if (fromEl.parentElement?.hasAttribute('data-skip-morph')) {
1764                    return false;
1765                }
1766                return !fromEl.hasAttribute('data-live-ignore');
1767            },
1768            beforeNodeRemoved(node) {
1769                if (!(node instanceof HTMLElement)) {
1770                    return true;
1771                }
1772                if (node.id && originalElementsToPreserve.has(node.id)) {
1773                    markElementAsNeedingPostMorphSwap(node.id, false);
1774                    return true;
1775                }
1776                if (externalMutationTracker.wasElementAdded(node)) {
1777                    return false;
1778                }
1779                return !node.hasAttribute('data-live-ignore');
1780            },
1781        },
1782    });
1783    originalElementIdsToSwapAfter.forEach((id) => {
1784        const newElement = rootFromElement.querySelector(`#${id}`);
1785        const originalElement = originalElementsToPreserve.get(id);
1786        if (!(newElement instanceof HTMLElement) || !(originalElement instanceof HTMLElement)) {
1787            throw new Error('Missing elements.');
1788        }
1789        newElement.replaceWith(originalElement);
1790    });
1791}
1792
1793class UnsyncedInputsTracker {
1794    constructor(component, modelElementResolver) {
1795        this.elementEventListeners = [
1796            { event: 'input', callback: (event) => this.handleInputEvent(event) },
1797        ];
1798        this.component = component;
1799        this.modelElementResolver = modelElementResolver;
1800        this.unsyncedInputs = new UnsyncedInputContainer();
1801    }
1802    activate() {
1803        this.elementEventListeners.forEach(({ event, callback }) => {
1804            this.component.element.addEventListener(event, callback);
1805        });
1806    }
1807    deactivate() {
1808        this.elementEventListeners.forEach(({ event, callback }) => {
1809            this.component.element.removeEventListener(event, callback);
1810        });
1811    }
1812    markModelAsSynced(modelName) {
1813        this.unsyncedInputs.markModelAsSynced(modelName);
1814    }
1815    handleInputEvent(event) {
1816        const target = event.target;
1817        if (!target) {
1818            return;
1819        }
1820        this.updateModelFromElement(target);
1821    }
1822    updateModelFromElement(element) {
1823        if (!elementBelongsToThisComponent(element, this.component)) {
1824            return;
1825        }
1826        if (!(element instanceof HTMLElement)) {
1827            throw new Error('Could not update model for non HTMLElement');
1828        }
1829        const modelName = this.modelElementResolver.getModelName(element);
1830        this.unsyncedInputs.add(element, modelName);
1831    }
1832    getUnsyncedInputs() {
1833        return this.unsyncedInputs.allUnsyncedInputs();
1834    }
1835    getUnsyncedModels() {
1836        return Array.from(this.unsyncedInputs.getUnsyncedModelNames());
1837    }
1838    resetUnsyncedFields() {
1839        this.unsyncedInputs.resetUnsyncedFields();
1840    }
1841}
1842class UnsyncedInputContainer {
1843    constructor() {
1844        this.unsyncedNonModelFields = [];
1845        this.unsyncedModelNames = [];
1846        this.unsyncedModelFields = new Map();
1847    }
1848    add(element, modelName = null) {
1849        if (modelName) {
1850            this.unsyncedModelFields.set(modelName, element);
1851            if (!this.unsyncedModelNames.includes(modelName)) {
1852                this.unsyncedModelNames.push(modelName);
1853            }
1854            return;
1855        }
1856        this.unsyncedNonModelFields.push(element);
1857    }
1858    resetUnsyncedFields() {
1859        this.unsyncedModelFields.forEach((value, key) => {
1860            if (!this.unsyncedModelNames.includes(key)) {
1861                this.unsyncedModelFields.delete(key);
1862            }
1863        });
1864    }
1865    allUnsyncedInputs() {
1866        return [...this.unsyncedNonModelFields, ...this.unsyncedModelFields.values()];
1867    }
1868    markModelAsSynced(modelName) {
1869        const index = this.unsyncedModelNames.indexOf(modelName);
1870        if (index !== -1) {
1871            this.unsyncedModelNames.splice(index, 1);
1872        }
1873    }
1874    getUnsyncedModelNames() {
1875        return this.unsyncedModelNames;
1876    }
1877}
1878
1879function getDeepData(data, propertyPath) {
1880    const { currentLevelData, finalKey } = parseDeepData(data, propertyPath);
1881    if (currentLevelData === undefined) {
1882        return undefined;
1883    }
1884    return currentLevelData[finalKey];
1885}
1886const parseDeepData = (data, propertyPath) => {
1887    const finalData = JSON.parse(JSON.stringify(data));
1888    let currentLevelData = finalData;
1889    const parts = propertyPath.split('.');
1890    for (let i = 0; i < parts.length - 1; i++) {
1891        currentLevelData = currentLevelData[parts[i]];
1892    }
1893    const finalKey = parts[parts.length - 1];
1894    return {
1895        currentLevelData,
1896        finalData,
1897        finalKey,
1898        parts,
1899    };
1900};
1901
1902class ValueStore {
1903    constructor(props) {
1904        this.props = {};
1905        this.dirtyProps = {};
1906        this.pendingProps = {};
1907        this.updatedPropsFromParent = {};
1908        this.props = props;
1909    }
1910    get(name) {
1911        const normalizedName = normalizeModelName(name);
1912        if (this.dirtyProps[normalizedName] !== undefined) {
1913            return this.dirtyProps[normalizedName];
1914        }
1915        if (this.pendingProps[normalizedName] !== undefined) {
1916            return this.pendingProps[normalizedName];
1917        }
1918        if (this.props[normalizedName] !== undefined) {
1919            return this.props[normalizedName];
1920        }
1921        return getDeepData(this.props, normalizedName);
1922    }
1923    has(name) {
1924        return this.get(name) !== undefined;
1925    }
1926    set(name, value) {
1927        const normalizedName = normalizeModelName(name);
1928        if (this.get(normalizedName) === value) {
1929            return false;
1930        }
1931        this.dirtyProps[normalizedName] = value;
1932        return true;
1933    }
1934    getOriginalProps() {
1935        return { ...this.props };
1936    }
1937    getDirtyProps() {
1938        return { ...this.dirtyProps };
1939    }
1940    getUpdatedPropsFromParent() {
1941        return { ...this.updatedPropsFromParent };
1942    }
1943    flushDirtyPropsToPending() {
1944        this.pendingProps = { ...this.dirtyProps };
1945        this.dirtyProps = {};
1946    }
1947    reinitializeAllProps(props) {
1948        this.props = props;
1949        this.updatedPropsFromParent = {};
1950        this.pendingProps = {};
1951    }
1952    pushPendingPropsBackToDirty() {
1953        this.dirtyProps = { ...this.pendingProps, ...this.dirtyProps };
1954        this.pendingProps = {};
1955    }
1956    storeNewPropsFromParent(props) {
1957        let changed = false;
1958        for (const [key, value] of Object.entries(props)) {
1959            const currentValue = this.get(key);
1960            if (currentValue !== value) {
1961                changed = true;
1962            }
1963        }
1964        if (changed) {
1965            this.updatedPropsFromParent = props;
1966        }
1967        return changed;
1968    }
1969}
1970
1971class Component {
1972    constructor(element, name, props, listeners, id, backend, elementDriver) {
1973        this.fingerprint = '';
1974        this.defaultDebounce = 150;
1975        this.backendRequest = null;
1976        this.pendingActions = [];
1977        this.pendingFiles = {};
1978        this.isRequestPending = false;
1979        this.requestDebounceTimeout = null;
1980        this.element = element;
1981        this.name = name;
1982        this.backend = backend;
1983        this.elementDriver = elementDriver;
1984        this.id = id;
1985        this.listeners = new Map();
1986        listeners.forEach((listener) => {
1987            if (!this.listeners.has(listener.event)) {
1988                this.listeners.set(listener.event, []);
1989            }
1990            this.listeners.get(listener.event)?.push(listener.action);
1991        });
1992        this.valueStore = new ValueStore(props);
1993        this.unsyncedInputsTracker = new UnsyncedInputsTracker(this, elementDriver);
1994        this.hooks = new HookManager();
1995        this.resetPromise();
1996        this.externalMutationTracker = new ExternalMutationTracker(this.element, (element) => elementBelongsToThisComponent(element, this));
1997        this.externalMutationTracker.start();
1998    }
1999    addPlugin(plugin) {
2000        plugin.attachToComponent(this);
2001    }
2002    connect() {
2003        registerComponent(this);
2004        this.hooks.triggerHook('connect', this);
2005        this.unsyncedInputsTracker.activate();
2006        this.externalMutationTracker.start();
2007    }
2008    disconnect() {
2009        unregisterComponent(this);
2010        this.hooks.triggerHook('disconnect', this);
2011        this.clearRequestDebounceTimeout();
2012        this.unsyncedInputsTracker.deactivate();
2013        this.externalMutationTracker.stop();
2014    }
2015    on(hookName, callback) {
2016        this.hooks.register(hookName, callback);
2017    }
2018    off(hookName, callback) {
2019        this.hooks.unregister(hookName, callback);
2020    }
2021    set(model, value, reRender = false, debounce = false) {
2022        const promise = this.nextRequestPromise;
2023        const modelName = normalizeModelName(model);
2024        if (!this.valueStore.has(modelName)) {
2025            throw new Error(`Invalid model name "${model}".`);
2026        }
2027        const isChanged = this.valueStore.set(modelName, value);
2028        this.hooks.triggerHook('model:set', model, value, this);
2029        this.unsyncedInputsTracker.markModelAsSynced(modelName);
2030        if (reRender && isChanged) {
2031            this.debouncedStartRequest(debounce);
2032        }
2033        return promise;
2034    }
2035    getData(model) {
2036        const modelName = normalizeModelName(model);
2037        if (!this.valueStore.has(modelName)) {
2038            throw new Error(`Invalid model "${model}".`);
2039        }
2040        return this.valueStore.get(modelName);
2041    }
2042    action(name, args = {}, debounce = false) {
2043        const promise = this.nextRequestPromise;
2044        this.pendingActions.push({
2045            name,
2046            args,
2047        });
2048        this.debouncedStartRequest(debounce);
2049        return promise;
2050    }
2051    files(key, input) {
2052        this.pendingFiles[key] = input;
2053    }
2054    render() {
2055        const promise = this.nextRequestPromise;
2056        this.tryStartingRequest();
2057        return promise;
2058    }
2059    getUnsyncedModels() {
2060        return this.unsyncedInputsTracker.getUnsyncedModels();
2061    }
2062    emit(name, data, onlyMatchingComponentsNamed = null) {
2063        this.performEmit(name, data, false, onlyMatchingComponentsNamed);
2064    }
2065    emitUp(name, data, onlyMatchingComponentsNamed = null) {
2066        this.performEmit(name, data, true, onlyMatchingComponentsNamed);
2067    }
2068    emitSelf(name, data) {
2069        this.doEmit(name, data);
2070    }
2071    performEmit(name, data, emitUp, matchingName) {
2072        const components = findComponents(this, emitUp, matchingName);
2073        components.forEach((component) => {
2074            component.doEmit(name, data);
2075        });
2076    }
2077    doEmit(name, data) {
2078        if (!this.listeners.has(name)) {
2079            return;
2080        }
2081        const actions = this.listeners.get(name) || [];
2082        actions.forEach((action) => {
2083            this.action(action, data, 1);
2084        });
2085    }
2086    isTurboEnabled() {
2087        return typeof Turbo !== 'undefined' && !this.element.closest('[data-turbo="false"]');
2088    }
2089    tryStartingRequest() {
2090        if (!this.backendRequest) {
2091            this.performRequest();
2092            return;
2093        }
2094        this.isRequestPending = true;
2095    }
2096    performRequest() {
2097        const thisPromiseResolve = this.nextRequestPromiseResolve;
2098        this.resetPromise();
2099        this.unsyncedInputsTracker.resetUnsyncedFields();
2100        const filesToSend = {};
2101        for (const [key, value] of Object.entries(this.pendingFiles)) {
2102            if (value.files) {
2103                filesToSend[key] = value.files;
2104            }
2105        }
2106        const requestConfig = {
2107            props: this.valueStore.getOriginalProps(),
2108            actions: this.pendingActions,
2109            updated: this.valueStore.getDirtyProps(),
2110            children: {},
2111            updatedPropsFromParent: this.valueStore.getUpdatedPropsFromParent(),
2112            files: filesToSend,
2113        };
2114        this.hooks.triggerHook('request:started', requestConfig);
2115        this.backendRequest = this.backend.makeRequest(requestConfig.props, requestConfig.actions, requestConfig.updated, requestConfig.children, requestConfig.updatedPropsFromParent, requestConfig.files);
2116        this.hooks.triggerHook('loading.state:started', this.element, this.backendRequest);
2117        this.pendingActions = [];
2118        this.valueStore.flushDirtyPropsToPending();
2119        this.isRequestPending = false;
2120        this.backendRequest.promise.then(async (response) => {
2121            const backendResponse = new BackendResponse(response);
2122            const html = await backendResponse.getBody();
2123            for (const input of Object.values(this.pendingFiles)) {
2124                input.value = '';
2125            }
2126            const headers = backendResponse.response.headers;
2127            if (!headers.get('Content-Type')?.includes('application/vnd.live-component+html') &&
2128                !headers.get('X-Live-Redirect')) {
2129                const controls = { displayError: true };
2130                this.valueStore.pushPendingPropsBackToDirty();
2131                this.hooks.triggerHook('response:error', backendResponse, controls);
2132                if (controls.displayError) {
2133                    this.renderError(html);
2134                }
2135                this.backendRequest = null;
2136                thisPromiseResolve(backendResponse);
2137                return response;
2138            }
2139            this.processRerender(html, backendResponse);
2140            this.backendRequest = null;
2141            thisPromiseResolve(backendResponse);
2142            if (this.isRequestPending) {
2143                this.isRequestPending = false;
2144                this.performRequest();
2145            }
2146            return response;
2147        });
2148    }
2149    processRerender(html, backendResponse) {
2150        const controls = { shouldRender: true };
2151        this.hooks.triggerHook('render:started', html, backendResponse, controls);
2152        if (!controls.shouldRender) {
2153            return;
2154        }
2155        if (backendResponse.response.headers.get('Location')) {
2156            if (this.isTurboEnabled()) {
2157                Turbo.visit(backendResponse.response.headers.get('Location'));
2158            }
2159            else {
2160                window.location.href = backendResponse.response.headers.get('Location') || '';
2161            }
2162            return;
2163        }
2164        this.hooks.triggerHook('loading.state:finished', this.element);
2165        const modifiedModelValues = {};
2166        Object.keys(this.valueStore.getDirtyProps()).forEach((modelName) => {
2167            modifiedModelValues[modelName] = this.valueStore.get(modelName);
2168        });
2169        let newElement;
2170        try {
2171            newElement = htmlToElement(html);
2172            if (!newElement.matches('[data-controller~=live]')) {
2173                throw new Error('A live component template must contain a single root controller element.');
2174            }
2175        }
2176        catch (error) {
2177            console.error(`There was a problem with the '${this.name}' component HTML returned:`, {
2178                id: this.id,
2179            });
2180            throw error;
2181        }
2182        this.externalMutationTracker.handlePendingChanges();
2183        this.externalMutationTracker.stop();
2184        executeMorphdom(this.element, newElement, this.unsyncedInputsTracker.getUnsyncedInputs(), (element) => getValueFromElement(element, this.valueStore), this.externalMutationTracker);
2185        this.externalMutationTracker.start();
2186        const newProps = this.elementDriver.getComponentProps();
2187        this.valueStore.reinitializeAllProps(newProps);
2188        const eventsToEmit = this.elementDriver.getEventsToEmit();
2189        const browserEventsToDispatch = this.elementDriver.getBrowserEventsToDispatch();
2190        Object.keys(modifiedModelValues).forEach((modelName) => {
2191            this.valueStore.set(modelName, modifiedModelValues[modelName]);
2192        });
2193        eventsToEmit.forEach(({ event, data, target, componentName }) => {
2194            if (target === 'up') {
2195                this.emitUp(event, data, componentName);
2196                return;
2197            }
2198            if (target === 'self') {
2199                this.emitSelf(event, data);
2200                return;
2201            }
2202            this.emit(event, data, componentName);
2203        });
2204        browserEventsToDispatch.forEach(({ event, payload }) => {
2205            this.element.dispatchEvent(new CustomEvent(event, {
2206                detail: payload,
2207                bubbles: true,
2208            }));
2209        });
2210        this.hooks.triggerHook('render:finished', this);
2211    }
2212    calculateDebounce(debounce) {
2213        if (debounce === true) {
2214            return this.defaultDebounce;
2215        }
2216        if (debounce === false) {
2217            return 0;
2218        }
2219        return debounce;
2220    }
2221    clearRequestDebounceTimeout() {
2222        if (this.requestDebounceTimeout) {
2223            clearTimeout(this.requestDebounceTimeout);
2224            this.requestDebounceTimeout = null;
2225        }
2226    }
2227    debouncedStartRequest(debounce) {
2228        this.clearRequestDebounceTimeout();
2229        this.requestDebounceTimeout = window.setTimeout(() => {
2230            this.render();
2231        }, this.calculateDebounce(debounce));
2232    }
2233    renderError(html) {
2234        let modal = document.getElementById('live-component-error');
2235        if (modal) {
2236            modal.innerHTML = '';
2237        }
2238        else {
2239            modal = document.createElement('div');
2240            modal.id = 'live-component-error';
2241            modal.style.padding = '50px';
2242            modal.style.backgroundColor = 'rgba(0, 0, 0, .5)';
2243            modal.style.zIndex = '100000';
2244            modal.style.position = 'fixed';
2245            modal.style.top = '0px';
2246            modal.style.bottom = '0px';
2247            modal.style.left = '0px';
2248            modal.style.right = '0px';
2249            modal.style.display = 'flex';
2250            modal.style.flexDirection = 'column';
2251        }
2252        const iframe = document.createElement('iframe');
2253        iframe.style.borderRadius = '5px';
2254        iframe.style.flexGrow = '1';
2255        modal.appendChild(iframe);
2256        document.body.prepend(modal);
2257        document.body.style.overflow = 'hidden';
2258        if (iframe.contentWindow) {
2259            iframe.contentWindow.document.open();
2260            iframe.contentWindow.document.write(html);
2261            iframe.contentWindow.document.close();
2262        }
2263        const closeModal = (modal) => {
2264            if (modal) {
2265                modal.outerHTML = '';
2266            }
2267            document.body.style.overflow = 'visible';
2268        };
2269        modal.addEventListener('click', () => closeModal(modal));
2270        modal.setAttribute('tabindex', '0');
2271        modal.addEventListener('keydown', (e) => {
2272            if (e.key === 'Escape') {
2273                closeModal(modal);
2274            }
2275        });
2276        modal.focus();
2277    }
2278    resetPromise() {
2279        this.nextRequestPromise = new Promise((resolve) => {
2280            this.nextRequestPromiseResolve = resolve;
2281        });
2282    }
2283    _updateFromParentProps(props) {
2284        const isChanged = this.valueStore.storeNewPropsFromParent(props);
2285        if (isChanged) {
2286            this.render();
2287        }
2288    }
2289}
2290function proxifyComponent(component) {
2291    return new Proxy(component, {
2292        get(component, prop) {
2293            if (prop in component || typeof prop !== 'string') {
2294                if (typeof component[prop] === 'function') {
2295                    const callable = component[prop];
2296                    return (...args) => {
2297                        return callable.apply(component, args);
2298                    };
2299                }
2300                return Reflect.get(component, prop);
2301            }
2302            if (component.valueStore.has(prop)) {
2303                return component.getData(prop);
2304            }
2305            return (args) => {
2306                return component.action.apply(component, [prop, args]);
2307            };
2308        },
2309        set(target, property, value) {
2310            if (property in target) {
2311                target[property] = value;
2312                return true;
2313            }
2314            target.set(property, value);
2315            return true;
2316        },
2317    });
2318}
2319
2320class StimulusElementDriver {
2321    constructor(controller) {
2322        this.controller = controller;
2323    }
2324    getModelName(element) {
2325        const modelDirective = getModelDirectiveFromElement(element, false);
2326        if (!modelDirective) {
2327            return null;
2328        }
2329        return modelDirective.action;
2330    }
2331    getComponentProps() {
2332        return this.controller.propsValue;
2333    }
2334    getEventsToEmit() {
2335        return this.controller.eventsToEmitValue;
2336    }
2337    getBrowserEventsToDispatch() {
2338        return this.controller.eventsToDispatchValue;
2339    }
2340}
2341
2342function getModelBinding (modelDirective) {
2343    let shouldRender = true;
2344    let targetEventName = null;
2345    let debounce = false;
2346    modelDirective.modifiers.forEach((modifier) => {
2347        switch (modifier.name) {
2348            case 'on':
2349                if (!modifier.value) {
2350                    throw new Error(`The "on" modifier in ${modelDirective.getString()} requires a value - e.g. on(change).`);
2351                }
2352                if (!['input', 'change'].includes(modifier.value)) {
2353                    throw new Error(`The "on" modifier in ${modelDirective.getString()} only accepts the arguments "input" or "change".`);
2354                }
2355                targetEventName = modifier.value;
2356                break;
2357            case 'norender':
2358                shouldRender = false;
2359                break;
2360            case 'debounce':
2361                debounce = modifier.value ? Number.parseInt(modifier.value) : true;
2362                break;
2363            default:
2364                throw new Error(`Unknown modifier "${modifier.name}" in data-model="${modelDirective.getString()}".`);
2365        }
2366    });
2367    const [modelName, innerModelName] = modelDirective.action.split(':');
2368    return {
2369        modelName,
2370        innerModelName: innerModelName || null,
2371        shouldRender,
2372        debounce,
2373        targetEventName,
2374    };
2375}
2376
2377class ChildComponentPlugin {
2378    constructor(component) {
2379        this.parentModelBindings = [];
2380        this.component = component;
2381        const modelDirectives = getAllModelDirectiveFromElements(this.component.element);
2382        this.parentModelBindings = modelDirectives.map(getModelBinding);
2383    }
2384    attachToComponent(component) {
2385        component.on('request:started', (requestData) => {
2386            requestData.children = this.getChildrenFingerprints();
2387        });
2388        component.on('model:set', (model, value) => {
2389            this.notifyParentModelChange(model, value);
2390        });
2391    }
2392    getChildrenFingerprints() {
2393        const fingerprints = {};
2394        this.getChildren().forEach((child) => {
2395            if (!child.id) {
2396                throw new Error('missing id');
2397            }
2398            fingerprints[child.id] = {
2399                fingerprint: child.fingerprint,
2400                tag: child.element.tagName.toLowerCase(),
2401            };
2402        });
2403        return fingerprints;
2404    }
2405    notifyParentModelChange(modelName, value) {
2406        const parentComponent = findParent(this.component);
2407        if (!parentComponent) {
2408            return;
2409        }
2410        this.parentModelBindings.forEach((modelBinding) => {
2411            const childModelName = modelBinding.innerModelName || 'value';
2412            if (childModelName !== modelName) {
2413                return;
2414            }
2415            parentComponent.set(modelBinding.modelName, value, modelBinding.shouldRender, modelBinding.debounce);
2416        });
2417    }
2418    getChildren() {
2419        return findChildren(this.component);
2420    }
2421}
2422
2423class LazyPlugin {
2424    constructor() {
2425        this.intersectionObserver = null;
2426    }
2427    attachToComponent(component) {
2428        if ('lazy' !== component.element.attributes.getNamedItem('loading')?.value) {
2429            return;
2430        }
2431        component.on('connect', () => {
2432            this.getObserver().observe(component.element);
2433        });
2434        component.on('disconnect', () => {
2435            this.intersectionObserver?.unobserve(component.element);
2436        });
2437    }
2438    getObserver() {
2439        if (!this.intersectionObserver) {
2440            this.intersectionObserver = new IntersectionObserver((entries, observer) => {
2441                entries.forEach((entry) => {
2442                    if (entry.isIntersecting) {
2443                        entry.target.dispatchEvent(new CustomEvent('live:appear'));
2444                        observer.unobserve(entry.target);
2445                    }
2446                });
2447            });
2448        }
2449        return this.intersectionObserver;
2450    }
2451}
2452
2453class LoadingPlugin {
2454    attachToComponent(component) {
2455        component.on('loading.state:started', (element, request) => {
2456            this.startLoading(component, element, request);
2457        });
2458        component.on('loading.state:finished', (element) => {
2459            this.finishLoading(component, element);
2460        });
2461        this.finishLoading(component, component.element);
2462    }
2463    startLoading(component, targetElement, backendRequest) {
2464        this.handleLoadingToggle(component, true, targetElement, backendRequest);
2465    }
2466    finishLoading(component, targetElement) {
2467        this.handleLoadingToggle(component, false, targetElement, null);
2468    }
2469    handleLoadingToggle(component, isLoading, targetElement, backendRequest) {
2470        if (isLoading) {
2471            this.addAttributes(targetElement, ['busy']);
2472        }
2473        else {
2474            this.removeAttributes(targetElement, ['busy']);
2475        }
2476        this.getLoadingDirectives(component, targetElement).forEach(({ element, directives }) => {
2477            if (isLoading) {
2478                this.addAttributes(element, ['data-live-is-loading']);
2479            }
2480            else {
2481                this.removeAttributes(element, ['data-live-is-loading']);
2482            }
2483            directives.forEach((directive) => {
2484                this.handleLoadingDirective(element, isLoading, directive, backendRequest);
2485            });
2486        });
2487    }
2488    handleLoadingDirective(element, isLoading, directive, backendRequest) {
2489        const finalAction = parseLoadingAction(directive.action, isLoading);
2490        const targetedActions = [];
2491        const targetedModels = [];
2492        let delay = 0;
2493        const validModifiers = new Map();
2494        validModifiers.set('delay', (modifier) => {
2495            if (!isLoading) {
2496                return;
2497            }
2498            delay = modifier.value ? Number.parseInt(modifier.value) : 200;
2499        });
2500        validModifiers.set('action', (modifier) => {
2501            if (!modifier.value) {
2502                throw new Error(`The "action" in data-loading must have an action name - e.g. action(foo). It's missing for "${directive.getString()}"`);
2503            }
2504            targetedActions.push(modifier.value);
2505        });
2506        validModifiers.set('model', (modifier) => {
2507            if (!modifier.value) {
2508                throw new Error(`The "model" in data-loading must have an action name - e.g. model(foo). It's missing for "${directive.getString()}"`);
2509            }
2510            targetedModels.push(modifier.value);
2511        });
2512        directive.modifiers.forEach((modifier) => {
2513            if (validModifiers.has(modifier.name)) {
2514                const callable = validModifiers.get(modifier.name) ?? (() => { });
2515                callable(modifier);
2516                return;
2517            }
2518            throw new Error(`Unknown modifier "${modifier.name}" used in data-loading="${directive.getString()}". Available modifiers are: ${Array.from(validModifiers.keys()).join(', ')}.`);
2519        });
2520        if (isLoading &&
2521            targetedActions.length > 0 &&
2522            backendRequest &&
2523            !backendRequest.containsOneOfActions(targetedActions)) {
2524            return;
2525        }
2526        if (isLoading &&
2527            targetedModels.length > 0 &&
2528            backendRequest &&
2529            !backendRequest.areAnyModelsUpdated(targetedModels)) {
2530            return;
2531        }
2532        let loadingDirective;
2533        switch (finalAction) {
2534            case 'show':
2535                loadingDirective = () => this.showElement(element);
2536                break;
2537            case 'hide':
2538                loadingDirective = () => this.hideElement(element);
2539                break;
2540            case 'addClass':
2541                loadingDirective = () => this.addClass(element, directive.args);
2542                break;
2543            case 'removeClass':
2544                loadingDirective = () => this.removeClass(element, directive.args);
2545                break;
2546            case 'addAttribute':
2547                loadingDirective = () => this.addAttributes(element, directive.args);
2548                break;
2549            case 'removeAttribute':
2550                loadingDirective = () => this.removeAttributes(element, directive.args);
2551                break;
2552            default:
2553                throw new Error(`Unknown data-loading action "${finalAction}"`);
2554        }
2555        if (delay) {
2556            window.setTimeout(() => {
2557                if (backendRequest && !backendRequest.isResolved) {
2558                    loadingDirective();
2559                }
2560            }, delay);
2561            return;
2562        }
2563        loadingDirective();
2564    }
2565    getLoadingDirectives(component, element) {
2566        const loadingDirectives = [];
2567        let matchingElements = [...Array.from(element.querySelectorAll('[data-loading]'))];
2568        matchingElements = matchingElements.filter((elt) => elementBelongsToThisComponent(elt, component));
2569        if (element.hasAttribute('data-loading')) {
2570            matchingElements = [element, ...matchingElements];
2571        }
2572        matchingElements.forEach((element) => {
2573            if (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) {
2574                throw new Error('Invalid Element Type');
2575            }
2576            const directives = parseDirectives(element.dataset.loading || 'show');
2577            loadingDirectives.push({
2578                element,
2579                directives,
2580            });
2581        });
2582        return loadingDirectives;
2583    }
2584    showElement(element) {
2585        element.style.display = 'revert';
2586    }
2587    hideElement(element) {
2588        element.style.display = 'none';
2589    }
2590    addClass(element, classes) {
2591        element.classList.add(...combineSpacedArray(classes));
2592    }
2593    removeClass(element, classes) {
2594        element.classList.remove(...combineSpacedArray(classes));
2595        if (element.classList.length === 0) {
2596            element.removeAttribute('class');
2597        }
2598    }
2599    addAttributes(element, attributes) {
2600        attributes.forEach((attribute) => {
2601            element.setAttribute(attribute, '');
2602        });
2603    }
2604    removeAttributes(element, attributes) {
2605        attributes.forEach((attribute) => {
2606            element.removeAttribute(attribute);
2607        });
2608    }
2609}
2610const parseLoadingAction = (action, isLoading) => {
2611    switch (action) {
2612        case 'show':
2613            return isLoading ? 'show' : 'hide';
2614        case 'hide':
2615            return isLoading ? 'hide' : 'show';
2616        case 'addClass':
2617            return isLoading ? 'addClass' : 'removeClass';
2618        case 'removeClass':
2619            return isLoading ? 'removeClass' : 'addClass';
2620        case 'addAttribute':
2621            return isLoading ? 'addAttribute' : 'removeAttribute';
2622        case 'removeAttribute':
2623            return isLoading ? 'removeAttribute' : 'addAttribute';
2624    }
2625    throw new Error(`Unknown data-loading action "${action}"`);
2626};
2627
2628class PageUnloadingPlugin {
2629    constructor() {
2630        this.isConnected = false;
2631    }
2632    attachToComponent(component) {
2633        component.on('render:started', (html, response, controls) => {
2634            if (!this.isConnected) {
2635                controls.shouldRender = false;
2636            }
2637        });
2638        component.on('connect', () => {
2639            this.isConnected = true;
2640        });
2641        component.on('disconnect', () => {
2642            this.isConnected = false;
2643        });
2644    }
2645}
2646
2647class PollingDirector {
2648    constructor(component) {
2649        this.isPollingActive = true;
2650        this.pollingIntervals = [];
2651        this.component = component;
2652    }
2653    addPoll(actionName, duration) {
2654        this.polls.push({ actionName, duration });
2655        if (this.isPollingActive) {
2656            this.initiatePoll(actionName, duration);
2657        }
2658    }
2659    startAllPolling() {
2660        if (this.isPollingActive) {
2661            return;
2662        }
2663        this.isPollingActive = true;
2664        this.polls.forEach(({ actionName, duration }) => {
2665            this.initiatePoll(actionName, duration);
2666        });
2667    }
2668    stopAllPolling() {
2669        this.isPollingActive = false;
2670        this.pollingIntervals.forEach((interval) => {
2671            clearInterval(interval);
2672        });
2673    }
2674    clearPolling() {
2675        this.stopAllPolling();
2676        this.polls = [];
2677        this.startAllPolling();
2678    }
2679    initiatePoll(actionName, duration) {
2680        let callback;
2681        if (actionName === '$render') {
2682            callback = () => {
2683                this.component.render();
2684            };
2685        }
2686        else {
2687            callback = () => {
2688                this.component.action(actionName, {}, 0);
2689            };
2690        }
2691        const timer = window.setInterval(() => {
2692            callback();
2693        }, duration);
2694        this.pollingIntervals.push(timer);
2695    }
2696}
2697
2698class PollingPlugin {
2699    attachToComponent(component) {
2700        this.element = component.element;
2701        this.pollingDirector = new PollingDirector(component);
2702        this.initializePolling();
2703        component.on('connect', () => {
2704            this.pollingDirector.startAllPolling();
2705        });
2706        component.on('disconnect', () => {
2707            this.pollingDirector.stopAllPolling();
2708        });
2709        component.on('render:finished', () => {
2710            this.initializePolling();
2711        });
2712    }
2713    addPoll(actionName, duration) {
2714        this.pollingDirector.addPoll(actionName, duration);
2715    }
2716    clearPolling() {
2717        this.pollingDirector.clearPolling();
2718    }
2719    initializePolling() {
2720        this.clearPolling();
2721        if (this.element.dataset.poll === undefined) {
2722            return;
2723        }
2724        const rawPollConfig = this.element.dataset.poll;
2725        const directives = parseDirectives(rawPollConfig || '$render');
2726        directives.forEach((directive) => {
2727            let duration = 2000;
2728            directive.modifiers.forEach((modifier) => {
2729                switch (modifier.name) {
2730                    case 'delay':
2731                        if (modifier.value) {
2732                            duration = Number.parseInt(modifier.value);
2733                        }
2734                        break;
2735                    default:
2736                        console.warn(`Unknown modifier "${modifier.name}" in data-poll "${rawPollConfig}".`);
2737                }
2738            });
2739            this.addPoll(directive.action, duration);
2740        });
2741    }
2742}
2743
2744function isValueEmpty(value) {
2745    if (null === value || value === '' || undefined === value || (Array.isArray(value) && value.length === 0)) {
2746        return true;
2747    }
2748    if (typeof value !== 'object') {
2749        return false;
2750    }
2751    for (const key of Object.keys(value)) {
2752        if (!isValueEmpty(value[key])) {
2753            return false;
2754        }
2755    }
2756    return true;
2757}
2758function toQueryString(data) {
2759    const buildQueryStringEntries = (data, entries = {}, baseKey = '') => {
2760        Object.entries(data).forEach(([iKey, iValue]) => {
2761            const key = baseKey === '' ? iKey : `${baseKey}[${iKey}]`;
2762            if ('' === baseKey && isValueEmpty(iValue)) {
2763                entries[key] = '';
2764            }
2765            else if (null !== iValue) {
2766                if (typeof iValue === 'object') {
2767                    entries = { ...entries, ...buildQueryStringEntries(iValue, entries, key) };
2768                }
2769                else {
2770                    entries[key] = encodeURIComponent(iValue)
2771                        .replace(/%20/g, '+')
2772                        .replace(/%2C/g, ',');
2773                }
2774            }
2775        });
2776        return entries;
2777    };
2778    const entries = buildQueryStringEntries(data);
2779    return Object.entries(entries)
2780        .map(([key, value]) => `${key}=${value}`)
2781        .join('&');
2782}
2783function fromQueryString(search) {
2784    search = search.replace('?', '');
2785    if (search === '')
2786        return {};
2787    const insertDotNotatedValueIntoData = (key, value, data) => {
2788        const [first, second, ...rest] = key.split('.');
2789        if (!second) {
2790            data[key] = value;
2791            return value;
2792        }
2793        if (data[first] === undefined) {
2794            data[first] = Number.isNaN(Number.parseInt(second)) ? {} : [];
2795        }
2796        insertDotNotatedValueIntoData([second, ...rest].join('.'), value, data[first]);
2797    };
2798    const entries = search.split('&').map((i) => i.split('='));
2799    const data = {};
2800    entries.forEach(([key, value]) => {
2801        value = decodeURIComponent(value.replace(/\+/g, '%20'));
2802        if (!key.includes('[')) {
2803            data[key] = value;
2804        }
2805        else {
2806            if ('' === value)
2807                return;
2808            const dotNotatedKey = key.replace(/\[/g, '.').replace(/]/g, '');
2809            insertDotNotatedValueIntoData(dotNotatedKey, value, data);
2810        }
2811    });
2812    return data;
2813}
2814class UrlUtils extends URL {
2815    has(key) {
2816        const data = this.getData();
2817        return Object.keys(data).includes(key);
2818    }
2819    set(key, value) {
2820        const data = this.getData();
2821        data[key] = value;
2822        this.setData(data);
2823    }
2824    get(key) {
2825        return this.getData()[key];
2826    }
2827    remove(key) {
2828        const data = this.getData();
2829        delete data[key];
2830        this.setData(data);
2831    }
2832    getData() {
2833        if (!this.search) {
2834            return {};
2835        }
2836        return fromQueryString(this.search);
2837    }
2838    setData(data) {
2839        this.search = toQueryString(data);
2840    }
2841}
2842class HistoryStrategy {
2843    static replace(url) {
2844        history.replaceState(history.state, '', url);
2845    }
2846}
2847
2848class QueryStringPlugin {
2849    constructor(mapping) {
2850        this.mapping = mapping;
2851    }
2852    attachToComponent(component) {
2853        component.on('render:finished', (component) => {
2854            const urlUtils = new UrlUtils(window.location.href);
2855            const currentUrl = urlUtils.toString();
2856            Object.entries(this.mapping).forEach(([prop, mapping]) => {
2857                const value = component.valueStore.get(prop);
2858                urlUtils.set(mapping.name, value);
2859            });
2860            if (currentUrl !== urlUtils.toString()) {
2861                HistoryStrategy.replace(urlUtils);
2862            }
2863        });
2864    }
2865}
2866
2867class SetValueOntoModelFieldsPlugin {
2868    attachToComponent(component) {
2869        this.synchronizeValueOfModelFields(component);
2870        component.on('render:finished', () => {
2871            this.synchronizeValueOfModelFields(component);
2872        });
2873    }
2874    synchronizeValueOfModelFields(component) {
2875        component.element.querySelectorAll('[data-model]').forEach((element) => {
2876            if (!(element instanceof HTMLElement)) {
2877                throw new Error('Invalid element using data-model.');
2878            }
2879            if (element instanceof HTMLFormElement) {
2880                return;
2881            }
2882            if (!elementBelongsToThisComponent(element, component)) {
2883                return;
2884            }
2885            const modelDirective = getModelDirectiveFromElement(element);
2886            if (!modelDirective) {
2887                return;
2888            }
2889            const modelName = modelDirective.action;
2890            if (component.getUnsyncedModels().includes(modelName)) {
2891                return;
2892            }
2893            if (component.valueStore.has(modelName)) {
2894                setValueOnElement(element, component.valueStore.get(modelName));
2895            }
2896            if (element instanceof HTMLSelectElement && !element.multiple) {
2897                component.valueStore.set(modelName, getValueFromElement(element, component.valueStore));
2898            }
2899        });
2900    }
2901}
2902
2903class ValidatedFieldsPlugin {
2904    attachToComponent(component) {
2905        component.on('model:set', (modelName) => {
2906            this.handleModelSet(modelName, component.valueStore);
2907        });
2908    }
2909    handleModelSet(modelName, valueStore) {
2910        if (valueStore.has('validatedFields')) {
2911            const validatedFields = [...valueStore.get('validatedFields')];
2912            if (!validatedFields.includes(modelName)) {
2913                validatedFields.push(modelName);
2914            }
2915            valueStore.set('validatedFields', validatedFields);
2916        }
2917    }
2918}
2919
2920class LiveControllerDefault extends Controller {
2921    constructor() {
2922        super(...arguments);
2923        this.pendingActionTriggerModelElement = null;
2924        this.elementEventListeners = [
2925            { event: 'input', callback: (event) => this.handleInputEvent(event) },
2926            { event: 'change', callback: (event) => this.handleChangeEvent(event) },
2927        ];
2928        this.pendingFiles = {};
2929    }
2930    initialize() {
2931        this.mutationObserver = new MutationObserver(this.onMutations.bind(this));
2932        this.createComponent();
2933    }
2934    connect() {
2935        this.connectComponent();
2936        this.mutationObserver.observe(this.element, {
2937            attributes: true,
2938        });
2939    }
2940    disconnect() {
2941        this.disconnectComponent();
2942        this.mutationObserver.disconnect();
2943    }
2944    update(event) {
2945        if (event.type === 'input' || event.type === 'change') {
2946            throw new Error(`Since LiveComponents 2.3, you no longer need data-action="live#update" on form elements. Found on element: ${getElementAsTagText(event.currentTarget)}`);
2947        }
2948        this.updateModelFromElementEvent(event.currentTarget, null);
2949    }
2950    action(event) {
2951        const params = event.params;
2952        if (!params.action) {
2953            throw new Error(`No action name provided on element: ${getElementAsTagText(event.currentTarget)}. Did you forget to add the "data-live-action-param" attribute?`);
2954        }
2955        const rawAction = params.action;
2956        const actionArgs = { ...params };
2957        delete actionArgs.action;
2958        const directives = parseDirectives(rawAction);
2959        let debounce = false;
2960        directives.forEach((directive) => {
2961            let pendingFiles = {};
2962            const validModifiers = new Map();
2963            validModifiers.set('stop', () => {
2964                event.stopPropagation();
2965            });
2966            validModifiers.set('self', () => {
2967                if (event.target !== event.currentTarget) {
2968                    return;
2969                }
2970            });
2971            validModifiers.set('debounce', (modifier) => {
2972                debounce = modifier.value ? Number.parseInt(modifier.value) : true;
2973            });
2974            validModifiers.set('files', (modifier) => {
2975                if (!modifier.value) {
2976                    pendingFiles = this.pendingFiles;
2977                }
2978                else if (this.pendingFiles[modifier.value]) {
2979                    pendingFiles[modifier.value] = this.pendingFiles[modifier.value];
2980                }
2981            });
2982            directive.modifiers.forEach((modifier) => {
2983                if (validModifiers.has(modifier.name)) {
2984                    const callable = validModifiers.get(modifier.name) ?? (() => { });
2985                    callable(modifier);
2986                    return;
2987                }
2988                console.warn(`Unknown modifier ${modifier.name} in action "${rawAction}". Available modifiers are: ${Array.from(validModifiers.keys()).join(', ')}.`);
2989            });
2990            for (const [key, input] of Object.entries(pendingFiles)) {
2991                if (input.files) {
2992                    this.component.files(key, input);
2993                }
2994                delete this.pendingFiles[key];
2995            }
2996            this.component.action(directive.action, actionArgs, debounce);
2997            if (getModelDirectiveFromElement(event.currentTarget, false)) {
2998                this.pendingActionTriggerModelElement = event.currentTarget;
2999            }
3000        });
3001    }
3002    $render() {
3003        return this.component.render();
3004    }
3005    emit(event) {
3006        this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => {
3007            this.component.emit(name, data, nameMatch);
3008        });
3009    }
3010    emitUp(event) {
3011        this.getEmitDirectives(event).forEach(({ name, data, nameMatch }) => {
3012            this.component.emitUp(name, data, nameMatch);
3013        });
3014    }
3015    emitSelf(event) {
3016        this.getEmitDirectives(event).forEach(({ name, data }) => {
3017            this.component.emitSelf(name, data);
3018        });
3019    }
3020    $updateModel(model, value, shouldRender = true, debounce = true) {
3021        return this.component.set(model, value, shouldRender, debounce);
3022    }
3023    propsUpdatedFromParentValueChanged() {
3024        this.component._updateFromParentProps(this.propsUpdatedFromParentValue);
3025    }
3026    fingerprintValueChanged() {
3027        this.component.fingerprint = this.fingerprintValue;
3028    }
3029    getEmitDirectives(event) {
3030        const params = event.params;
3031        if (!params.event) {
3032            throw new Error(`No event name provided on element: ${getElementAsTagText(event.currentTarget)}. Did you forget to add the "data-live-event-param" attribute?`);
3033        }
3034        const eventInfo = params.event;
3035        const eventArgs = { ...params };
3036        delete eventArgs.event;
3037        const directives = parseDirectives(eventInfo);
3038        const emits = [];
3039        directives.forEach((directive) => {
3040            let nameMatch = null;
3041            directive.modifiers.forEach((modifier) => {
3042                switch (modifier.name) {
3043                    case 'name':
3044                        nameMatch = modifier.value;
3045                        break;
3046                    default:
3047                        throw new Error(`Unknown modifier ${modifier.name} in event "${eventInfo}".`);
3048                }
3049            });
3050            emits.push({
3051                name: directive.action,
3052                data: eventArgs,
3053                nameMatch,
3054            });
3055        });
3056        return emits;
3057    }
3058    createComponent() {
3059        const id = this.element.id || null;
3060        this.component = new Component(this.element, this.nameValue, this.propsValue, this.listenersValue, id, LiveControllerDefault.backendFactory(this), new StimulusElementDriver(this));
3061        this.proxiedComponent = proxifyComponent(this.component);
3062        Object.defineProperty(this.element, '__component', {
3063            value: this.proxiedComponent,
3064            writable: true,
3065        });
3066        if (this.hasDebounceValue) {
3067            this.component.defaultDebounce = this.debounceValue;
3068        }
3069        const plugins = [
3070            new LoadingPlugin(),
3071            new LazyPlugin(),
3072            new ValidatedFieldsPlugin(),
3073            new PageUnloadingPlugin(),
3074            new PollingPlugin(),
3075            new SetValueOntoModelFieldsPlugin(),
3076            new QueryStringPlugin(this.queryMappingValue),
3077            new ChildComponentPlugin(this.component),
3078        ];
3079        plugins.forEach((plugin) => {
3080            this.component.addPlugin(plugin);
3081        });
3082    }
3083    connectComponent() {
3084        this.component.connect();
3085        this.mutationObserver.observe(this.element, {
3086            attributes: true,
3087        });
3088        this.elementEventListeners.forEach(({ event, callback }) => {
3089            this.component.element.addEventListener(event, callback);
3090        });
3091        this.dispatchEvent('connect');
3092    }
3093    disconnectComponent() {
3094        this.component.disconnect();
3095        this.elementEventListeners.forEach(({ event, callback }) => {
3096            this.component.element.removeEventListener(event, callback);
3097        });
3098        this.dispatchEvent('disconnect');
3099    }
3100    handleInputEvent(event) {
3101        const target = event.target;
3102        if (!target) {
3103            return;
3104        }
3105        this.updateModelFromElementEvent(target, 'input');
3106    }
3107    handleChangeEvent(event) {
3108        const target = event.target;
3109        if (!target) {
3110            return;
3111        }
3112        this.updateModelFromElementEvent(target, 'change');
3113    }
3114    updateModelFromElementEvent(element, eventName) {
3115        if (!elementBelongsToThisComponent(element, this.component)) {
3116            return;
3117        }
3118        if (!(element instanceof HTMLElement)) {
3119            throw new Error('Could not update model for non HTMLElement');
3120        }
3121        if (element instanceof HTMLInputElement && element.type === 'file') {
3122            const key = element.name;
3123            if (element.files?.length) {
3124                this.pendingFiles[key] = element;
3125            }
3126            else if (this.pendingFiles[key]) {
3127                delete this.pendingFiles[key];
3128            }
3129        }
3130        const modelDirective = getModelDirectiveFromElement(element, false);
3131        if (!modelDirective) {
3132            return;
3133        }
3134        const modelBinding = getModelBinding(modelDirective);
3135        if (!modelBinding.targetEventName) {
3136            modelBinding.targetEventName = 'input';
3137        }
3138        if (this.pendingActionTriggerModelElement === element) {
3139            modelBinding.shouldRender = false;
3140        }
3141        if (eventName === 'change' && modelBinding.targetEventName === 'input') {
3142            modelBinding.targetEventName = 'change';
3143        }
3144        if (eventName && modelBinding.targetEventName !== eventName) {
3145            return;
3146        }
3147        if (false === modelBinding.debounce) {
3148            if (modelBinding.targetEventName === 'input') {
3149                modelBinding.debounce = true;
3150            }
3151            else {
3152                modelBinding.debounce = 0;
3153            }
3154        }
3155        const finalValue = getValueFromElement(element, this.component.valueStore);
3156        this.component.set(modelBinding.modelName, finalValue, modelBinding.shouldRender, modelBinding.debounce);
3157    }
3158    dispatchEvent(name, detail = {}, canBubble = true, cancelable = false) {
3159        detail.controller = this;
3160        detail.component = this.proxiedComponent;
3161        this.dispatch(name, { detail, prefix: 'live', cancelable, bubbles: canBubble });
3162    }
3163    onMutations(mutations) {
3164        mutations.forEach((mutation) => {
3165            if (mutation.type === 'attributes' &&
3166                mutation.attributeName === 'id' &&
3167                this.element.id !== this.component.id) {
3168                this.disconnectComponent();
3169                this.createComponent();
3170                this.connectComponent();
3171            }
3172        });
3173    }
3174}
3175LiveControllerDefault.values = {
3176    name: String,
3177    url: String,
3178    props: { type: Object, default: {} },
3179    propsUpdatedFromParent: { type: Object, default: {} },
3180    listeners: { type: Array, default: [] },
3181    eventsToEmit: { type: Array, default: [] },
3182    eventsToDispatch: { type: Array, default: [] },
3183    debounce: { type: Number, default: 150 },
3184    fingerprint: { type: String, default: '' },
3185    requestMethod: { type: String, default: 'post' },
3186    queryMapping: { type: Object, default: {} },
3187};
3188LiveControllerDefault.backendFactory = (controller) => new Backend(controller.urlValue, controller.requestMethodValue);
3189
3190export { Component, LiveControllerDefault as default, getComponent };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.