mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-29 01:22:07 +01:00
Create DialogController (w-dialog) & TeleportController (w-teleport)
This commit is contained in:
parent
a15f7d188b
commit
c556acef35
139
client/src/controllers/DialogController.test.js
Normal file
139
client/src/controllers/DialogController.test.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { Application } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
import A11yDialog from 'a11y-dialog';
|
||||||
|
import { DialogController } from './DialogController';
|
||||||
|
|
||||||
|
describe('DialogController', () => {
|
||||||
|
let application;
|
||||||
|
|
||||||
|
describe('basic behaviour', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
application?.stop();
|
||||||
|
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<section>
|
||||||
|
<div
|
||||||
|
id="dialog-container"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-controller="w-dialog"
|
||||||
|
data-action="w-dialog:hide->w-dialog#hide w-dialog:show->w-dialog#show"
|
||||||
|
>
|
||||||
|
<div role="document">
|
||||||
|
<div id="dialog-body" data-w-dialog-target="body">CONTENT</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>`;
|
||||||
|
|
||||||
|
application = new Application();
|
||||||
|
application.register('w-dialog', DialogController);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate the controlled element with the A11y library', async () => {
|
||||||
|
const listener = jest.fn();
|
||||||
|
document.addEventListener('w-dialog:ready', listener);
|
||||||
|
|
||||||
|
expect(listener).not.toHaveBeenCalled();
|
||||||
|
application.start();
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalled();
|
||||||
|
const { body, dialog } = listener.mock.calls[0][0].detail;
|
||||||
|
|
||||||
|
expect(body).toEqual(document.getElementById('dialog-body'));
|
||||||
|
expect(dialog).toBeInstanceOf(A11yDialog);
|
||||||
|
expect(dialog.$el).toEqual(document.getElementById('dialog-container'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support the ability to show and hide the dialog', async () => {
|
||||||
|
const shownListener = jest.fn();
|
||||||
|
document.addEventListener('w-dialog:shown', shownListener);
|
||||||
|
|
||||||
|
const hiddenListener = jest.fn();
|
||||||
|
document.addEventListener('w-dialog:hidden', hiddenListener);
|
||||||
|
|
||||||
|
application.start();
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(shownListener).not.toHaveBeenCalled();
|
||||||
|
expect(hiddenListener).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const dialog = document.getElementById('dialog-container');
|
||||||
|
|
||||||
|
// closed by default
|
||||||
|
expect(dialog.getAttribute('aria-hidden')).toEqual('true');
|
||||||
|
expect(document.documentElement.style.overflowY).toBe('');
|
||||||
|
|
||||||
|
// show the dialog manually
|
||||||
|
dialog.dispatchEvent(new CustomEvent('w-dialog:show'));
|
||||||
|
|
||||||
|
expect(dialog.getAttribute('aria-hidden')).toEqual(null);
|
||||||
|
expect(shownListener).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
detail: expect.objectContaining({
|
||||||
|
body: document.getElementById('dialog-body'),
|
||||||
|
dialog: expect.any(Object),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(hiddenListener).not.toHaveBeenCalled();
|
||||||
|
// add style to root element on shown by default
|
||||||
|
expect(document.documentElement.style.overflowY).toBe('hidden');
|
||||||
|
|
||||||
|
// hide the dialog manually
|
||||||
|
dialog.dispatchEvent(new CustomEvent('w-dialog:hide'));
|
||||||
|
|
||||||
|
expect(dialog.getAttribute('aria-hidden')).toEqual('true');
|
||||||
|
expect(shownListener).toHaveBeenCalledTimes(1);
|
||||||
|
expect(hiddenListener).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
detail: expect.objectContaining({
|
||||||
|
body: document.getElementById('dialog-body'),
|
||||||
|
dialog: expect.any(Object),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// reset style on root element when hidden by default
|
||||||
|
expect(document.documentElement.style.overflowY).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support the ability use a theme to avoid document style change', async () => {
|
||||||
|
const dialog = document.getElementById('dialog-container');
|
||||||
|
|
||||||
|
// adding a theme value
|
||||||
|
dialog.classList.add('w-dialog--floating');
|
||||||
|
dialog.setAttribute('data-w-dialog-theme-value', 'floating');
|
||||||
|
|
||||||
|
application.start();
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
// closed by default
|
||||||
|
expect(document.documentElement.style.overflowY).toBe('');
|
||||||
|
expect(dialog.getAttribute('aria-hidden')).toEqual('true');
|
||||||
|
|
||||||
|
const shownListener = jest.fn();
|
||||||
|
document.addEventListener('w-dialog:shown', shownListener);
|
||||||
|
|
||||||
|
const hiddenListener = jest.fn();
|
||||||
|
document.addEventListener('w-dialog:hidden', hiddenListener);
|
||||||
|
|
||||||
|
dialog.dispatchEvent(new CustomEvent('w-dialog:show'));
|
||||||
|
|
||||||
|
expect(dialog.getAttribute('aria-hidden')).toEqual(null);
|
||||||
|
expect(document.documentElement.style.overflowY).toBeFalsy();
|
||||||
|
expect(shownListener).toHaveBeenCalled();
|
||||||
|
|
||||||
|
dialog.dispatchEvent(new CustomEvent('w-dialog:hide'));
|
||||||
|
|
||||||
|
expect(dialog.getAttribute('aria-hidden')).toEqual('true');
|
||||||
|
expect(document.documentElement.style.overflowY).toBeFalsy();
|
||||||
|
expect(hiddenListener).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,44 +1,54 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
import A11yDialog from 'a11y-dialog';
|
import A11yDialog from 'a11y-dialog';
|
||||||
|
|
||||||
export const dialog = (
|
const FLOATING = 'floating';
|
||||||
dialogTemplates = document.querySelectorAll('[data-wagtail-dialog]'),
|
|
||||||
rootElement = document.body,
|
|
||||||
) => {
|
|
||||||
const dialogs = Array.from(dialogTemplates).map((template) => {
|
|
||||||
const html = document.documentElement;
|
|
||||||
const templateContent = template.content.firstElementChild;
|
|
||||||
|
|
||||||
const { dialogRootSelector, theme } = templateContent.dataset;
|
/**
|
||||||
const dialogRoot =
|
* Instantiates an a11y dialog on the controlled element.
|
||||||
(dialogRootSelector && rootElement.querySelector(dialogRootSelector)) ||
|
* Adds support for hide and show methods and blocking body
|
||||||
rootElement;
|
* scroll when the dialog is open.
|
||||||
dialogRoot.appendChild(templateContent);
|
*
|
||||||
|
* @example
|
||||||
|
* <div
|
||||||
|
* data-controller="w-dialog"
|
||||||
|
* data-w-dialog-theme-value="floating"
|
||||||
|
* >
|
||||||
|
* <div data-w-dialog-target="body"></div>
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
|
export class DialogController extends Controller<HTMLElement> {
|
||||||
|
static values = {
|
||||||
|
theme: { default: '', type: String },
|
||||||
|
};
|
||||||
|
|
||||||
const dialogTemplate = new A11yDialog(templateContent);
|
static targets = ['body'];
|
||||||
|
|
||||||
if (theme !== 'floating') {
|
declare dialog: A11yDialog;
|
||||||
// Prevent scrolling when dialog is open
|
declare readonly bodyTarget: HTMLElement;
|
||||||
dialogTemplate
|
declare readonly themeValue: string;
|
||||||
.on('show', () => {
|
|
||||||
html.style.overflowY = 'hidden';
|
|
||||||
})
|
|
||||||
.on('hide', () => {
|
|
||||||
html.style.overflowY = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach event listeners to the dialog root (element with id), so it's
|
connect() {
|
||||||
// possible to show/close the dialog somewhere else with no access to the
|
this.dialog = new A11yDialog(this.element);
|
||||||
// A11yDialog instance.
|
const detail = { body: this.bodyTarget, dialog: this.dialog };
|
||||||
templateContent.addEventListener('wagtail:show', () =>
|
const isFloating = this.themeValue === FLOATING;
|
||||||
dialogTemplate.show(),
|
this.dialog
|
||||||
);
|
.on('show', () => {
|
||||||
templateContent.addEventListener('wagtail:hide', () =>
|
if (!isFloating) document.documentElement.style.overflowY = 'hidden';
|
||||||
dialogTemplate.hide(),
|
this.dispatch('shown', { detail });
|
||||||
);
|
})
|
||||||
|
.on('hide', () => {
|
||||||
|
if (!isFloating) document.documentElement.style.overflowY = '';
|
||||||
|
this.dispatch('hidden', { detail });
|
||||||
|
});
|
||||||
|
this.dispatch('ready', { detail });
|
||||||
|
return this.dialog;
|
||||||
|
}
|
||||||
|
|
||||||
return dialogTemplate;
|
hide() {
|
||||||
});
|
this.dialog.hide();
|
||||||
|
}
|
||||||
|
|
||||||
return dialogs;
|
show() {
|
||||||
};
|
this.dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
161
client/src/controllers/TeleportController.test.js
Normal file
161
client/src/controllers/TeleportController.test.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { Application } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
import { TeleportController } from './TeleportController';
|
||||||
|
|
||||||
|
describe('TeleportController', () => {
|
||||||
|
let application;
|
||||||
|
|
||||||
|
describe('basic behaviour', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
application?.stop();
|
||||||
|
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<main>
|
||||||
|
<template id="template" data-controller="w-teleport">
|
||||||
|
<div id="content">Some content</div>
|
||||||
|
</template>
|
||||||
|
</main>`;
|
||||||
|
|
||||||
|
application = new Application();
|
||||||
|
application.register('w-teleport', TeleportController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the Template element content to the body and remove the template by default', async () => {
|
||||||
|
expect(document.querySelectorAll('template')).toHaveLength(1);
|
||||||
|
expect(document.getElementById('content')).toBeNull();
|
||||||
|
|
||||||
|
const appendCallback = jest.fn();
|
||||||
|
document.addEventListener('w-teleport:append', appendCallback);
|
||||||
|
const appendedCallback = jest.fn();
|
||||||
|
document.addEventListener('w-teleport:appended', appendedCallback);
|
||||||
|
|
||||||
|
application.start();
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
// updating the DOM
|
||||||
|
|
||||||
|
expect(document.querySelectorAll('template')).toHaveLength(0);
|
||||||
|
expect(document.getElementById('content')).not.toBeNull();
|
||||||
|
expect(document.getElementById('content').parentElement).toEqual(
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
|
|
||||||
|
// dispatching events
|
||||||
|
|
||||||
|
expect(appendCallback).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
detail: { complete: expect.any(Function), target: document.body },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(appendedCallback).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ detail: { target: document.body } }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow a value to have the Template element kept', async () => {
|
||||||
|
document
|
||||||
|
.querySelector('template')
|
||||||
|
.setAttribute('data-w-teleport-keep-value', 'true');
|
||||||
|
|
||||||
|
expect(document.querySelectorAll('template')).toHaveLength(1);
|
||||||
|
expect(document.getElementById('content')).toBeNull();
|
||||||
|
|
||||||
|
application.start();
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(document.querySelectorAll('template')).toHaveLength(1);
|
||||||
|
expect(document.getElementById('content')).not.toBeNull();
|
||||||
|
expect(document.getElementById('content').parentElement).toEqual(
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow the target container to be based on a provided selector value', async () => {
|
||||||
|
document.body.innerHTML += `
|
||||||
|
<div id="target-container"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const template = document.querySelector('template');
|
||||||
|
template.setAttribute(
|
||||||
|
'data-w-teleport-target-value',
|
||||||
|
'#target-container',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(document.getElementById('target-container').innerHTML).toEqual('');
|
||||||
|
|
||||||
|
application.start();
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(document.getElementById('target-container').innerHTML).toEqual(
|
||||||
|
'<div id="content">Some content</div>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow for a default target container within the root element of a shadow DOM', async () => {
|
||||||
|
const shadowHost = document.createElement('div');
|
||||||
|
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
|
||||||
|
document.body.appendChild(shadowHost);
|
||||||
|
|
||||||
|
const template = document.getElementById('template');
|
||||||
|
const content = template.content.cloneNode(true);
|
||||||
|
|
||||||
|
const targetContainer = document.createElement('div');
|
||||||
|
targetContainer.setAttribute('id', 'target-container');
|
||||||
|
targetContainer.appendChild(content);
|
||||||
|
shadowRoot.appendChild(targetContainer);
|
||||||
|
|
||||||
|
application.start();
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(shadowRoot.querySelector('#target-container').innerHTML).toContain(
|
||||||
|
'<div id="content">Some content</div>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if the template content is empty', async () => {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
document.getElementById('template').innerHTML = '';
|
||||||
|
|
||||||
|
application.handleError = (error, message) => {
|
||||||
|
errors.push({ error, message });
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.resolve(application.start());
|
||||||
|
|
||||||
|
expect(errors).toEqual([
|
||||||
|
{
|
||||||
|
error: new Error('Invalid template content.'),
|
||||||
|
message: 'Error connecting controller',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if a valid target container cannot be resolved', async () => {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('template')
|
||||||
|
.setAttribute('data-w-teleport-target-value', '#missing-container');
|
||||||
|
|
||||||
|
application.handleError = (error, message) => {
|
||||||
|
errors.push({ error, message });
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.resolve(application.start());
|
||||||
|
|
||||||
|
expect(errors).toEqual([
|
||||||
|
{
|
||||||
|
error: new Error(
|
||||||
|
"No valid target container found at '#missing-container'.",
|
||||||
|
),
|
||||||
|
message: 'Error connecting controller',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
97
client/src/controllers/TeleportController.ts
Normal file
97
client/src/controllers/TeleportController.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the controlled element's content to be copied and appended
|
||||||
|
* to another place in the DOM. Once copied, the original controlled
|
||||||
|
* element will be removed from the DOM unless `keep` is true.
|
||||||
|
* If a target selector isn't provided, a default target of
|
||||||
|
* `document.body` or the Shadow Root's first DOM node will be used.
|
||||||
|
* Depending on location of the controlled element.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <aside>
|
||||||
|
* <template
|
||||||
|
* data-controller="w-teleport"
|
||||||
|
* data-w-teleport-target-value="#other-location"
|
||||||
|
* >
|
||||||
|
* <div class="content-to-clone">Some content</div>
|
||||||
|
* </template>
|
||||||
|
* <div id="other-location"></div>
|
||||||
|
* </aside>
|
||||||
|
*/
|
||||||
|
export class TeleportController extends Controller<HTMLTemplateElement> {
|
||||||
|
static values = {
|
||||||
|
keep: { default: false, type: Boolean },
|
||||||
|
target: { default: '', type: String },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** If true, keep the original DOM element intact, otherwise remove it when cloned. */
|
||||||
|
declare keepValue: boolean;
|
||||||
|
/** A selector to determine the target location to clone the element. */
|
||||||
|
declare targetValue: string;
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.append();
|
||||||
|
}
|
||||||
|
|
||||||
|
append() {
|
||||||
|
const target = this.target;
|
||||||
|
let completed = false;
|
||||||
|
|
||||||
|
const complete = () => {
|
||||||
|
if (completed) return;
|
||||||
|
target.append(this.templateElement);
|
||||||
|
this.dispatch('appended', { cancelable: false, detail: { target } });
|
||||||
|
completed = true;
|
||||||
|
if (this.keepValue) return;
|
||||||
|
this.element.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const event = this.dispatch('append', {
|
||||||
|
cancelable: true,
|
||||||
|
detail: { complete, target },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!event.defaultPrevented) complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a valid target element, defaulting to the document.body
|
||||||
|
* or the shadow root's first DOM node if no target selector provided.
|
||||||
|
*/
|
||||||
|
get target() {
|
||||||
|
let target: any;
|
||||||
|
|
||||||
|
if (this.targetValue) {
|
||||||
|
target = document.querySelector(this.targetValue);
|
||||||
|
} else {
|
||||||
|
const rootNode = this.element.getRootNode();
|
||||||
|
target =
|
||||||
|
rootNode instanceof Document ? rootNode.body : rootNode.firstChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(target instanceof Element)) {
|
||||||
|
throw new Error(
|
||||||
|
`No valid target container found at ${
|
||||||
|
this.targetValue ? `'${this.targetValue}'` : 'the root node'
|
||||||
|
}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a valid HTMLElement from the controlled element's children.
|
||||||
|
*/
|
||||||
|
get templateElement() {
|
||||||
|
const templateElement =
|
||||||
|
this.element.content.firstElementChild?.cloneNode(true);
|
||||||
|
|
||||||
|
if (!(templateElement instanceof HTMLElement)) {
|
||||||
|
throw new Error('Invalid template content.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return templateElement;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import { ActionController } from './ActionController';
|
|||||||
import { AutosizeController } from './AutosizeController';
|
import { AutosizeController } from './AutosizeController';
|
||||||
import { BulkController } from './BulkController';
|
import { BulkController } from './BulkController';
|
||||||
import { CountController } from './CountController';
|
import { CountController } from './CountController';
|
||||||
|
import { DialogController } from './DialogController';
|
||||||
import { DismissibleController } from './DismissibleController';
|
import { DismissibleController } from './DismissibleController';
|
||||||
import { DropdownController } from './DropdownController';
|
import { DropdownController } from './DropdownController';
|
||||||
import { MessagesController } from './MessagesController';
|
import { MessagesController } from './MessagesController';
|
||||||
@ -15,6 +16,7 @@ import { SubmitController } from './SubmitController';
|
|||||||
import { SwapController } from './SwapController';
|
import { SwapController } from './SwapController';
|
||||||
import { SyncController } from './SyncController';
|
import { SyncController } from './SyncController';
|
||||||
import { TagController } from './TagController';
|
import { TagController } from './TagController';
|
||||||
|
import { TeleportController } from './TeleportController';
|
||||||
import { TooltipController } from './TooltipController';
|
import { TooltipController } from './TooltipController';
|
||||||
import { UpgradeController } from './UpgradeController';
|
import { UpgradeController } from './UpgradeController';
|
||||||
|
|
||||||
@ -27,6 +29,7 @@ export const coreControllerDefinitions: Definition[] = [
|
|||||||
{ controllerConstructor: AutosizeController, identifier: 'w-autosize' },
|
{ controllerConstructor: AutosizeController, identifier: 'w-autosize' },
|
||||||
{ controllerConstructor: BulkController, identifier: 'w-bulk' },
|
{ controllerConstructor: BulkController, identifier: 'w-bulk' },
|
||||||
{ controllerConstructor: CountController, identifier: 'w-count' },
|
{ controllerConstructor: CountController, identifier: 'w-count' },
|
||||||
|
{ controllerConstructor: DialogController, identifier: 'w-dialog' },
|
||||||
{ controllerConstructor: DismissibleController, identifier: 'w-dismissible' },
|
{ controllerConstructor: DismissibleController, identifier: 'w-dismissible' },
|
||||||
{ controllerConstructor: DropdownController, identifier: 'w-dropdown' },
|
{ controllerConstructor: DropdownController, identifier: 'w-dropdown' },
|
||||||
{ controllerConstructor: MessagesController, identifier: 'w-messages' },
|
{ controllerConstructor: MessagesController, identifier: 'w-messages' },
|
||||||
@ -37,6 +40,7 @@ export const coreControllerDefinitions: Definition[] = [
|
|||||||
{ controllerConstructor: SwapController, identifier: 'w-swap' },
|
{ controllerConstructor: SwapController, identifier: 'w-swap' },
|
||||||
{ controllerConstructor: SyncController, identifier: 'w-sync' },
|
{ controllerConstructor: SyncController, identifier: 'w-sync' },
|
||||||
{ controllerConstructor: TagController, identifier: 'w-tag' },
|
{ controllerConstructor: TagController, identifier: 'w-tag' },
|
||||||
|
{ controllerConstructor: TeleportController, identifier: 'w-teleport' },
|
||||||
{ controllerConstructor: TooltipController, identifier: 'w-tooltip' },
|
{ controllerConstructor: TooltipController, identifier: 'w-tooltip' },
|
||||||
{ controllerConstructor: UpgradeController, identifier: 'w-upgrade' },
|
{ controllerConstructor: UpgradeController, identifier: 'w-upgrade' },
|
||||||
];
|
];
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Icon, Portal } from '../..';
|
import { Icon, Portal } from '../..';
|
||||||
import { initTooltips } from '../../includes/initTooltips';
|
import { initTooltips } from '../../includes/initTooltips';
|
||||||
import { initTabs } from '../../includes/tabs';
|
import { initTabs } from '../../includes/tabs';
|
||||||
import { dialog } from '../../includes/dialog';
|
|
||||||
import initCollapsibleBreadcrumbs from '../../includes/breadcrumbs';
|
import initCollapsibleBreadcrumbs from '../../includes/breadcrumbs';
|
||||||
import initSidePanel from '../../includes/sidePanel';
|
import initSidePanel from '../../includes/sidePanel';
|
||||||
import {
|
import {
|
||||||
@ -22,7 +21,6 @@ window.wagtail.components = {
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initTooltips();
|
initTooltips();
|
||||||
initTabs();
|
initTabs();
|
||||||
dialog();
|
|
||||||
initCollapsibleBreadcrumbs();
|
initCollapsibleBreadcrumbs();
|
||||||
initSidePanel();
|
initSidePanel();
|
||||||
initCollapsiblePanels();
|
initCollapsiblePanels();
|
||||||
|
Loading…
Reference in New Issue
Block a user