kopia lustrzana https://github.com/wagtail/wagtail
Set up initial stimulus application integration
rodzic
70681ec2bb
commit
ede189ada5
44
.eslintrc.js
44
.eslintrc.js
|
@ -95,6 +95,50 @@ module.exports = {
|
||||||
'react/require-default-props': 'off',
|
'react/require-default-props': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Rules we want to enforce or change for Stimulus Controllers
|
||||||
|
{
|
||||||
|
files: ['*Controller.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/member-ordering': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
classes: {
|
||||||
|
memberTypes: ['signature', 'field', 'method'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/naming-convention': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'method',
|
||||||
|
format: ['camelCase'],
|
||||||
|
custom: {
|
||||||
|
// Use connect or initialize instead of constructor, avoid generic 'render' or 'update' methods and instead be more specific.
|
||||||
|
regex: '^(constructor|render|update)$',
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'property',
|
||||||
|
format: ['camelCase'],
|
||||||
|
custom: {
|
||||||
|
// Use Stimulus values where possible for internal state, avoid a generic state object as these are not reactive.
|
||||||
|
regex: '^(state)$',
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-restricted-properties': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
object: 'window',
|
||||||
|
property: 'Stimulus',
|
||||||
|
message:
|
||||||
|
"Please import the base Controller or only access the Stimulus instance via the controller's `this.application` attribute.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
// Rules we don’t want to enforce for test and tooling code.
|
// Rules we don’t want to enforce for test and tooling code.
|
||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# `src/controllers` folder
|
||||||
|
|
||||||
|
**Important:** This is a migration in progress, any large refactors or new code should adopt this approach.
|
||||||
|
|
||||||
|
- Wagtail uses [Stimulus](https://stimulus.hotwired.dev/) as a way to attach interactive behaviour to DOM elements.
|
||||||
|
- This is a lightweight JavaScript framework that allows a JavaScript class to be attached to any DOM element that adheres to a specific usage of `data-` attributes on the element.
|
||||||
|
- Each file within this folder should contain one Stimulus controller class, using a matching file name (for example `class MyAwesomeController, `MyAwesomeController.ts`, all TitleCase).
|
||||||
|
- Controllers that are included in the `index.ts` default export will automatically be included in the core bundle and provided by default.
|
||||||
|
- Stories need to be written as JavaScript for now - `MyController.stories.js` as the compiled JavaScript from StoryBook conflicts with Stimulus' usage of adding getters only on Controller instantiation.
|
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Definition } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Important: Only add default core controllers that should load with the base admin JS bundle.
|
||||||
|
*/
|
||||||
|
export const coreControllerDefinitions: Definition[] = [
|
||||||
|
/* .. */
|
||||||
|
];
|
|
@ -1,9 +1,15 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import { coreControllerDefinitions } from '../../controllers';
|
||||||
import { escapeHtml } from '../../utils/text';
|
import { escapeHtml } from '../../utils/text';
|
||||||
import { initButtonSelects } from '../../includes/initButtonSelects';
|
import { initButtonSelects } from '../../includes/initButtonSelects';
|
||||||
|
import { initStimulus } from '../../includes/initStimulus';
|
||||||
import { initTagField } from '../../includes/initTagField';
|
import { initTagField } from '../../includes/initTagField';
|
||||||
import { initTooltips } from '../../includes/initTooltips';
|
import { initTooltips } from '../../includes/initTooltips';
|
||||||
|
|
||||||
|
/** initialise Wagtail Stimulus application with core controller definitions */
|
||||||
|
window.Stimulus = initStimulus({ definitions: coreControllerDefinitions });
|
||||||
|
|
||||||
/* generic function for adding a message to message area through JS alone */
|
/* generic function for adding a message to message area through JS alone */
|
||||||
function addMessage(status, text) {
|
function addMessage(status, text) {
|
||||||
$('.messages')
|
$('.messages')
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
import { StimulusWrapper } from '../../storybook/StimulusWrapper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An example Stimulus controller that allows for an element to have
|
||||||
|
* a random dice value.
|
||||||
|
*/
|
||||||
|
class ExampleDiceController extends Controller {
|
||||||
|
static targets = ['element'];
|
||||||
|
static values = { number: { type: Number, default: 6 } };
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.roll();
|
||||||
|
}
|
||||||
|
|
||||||
|
roll() {
|
||||||
|
const numberValue = this.numberValue;
|
||||||
|
const element = this.elementTarget;
|
||||||
|
|
||||||
|
const result = Math.floor(Math.random() * numberValue) + 1;
|
||||||
|
|
||||||
|
if (numberValue === 6) {
|
||||||
|
element.setAttribute('title', `${result}`);
|
||||||
|
element.textContent = `${['⚀', '⚁', '⚂', '⚃', '⚄', '⚅'][result - 1]}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.removeAttribute('title');
|
||||||
|
element.textContent = `${result}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitions = [
|
||||||
|
{ controllerConstructor: ExampleDiceController, identifier: 'dice' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const Template = ({ debug, number }) => (
|
||||||
|
<StimulusWrapper
|
||||||
|
debug={debug}
|
||||||
|
definitions={[
|
||||||
|
{ controllerConstructor: ExampleDiceController, identifier: 'dice' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
data-controller="dice"
|
||||||
|
{...(number && { 'data-dice-number-value': number })}
|
||||||
|
>
|
||||||
|
<button type="button" className="button w-mr-3" data-action="dice#roll">
|
||||||
|
Roll the dice
|
||||||
|
</button>
|
||||||
|
<kbd
|
||||||
|
data-dice-target="element"
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
minWidth: '4ch',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</StimulusWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Stimulus/Example',
|
||||||
|
argTypes: {
|
||||||
|
debug: {
|
||||||
|
control: { type: 'boolean' },
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
control: { type: 'select' },
|
||||||
|
description: 'Dice sides',
|
||||||
|
options: [2, 4, 6, 10, 20],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Base = Template.bind({ debug: true });
|
|
@ -0,0 +1,232 @@
|
||||||
|
import { Application, Controller } from '@hotwired/stimulus';
|
||||||
|
import { initStimulus } from './initStimulus';
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example controller (shortcut method definitions object) from documentation
|
||||||
|
*/
|
||||||
|
const wordCountController = {
|
||||||
|
STATIC: {
|
||||||
|
values: { max: { default: 10, type: Number } },
|
||||||
|
},
|
||||||
|
connect() {
|
||||||
|
this.setupOutput();
|
||||||
|
this.updateCount();
|
||||||
|
},
|
||||||
|
setupOutput() {
|
||||||
|
if (this.output) return;
|
||||||
|
const template = document.createElement('template');
|
||||||
|
template.innerHTML = `<output name='word-count' for='${this.element.id}'></output>`;
|
||||||
|
const output = template.content.firstChild;
|
||||||
|
this.element.insertAdjacentElement('beforebegin', output);
|
||||||
|
this.output = output;
|
||||||
|
},
|
||||||
|
updateCount(event) {
|
||||||
|
const value = event ? event.target.value : this.element.value;
|
||||||
|
const words = (value || '').split(' ');
|
||||||
|
this.output.textContent = `${words.length} / ${this.maxValue} words`;
|
||||||
|
},
|
||||||
|
disconnect() {
|
||||||
|
this.output && this.output.remove();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example controller from documentation as an ES6 class
|
||||||
|
*/
|
||||||
|
class WordCountController extends Controller {
|
||||||
|
static values = { max: { default: 10, type: Number } };
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
const output = document.createElement('output');
|
||||||
|
output.setAttribute('name', 'word-count');
|
||||||
|
output.setAttribute('for', this.element.id);
|
||||||
|
output.style.float = 'right';
|
||||||
|
this.element.insertAdjacentElement('beforebegin', output);
|
||||||
|
this.output = output;
|
||||||
|
this.updateCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupOutput() {
|
||||||
|
if (this.output) return;
|
||||||
|
const template = document.createElement('template');
|
||||||
|
template.innerHTML = `<output name='word-count' for='${this.element.id}' style='float: right;'></output>`;
|
||||||
|
const output = template.content.firstChild;
|
||||||
|
this.element.insertAdjacentElement('beforebegin', output);
|
||||||
|
this.output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCount(event) {
|
||||||
|
const value = event ? event.target.value : this.element.value;
|
||||||
|
const words = (value || '').split(' ');
|
||||||
|
this.output.textContent = `${words.length} / ${this.maxValue} words`;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this.output && this.output.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('initStimulus', () => {
|
||||||
|
const mockControllerConnected = jest.fn();
|
||||||
|
|
||||||
|
class TestMockController extends Controller {
|
||||||
|
static targets = ['item'];
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
mockControllerConnected();
|
||||||
|
this.itemTargets.forEach((item) => {
|
||||||
|
item.setAttribute('hidden', '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<main>
|
||||||
|
<section data-controller="w-test-mock">
|
||||||
|
<div id="item" data-w-test-mock-target="item"></div>
|
||||||
|
</section>
|
||||||
|
</main>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
let application;
|
||||||
|
|
||||||
|
it('should initialise a stimulus application', () => {
|
||||||
|
const definitions = [
|
||||||
|
{ identifier: 'w-test-mock', controllerConstructor: TestMockController },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(mockControllerConnected).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
application = initStimulus({ debug: false, definitions });
|
||||||
|
|
||||||
|
expect(application).toBeInstanceOf(Application);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have set the debug value based on the option provided', () => {
|
||||||
|
expect(application.debug).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have loaded the controller definitions supplied', () => {
|
||||||
|
expect(mockControllerConnected).toHaveBeenCalled();
|
||||||
|
expect(application.controllers).toHaveLength(1);
|
||||||
|
expect(application.controllers[0]).toBeInstanceOf(TestMockController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support registering a controller via an object with the createController static method', async () => {
|
||||||
|
const section = document.createElement('section');
|
||||||
|
section.id = 'example-a';
|
||||||
|
section.innerHTML = `<input value="some words" id="example-a-input" data-controller="example-a" data-action="change->example-a#updateCount" />`;
|
||||||
|
|
||||||
|
// create a controller and register it
|
||||||
|
application.register(
|
||||||
|
'example-a',
|
||||||
|
application.constructor.createController(wordCountController),
|
||||||
|
);
|
||||||
|
|
||||||
|
// before controller element added - should not include an `output` element
|
||||||
|
expect(document.querySelector('#example-a > output')).toEqual(null);
|
||||||
|
|
||||||
|
document.querySelector('section').after(section);
|
||||||
|
|
||||||
|
await Promise.resolve({});
|
||||||
|
|
||||||
|
// after controller connected - should have an output element
|
||||||
|
expect(document.querySelector('#example-a > output').innerHTML).toEqual(
|
||||||
|
'2 / 10 words',
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.resolve({});
|
||||||
|
|
||||||
|
// should respond to changes on the input
|
||||||
|
const input = document.querySelector('#example-a > input');
|
||||||
|
input.setAttribute('value', 'even more words');
|
||||||
|
input.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
|
expect(document.querySelector('#example-a > output').innerHTML).toEqual(
|
||||||
|
'3 / 10 words',
|
||||||
|
);
|
||||||
|
|
||||||
|
// removal of the input should also remove the output (disconnect method)
|
||||||
|
input.remove();
|
||||||
|
|
||||||
|
await Promise.resolve({});
|
||||||
|
|
||||||
|
// should call the disconnect method (removal of the injected HTML)
|
||||||
|
expect(document.querySelector('#example-a > output')).toEqual(null);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
section.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support the documented approach for registering a controller via a class with register', async () => {
|
||||||
|
const section = document.createElement('section');
|
||||||
|
section.id = 'example-b';
|
||||||
|
section.innerHTML = `<input value="some words" id="example-b-input" data-controller="example-b" data-action="change->example-b#updateCount" data-example-b-max-value="5" />`;
|
||||||
|
|
||||||
|
// register a controller
|
||||||
|
application.register('example-b', WordCountController);
|
||||||
|
|
||||||
|
// before controller element added - should not include an `output` element
|
||||||
|
expect(document.querySelector('#example-b > output')).toEqual(null);
|
||||||
|
|
||||||
|
document.querySelector('section').after(section);
|
||||||
|
|
||||||
|
await Promise.resolve({});
|
||||||
|
|
||||||
|
// after controller connected - should have an output element
|
||||||
|
expect(document.querySelector('#example-b > output').innerHTML).toEqual(
|
||||||
|
'2 / 5 words',
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.resolve({});
|
||||||
|
|
||||||
|
// should respond to changes on the input
|
||||||
|
const input = document.querySelector('#example-b > input');
|
||||||
|
input.setAttribute('value', 'even more words');
|
||||||
|
input.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
|
expect(document.querySelector('#example-b > output').innerHTML).toEqual(
|
||||||
|
'3 / 5 words',
|
||||||
|
);
|
||||||
|
|
||||||
|
// removal of the input should also remove the output (disconnect method)
|
||||||
|
input.remove();
|
||||||
|
|
||||||
|
await Promise.resolve({});
|
||||||
|
|
||||||
|
// should call the disconnect method (removal of the injected HTML)
|
||||||
|
expect(document.querySelector('#example-b > output')).toEqual(null);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
section.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide access to a base Controller class on the returned application instance', () => {
|
||||||
|
expect(application.constructor.Controller).toEqual(Controller);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createController', () => {
|
||||||
|
const createController = initStimulus().constructor.createController;
|
||||||
|
|
||||||
|
it('should safely create a Stimulus Controller class if no args provided', () => {
|
||||||
|
const CustomController = createController();
|
||||||
|
expect(CustomController.prototype instanceof Controller).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a Stimulus Controller class with static properties', () => {
|
||||||
|
const someMethod = jest.fn();
|
||||||
|
|
||||||
|
const CustomController = createController({
|
||||||
|
STATIC: { targets: ['source'] },
|
||||||
|
someMethod,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(CustomController.targets).toEqual(['source']);
|
||||||
|
expect(CustomController.someMethod).toBeUndefined();
|
||||||
|
expect(CustomController.prototype.someMethod).toEqual(someMethod);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,81 @@
|
||||||
|
import type { Definition } from '@hotwired/stimulus';
|
||||||
|
import { Application, Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
type ControllerObjectDefinition = Record<string, () => void> & {
|
||||||
|
STATIC?: {
|
||||||
|
classes?: string[];
|
||||||
|
targets?: string[];
|
||||||
|
values: typeof Controller.values;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the Stimulus application class to provide some convenience
|
||||||
|
* static attributes or methods to be accessed globally.
|
||||||
|
*/
|
||||||
|
class WagtailApplication extends Application {
|
||||||
|
/**
|
||||||
|
* Ensure the base Controller class is available for new controllers.
|
||||||
|
*/
|
||||||
|
static Controller = Controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that accepts a plain old object and returns a Stimulus Controller.
|
||||||
|
* Useful when ES6 modules with base class being extended not in use
|
||||||
|
* or build tool not in use or for just super convenient class creation.
|
||||||
|
*
|
||||||
|
* Inspired heavily by
|
||||||
|
* https://github.com/StackExchange/Stacks/blob/v1.6.5/lib/ts/stacks.ts#L84
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* createController({
|
||||||
|
* STATIC: { targets = ['container'] }
|
||||||
|
* connect() {
|
||||||
|
* console.log('connected', this.element, this.containerTarget);
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static createController = (
|
||||||
|
controllerDefinition: ControllerObjectDefinition = {},
|
||||||
|
): typeof Controller => {
|
||||||
|
class NewController<X extends Element> extends Controller<X> {}
|
||||||
|
|
||||||
|
const { STATIC = {}, ...controllerDefinitionWithoutStatic } =
|
||||||
|
controllerDefinition;
|
||||||
|
|
||||||
|
// set up static values
|
||||||
|
Object.entries(STATIC).forEach(([key, value]) => {
|
||||||
|
NewController[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// set up class methods
|
||||||
|
Object.assign(NewController.prototype, controllerDefinitionWithoutStatic);
|
||||||
|
|
||||||
|
return NewController;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the Wagtail Stimulus application and dispatches and registers
|
||||||
|
* custom event behaviour.
|
||||||
|
*
|
||||||
|
* Loads the the supplied core controller definitions into the application.
|
||||||
|
* Turns on debug mode if in local development (for now).
|
||||||
|
*/
|
||||||
|
export const initStimulus = ({
|
||||||
|
debug = process.env.NODE_ENV === 'development',
|
||||||
|
definitions = [],
|
||||||
|
element = document.documentElement,
|
||||||
|
}: {
|
||||||
|
debug?: boolean;
|
||||||
|
definitions?: Definition[];
|
||||||
|
element?: HTMLElement;
|
||||||
|
} = {}): Application => {
|
||||||
|
const application = WagtailApplication.start(element);
|
||||||
|
|
||||||
|
application.debug = debug;
|
||||||
|
application.load(definitions);
|
||||||
|
|
||||||
|
return application;
|
||||||
|
};
|
|
@ -0,0 +1,56 @@
|
||||||
|
import type { Application, Definition } from '@hotwired/stimulus';
|
||||||
|
import React from 'react';
|
||||||
|
import { initStimulus } from '../src/includes/initStimulus';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around the Stimulus application to ensure that the application
|
||||||
|
* is scoped to only the specific story instance's DOM and also ensure
|
||||||
|
* that the hot-reloader / page switches to not re-instate new applications
|
||||||
|
* each time.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* import { StimulusWrapper } from '../storybook/StimulusWrapper';
|
||||||
|
* const Template = ({ debug }) =>
|
||||||
|
* <StimulusWrapper
|
||||||
|
* definitions={[{ controllerConstructor: AutoFieldController, identifier: 'w-something' }]}
|
||||||
|
* debug={debug}
|
||||||
|
* >
|
||||||
|
* <form data-controller="w-something" />
|
||||||
|
* </StimulusWrapper>
|
||||||
|
*/
|
||||||
|
export class StimulusWrapper extends React.Component<{
|
||||||
|
debug?: boolean;
|
||||||
|
definitions?: Definition[];
|
||||||
|
}> {
|
||||||
|
ref: React.RefObject<HTMLDivElement>;
|
||||||
|
application?: Application;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.ref = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { debug = false, definitions = [] } = this.props;
|
||||||
|
const element = this.ref.current || document.documentElement;
|
||||||
|
this.application = initStimulus({ debug, definitions, element });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate({ debug: prevDebug }) {
|
||||||
|
const { debug } = this.props;
|
||||||
|
if (debug !== prevDebug) {
|
||||||
|
Object.assign(this.application as Application, { debug });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (!this.application) return;
|
||||||
|
this.application.stop();
|
||||||
|
delete this.application;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children } = this.props;
|
||||||
|
return <div ref={this.ref}>{children}</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
"name": "wagtail",
|
"name": "wagtail",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hotwired/stimulus": "^3.2.1",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"a11y-dialog": "^7.4.0",
|
"a11y-dialog": "^7.4.0",
|
||||||
"draft-js": "^0.10.5",
|
"draft-js": "^0.10.5",
|
||||||
|
@ -2052,6 +2053,11 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@hotwired/stimulus": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-HGlzDcf9vv/EQrMJ5ZG6VWNs8Z/xMN+1o2OhV1gKiSG6CqZt5MCBB1gRg5ILiN3U0jEAxuDTNPRfBcnZBDmupQ=="
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.9.3",
|
"version": "0.9.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
@ -32867,6 +32873,11 @@
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@hotwired/stimulus": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-HGlzDcf9vv/EQrMJ5ZG6VWNs8Z/xMN+1o2OhV1gKiSG6CqZt5MCBB1gRg5ILiN3U0jEAxuDTNPRfBcnZBDmupQ=="
|
||||||
|
},
|
||||||
"@humanwhocodes/config-array": {
|
"@humanwhocodes/config-array": {
|
||||||
"version": "0.9.3",
|
"version": "0.9.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
|
@ -102,6 +102,7 @@
|
||||||
"webpack-cli": "^4.9.1"
|
"webpack-cli": "^4.9.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hotwired/stimulus": "^3.2.1",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"a11y-dialog": "^7.4.0",
|
"a11y-dialog": "^7.4.0",
|
||||||
"draft-js": "^0.10.5",
|
"draft-js": "^0.10.5",
|
||||||
|
|
Ładowanie…
Reference in New Issue