stimulus-rails 0.4.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -17
- data/app/assets/javascripts/stimulus-importmap-autoloader.js +1 -1
- data/app/assets/javascripts/stimulus.js +1 -1
- data/app/assets/javascripts/stimulus@3.0.0-beta.2.js +2034 -0
- data/lib/generators/stimulus/USAGE +15 -0
- data/lib/generators/stimulus/stimulus_generator.rb +16 -0
- data/lib/generators/stimulus/templates/controller.js.tt +7 -0
- data/lib/install/app/javascript/controllers/application.js +10 -0
- data/lib/install/app/javascript/controllers/index_for_importmap.js +2 -9
- data/lib/install/app/javascript/controllers/index_for_node.js +5 -9
- data/lib/install/stimulus_with_importmap.rb +2 -0
- data/lib/install/stimulus_with_node.rb +4 -9
- data/lib/stimulus/manifest.rb +27 -0
- data/lib/stimulus/version.rb +1 -1
- data/lib/tasks/stimulus_tasks.rake +21 -0
- metadata +8 -4
- data/lib/install/app/javascript/controllers/index_for_webpacker.js +0 -14
- data/lib/install/install.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d59e02b94caffed7b86098358bdcb974ea1069c5d5768d2cb273f330cd57c5f3
|
4
|
+
data.tar.gz: dc2ffd8ef10fc794fdd6869830c515818fe3c1b0f4a8e2e88d493d3bc8cffc9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2dbdfe28ad3e21b5c36cd263b8ab6461a244749cf3c18b8e305ed734da2e08d9d613024b0e85f71f560033e79b3d55754c0dced0b1364b982699caed3fbf884b
|
7
|
+
data.tar.gz: d9eaaddb06f12bdcf94ea78fb363020ab36b7558159591f7c03987bbbb13fda1e14d2bae2b91ddb0410d6d0baf0ba21b4ad6598c0ffd9cd9ab2e243320e9a4b2
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[Stimulus](https://stimulus.hotwired.dev) is a JavaScript framework with modest ambitions. It doesn’t seek to take over your entire front-end in fact, it’s not concerned with rendering HTML at all. Instead, it’s designed to augment your HTML with just enough behavior to make it shine. Stimulus pairs beautifully with Turbo to provide a complete solution for fast, compelling applications with a minimal amount of effort. Together they form the core of [Hotwire](https://hotwired.dev).
|
4
4
|
|
5
|
-
Stimulus for Rails makes it easy to use this modest framework with the asset pipeline and ES6/ESM in the browser. It relies on `importmap-rails` to make
|
5
|
+
Stimulus for Rails makes it easy to use this modest framework with the asset pipeline and ES6/ESM in the browser. It relies on either `importmap-rails` to make Stimulus available via ESM or a node-capable Rails (like via `jsbundling-rails`) to include Stimulus in the bundle. Make sure to install one of these first!
|
6
6
|
|
7
7
|
|
8
8
|
## Installation
|
@@ -11,31 +11,24 @@ Stimulus for Rails makes it easy to use this modest framework with the asset pip
|
|
11
11
|
2. Run `./bin/bundle install`.
|
12
12
|
3. Run `./bin/rails stimulus:install`
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
1. Create an example controller in `app/assets/javascripts/controllers/hello_controller.js`.
|
17
|
-
2. Append `import "@hotwired/stimulus-autoloader"` to your `app/assets/javascripts/application.js` entrypoint.
|
18
|
-
|
19
|
-
Make sure you've already installed `importmap-rails` and that it's referenced before `stimulus-rails` (or `hotwire-rails`) in your Gemfile.
|
20
|
-
|
21
|
-
If using Webpacker to manage JavaScript, the last command will:
|
22
|
-
|
23
|
-
1. Import the controllers directory in the application pack.
|
24
|
-
2. Create a controllers directory at `app/javascripts/controllers`.
|
25
|
-
3. Create an example controller in `app/javascripts/controllers/hello_controller.js`.
|
26
|
-
4. Install the Stimulus NPM package.
|
14
|
+
The installer will automatically detect whether you're using an [import map](https://github.com/rails/importmap-rails) or [JavaScript bundler](https://github.com/rails/jsbundling-rails) to manage your application's JavaScript. If you're using an import map, the Stimulus dependencies will be pinned to the versions of the library included with this gem. If you're using node, yarn will add the dependencies to your `package.json` file.
|
27
15
|
|
28
16
|
|
29
17
|
## Usage
|
30
18
|
|
31
|
-
|
19
|
+
The installer amends your JavaScript entry point at `app/javascript/application.js` to import the `app/javascript/controllers/index.js` file, which is responsible for setting up your Stimulus application and registering your controllers.
|
20
|
+
|
21
|
+
With an import-mapped application, controllers are automatically pinned and registered based on the file structure. With a node application, controllers need to be imported and registered directly in the index.js file, but this is done automatically using either the Stimulus generator (`./bin/rails generate stimulus [controller]`) or the dedicated `stimulus:manifest:update` task. Either will overwrite the `controllers/index.js` file.
|
32
22
|
|
33
|
-
|
23
|
+
You're encouraged to use the generator to add new controllers like so:
|
34
24
|
|
35
25
|
```javascript
|
26
|
+
// Run "./bin/rails g stimulus hello" to create the file and update the index, then amend:
|
27
|
+
|
36
28
|
// app/assets/javascripts/controllers/hello_controller.js
|
37
|
-
import { Controller } from "stimulus"
|
29
|
+
import { Controller } from "@hotwired/stimulus"
|
38
30
|
|
31
|
+
// Connects with data-controller="hello"
|
39
32
|
export default class extends Controller {
|
40
33
|
static targets = [ "name", "output" ]
|
41
34
|
|
@@ -14,7 +14,7 @@ export function parseImportmapJson() {
|
|
14
14
|
|
15
15
|
function registerControllerFromPath(path, under, application) {
|
16
16
|
const name = path
|
17
|
-
.replace(
|
17
|
+
.replace(new RegExp(`^${under}/`), "")
|
18
18
|
.replace("_controller", "")
|
19
19
|
.replace(/\//g, "--")
|
20
20
|
.replace(/_/g, "-")
|
@@ -0,0 +1,2034 @@
|
|
1
|
+
/*
|
2
|
+
Stimulus 3.0.0-beta.2
|
3
|
+
Copyright © 2021 Basecamp, LLC
|
4
|
+
*/
|
5
|
+
class EventListener {
|
6
|
+
constructor(eventTarget, eventName, eventOptions) {
|
7
|
+
this.eventTarget = eventTarget;
|
8
|
+
this.eventName = eventName;
|
9
|
+
this.eventOptions = eventOptions;
|
10
|
+
this.unorderedBindings = new Set();
|
11
|
+
}
|
12
|
+
connect() {
|
13
|
+
this.eventTarget.addEventListener(this.eventName, this, this.eventOptions);
|
14
|
+
}
|
15
|
+
disconnect() {
|
16
|
+
this.eventTarget.removeEventListener(this.eventName, this, this.eventOptions);
|
17
|
+
}
|
18
|
+
bindingConnected(binding) {
|
19
|
+
this.unorderedBindings.add(binding);
|
20
|
+
}
|
21
|
+
bindingDisconnected(binding) {
|
22
|
+
this.unorderedBindings.delete(binding);
|
23
|
+
}
|
24
|
+
handleEvent(event) {
|
25
|
+
const extendedEvent = extendEvent(event);
|
26
|
+
for (const binding of this.bindings) {
|
27
|
+
if (extendedEvent.immediatePropagationStopped) {
|
28
|
+
break;
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
binding.handleEvent(extendedEvent);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
get bindings() {
|
36
|
+
return Array.from(this.unorderedBindings).sort((left, right) => {
|
37
|
+
const leftIndex = left.index, rightIndex = right.index;
|
38
|
+
return leftIndex < rightIndex ? -1 : leftIndex > rightIndex ? 1 : 0;
|
39
|
+
});
|
40
|
+
}
|
41
|
+
}
|
42
|
+
function extendEvent(event) {
|
43
|
+
if ("immediatePropagationStopped" in event) {
|
44
|
+
return event;
|
45
|
+
}
|
46
|
+
else {
|
47
|
+
const { stopImmediatePropagation } = event;
|
48
|
+
return Object.assign(event, {
|
49
|
+
immediatePropagationStopped: false,
|
50
|
+
stopImmediatePropagation() {
|
51
|
+
this.immediatePropagationStopped = true;
|
52
|
+
stopImmediatePropagation.call(this);
|
53
|
+
}
|
54
|
+
});
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
class Dispatcher {
|
59
|
+
constructor(application) {
|
60
|
+
this.application = application;
|
61
|
+
this.eventListenerMaps = new Map;
|
62
|
+
this.started = false;
|
63
|
+
}
|
64
|
+
start() {
|
65
|
+
if (!this.started) {
|
66
|
+
this.started = true;
|
67
|
+
this.eventListeners.forEach(eventListener => eventListener.connect());
|
68
|
+
}
|
69
|
+
}
|
70
|
+
stop() {
|
71
|
+
if (this.started) {
|
72
|
+
this.started = false;
|
73
|
+
this.eventListeners.forEach(eventListener => eventListener.disconnect());
|
74
|
+
}
|
75
|
+
}
|
76
|
+
get eventListeners() {
|
77
|
+
return Array.from(this.eventListenerMaps.values())
|
78
|
+
.reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
|
79
|
+
}
|
80
|
+
bindingConnected(binding) {
|
81
|
+
this.fetchEventListenerForBinding(binding).bindingConnected(binding);
|
82
|
+
}
|
83
|
+
bindingDisconnected(binding) {
|
84
|
+
this.fetchEventListenerForBinding(binding).bindingDisconnected(binding);
|
85
|
+
}
|
86
|
+
handleError(error, message, detail = {}) {
|
87
|
+
this.application.handleError(error, `Error ${message}`, detail);
|
88
|
+
}
|
89
|
+
fetchEventListenerForBinding(binding) {
|
90
|
+
const { eventTarget, eventName, eventOptions } = binding;
|
91
|
+
return this.fetchEventListener(eventTarget, eventName, eventOptions);
|
92
|
+
}
|
93
|
+
fetchEventListener(eventTarget, eventName, eventOptions) {
|
94
|
+
const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget);
|
95
|
+
const cacheKey = this.cacheKey(eventName, eventOptions);
|
96
|
+
let eventListener = eventListenerMap.get(cacheKey);
|
97
|
+
if (!eventListener) {
|
98
|
+
eventListener = this.createEventListener(eventTarget, eventName, eventOptions);
|
99
|
+
eventListenerMap.set(cacheKey, eventListener);
|
100
|
+
}
|
101
|
+
return eventListener;
|
102
|
+
}
|
103
|
+
createEventListener(eventTarget, eventName, eventOptions) {
|
104
|
+
const eventListener = new EventListener(eventTarget, eventName, eventOptions);
|
105
|
+
if (this.started) {
|
106
|
+
eventListener.connect();
|
107
|
+
}
|
108
|
+
return eventListener;
|
109
|
+
}
|
110
|
+
fetchEventListenerMapForEventTarget(eventTarget) {
|
111
|
+
let eventListenerMap = this.eventListenerMaps.get(eventTarget);
|
112
|
+
if (!eventListenerMap) {
|
113
|
+
eventListenerMap = new Map;
|
114
|
+
this.eventListenerMaps.set(eventTarget, eventListenerMap);
|
115
|
+
}
|
116
|
+
return eventListenerMap;
|
117
|
+
}
|
118
|
+
cacheKey(eventName, eventOptions) {
|
119
|
+
const parts = [eventName];
|
120
|
+
Object.keys(eventOptions).sort().forEach(key => {
|
121
|
+
parts.push(`${eventOptions[key] ? "" : "!"}${key}`);
|
122
|
+
});
|
123
|
+
return parts.join(":");
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
const descriptorPattern = /^((.+?)(@(window|document))?->)?(.+?)(#([^:]+?))(:(.+))?$/;
|
128
|
+
function parseActionDescriptorString(descriptorString) {
|
129
|
+
const source = descriptorString.trim();
|
130
|
+
const matches = source.match(descriptorPattern) || [];
|
131
|
+
return {
|
132
|
+
eventTarget: parseEventTarget(matches[4]),
|
133
|
+
eventName: matches[2],
|
134
|
+
eventOptions: matches[9] ? parseEventOptions(matches[9]) : {},
|
135
|
+
identifier: matches[5],
|
136
|
+
methodName: matches[7]
|
137
|
+
};
|
138
|
+
}
|
139
|
+
function parseEventTarget(eventTargetName) {
|
140
|
+
if (eventTargetName == "window") {
|
141
|
+
return window;
|
142
|
+
}
|
143
|
+
else if (eventTargetName == "document") {
|
144
|
+
return document;
|
145
|
+
}
|
146
|
+
}
|
147
|
+
function parseEventOptions(eventOptions) {
|
148
|
+
return eventOptions.split(":").reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
|
149
|
+
}
|
150
|
+
function stringifyEventTarget(eventTarget) {
|
151
|
+
if (eventTarget == window) {
|
152
|
+
return "window";
|
153
|
+
}
|
154
|
+
else if (eventTarget == document) {
|
155
|
+
return "document";
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
function camelize(value) {
|
160
|
+
return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
|
161
|
+
}
|
162
|
+
function capitalize(value) {
|
163
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
164
|
+
}
|
165
|
+
function dasherize(value) {
|
166
|
+
return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`);
|
167
|
+
}
|
168
|
+
function tokenize(value) {
|
169
|
+
return value.match(/[^\s]+/g) || [];
|
170
|
+
}
|
171
|
+
|
172
|
+
class Action {
|
173
|
+
constructor(element, index, descriptor) {
|
174
|
+
this.element = element;
|
175
|
+
this.index = index;
|
176
|
+
this.eventTarget = descriptor.eventTarget || element;
|
177
|
+
this.eventName = descriptor.eventName || getDefaultEventNameForElement(element) || error("missing event name");
|
178
|
+
this.eventOptions = descriptor.eventOptions || {};
|
179
|
+
this.identifier = descriptor.identifier || error("missing identifier");
|
180
|
+
this.methodName = descriptor.methodName || error("missing method name");
|
181
|
+
}
|
182
|
+
static forToken(token) {
|
183
|
+
return new this(token.element, token.index, parseActionDescriptorString(token.content));
|
184
|
+
}
|
185
|
+
toString() {
|
186
|
+
const eventNameSuffix = this.eventTargetName ? `@${this.eventTargetName}` : "";
|
187
|
+
return `${this.eventName}${eventNameSuffix}->${this.identifier}#${this.methodName}`;
|
188
|
+
}
|
189
|
+
get params() {
|
190
|
+
if (this.eventTarget instanceof Element) {
|
191
|
+
return this.getParamsFromEventTargetAttributes(this.eventTarget);
|
192
|
+
}
|
193
|
+
else {
|
194
|
+
return {};
|
195
|
+
}
|
196
|
+
}
|
197
|
+
getParamsFromEventTargetAttributes(eventTarget) {
|
198
|
+
const params = {};
|
199
|
+
const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`);
|
200
|
+
const attributes = Array.from(eventTarget.attributes);
|
201
|
+
attributes.forEach(({ name, value }) => {
|
202
|
+
const match = name.match(pattern);
|
203
|
+
const key = match && match[1];
|
204
|
+
if (key) {
|
205
|
+
Object.assign(params, { [camelize(key)]: typecast(value) });
|
206
|
+
}
|
207
|
+
});
|
208
|
+
return params;
|
209
|
+
}
|
210
|
+
get eventTargetName() {
|
211
|
+
return stringifyEventTarget(this.eventTarget);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
const defaultEventNames = {
|
215
|
+
"a": e => "click",
|
216
|
+
"button": e => "click",
|
217
|
+
"form": e => "submit",
|
218
|
+
"input": e => e.getAttribute("type") == "submit" ? "click" : "input",
|
219
|
+
"select": e => "change",
|
220
|
+
"textarea": e => "input"
|
221
|
+
};
|
222
|
+
function getDefaultEventNameForElement(element) {
|
223
|
+
const tagName = element.tagName.toLowerCase();
|
224
|
+
if (tagName in defaultEventNames) {
|
225
|
+
return defaultEventNames[tagName](element);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
function error(message) {
|
229
|
+
throw new Error(message);
|
230
|
+
}
|
231
|
+
function typecast(value) {
|
232
|
+
try {
|
233
|
+
return JSON.parse(value);
|
234
|
+
}
|
235
|
+
catch (o_O) {
|
236
|
+
return value;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
class Binding {
|
241
|
+
constructor(context, action) {
|
242
|
+
this.context = context;
|
243
|
+
this.action = action;
|
244
|
+
}
|
245
|
+
get index() {
|
246
|
+
return this.action.index;
|
247
|
+
}
|
248
|
+
get eventTarget() {
|
249
|
+
return this.action.eventTarget;
|
250
|
+
}
|
251
|
+
get eventOptions() {
|
252
|
+
return this.action.eventOptions;
|
253
|
+
}
|
254
|
+
get identifier() {
|
255
|
+
return this.context.identifier;
|
256
|
+
}
|
257
|
+
handleEvent(event) {
|
258
|
+
if (this.willBeInvokedByEvent(event)) {
|
259
|
+
this.invokeWithEvent(event);
|
260
|
+
}
|
261
|
+
}
|
262
|
+
get eventName() {
|
263
|
+
return this.action.eventName;
|
264
|
+
}
|
265
|
+
get method() {
|
266
|
+
const method = this.controller[this.methodName];
|
267
|
+
if (typeof method == "function") {
|
268
|
+
return method;
|
269
|
+
}
|
270
|
+
throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`);
|
271
|
+
}
|
272
|
+
invokeWithEvent(event) {
|
273
|
+
const { target, currentTarget } = event;
|
274
|
+
try {
|
275
|
+
const { params } = this.action;
|
276
|
+
const actionEvent = Object.assign(event, { params });
|
277
|
+
this.method.call(this.controller, actionEvent);
|
278
|
+
this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
|
279
|
+
}
|
280
|
+
catch (error) {
|
281
|
+
const { identifier, controller, element, index } = this;
|
282
|
+
const detail = { identifier, controller, element, index, event };
|
283
|
+
this.context.handleError(error, `invoking action "${this.action}"`, detail);
|
284
|
+
}
|
285
|
+
}
|
286
|
+
willBeInvokedByEvent(event) {
|
287
|
+
const eventTarget = event.target;
|
288
|
+
if (this.element === eventTarget) {
|
289
|
+
return true;
|
290
|
+
}
|
291
|
+
else if (eventTarget instanceof Element && this.element.contains(eventTarget)) {
|
292
|
+
return this.scope.containsElement(eventTarget);
|
293
|
+
}
|
294
|
+
else {
|
295
|
+
return this.scope.containsElement(this.action.element);
|
296
|
+
}
|
297
|
+
}
|
298
|
+
get controller() {
|
299
|
+
return this.context.controller;
|
300
|
+
}
|
301
|
+
get methodName() {
|
302
|
+
return this.action.methodName;
|
303
|
+
}
|
304
|
+
get element() {
|
305
|
+
return this.scope.element;
|
306
|
+
}
|
307
|
+
get scope() {
|
308
|
+
return this.context.scope;
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
class ElementObserver {
|
313
|
+
constructor(element, delegate) {
|
314
|
+
this.element = element;
|
315
|
+
this.started = false;
|
316
|
+
this.delegate = delegate;
|
317
|
+
this.elements = new Set;
|
318
|
+
this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
|
319
|
+
}
|
320
|
+
start() {
|
321
|
+
if (!this.started) {
|
322
|
+
this.started = true;
|
323
|
+
this.mutationObserver.observe(this.element, { attributes: true, childList: true, subtree: true });
|
324
|
+
this.refresh();
|
325
|
+
}
|
326
|
+
}
|
327
|
+
stop() {
|
328
|
+
if (this.started) {
|
329
|
+
this.mutationObserver.takeRecords();
|
330
|
+
this.mutationObserver.disconnect();
|
331
|
+
this.started = false;
|
332
|
+
}
|
333
|
+
}
|
334
|
+
refresh() {
|
335
|
+
if (this.started) {
|
336
|
+
const matches = new Set(this.matchElementsInTree());
|
337
|
+
for (const element of Array.from(this.elements)) {
|
338
|
+
if (!matches.has(element)) {
|
339
|
+
this.removeElement(element);
|
340
|
+
}
|
341
|
+
}
|
342
|
+
for (const element of Array.from(matches)) {
|
343
|
+
this.addElement(element);
|
344
|
+
}
|
345
|
+
}
|
346
|
+
}
|
347
|
+
processMutations(mutations) {
|
348
|
+
if (this.started) {
|
349
|
+
for (const mutation of mutations) {
|
350
|
+
this.processMutation(mutation);
|
351
|
+
}
|
352
|
+
}
|
353
|
+
}
|
354
|
+
processMutation(mutation) {
|
355
|
+
if (mutation.type == "attributes") {
|
356
|
+
this.processAttributeChange(mutation.target, mutation.attributeName);
|
357
|
+
}
|
358
|
+
else if (mutation.type == "childList") {
|
359
|
+
this.processRemovedNodes(mutation.removedNodes);
|
360
|
+
this.processAddedNodes(mutation.addedNodes);
|
361
|
+
}
|
362
|
+
}
|
363
|
+
processAttributeChange(node, attributeName) {
|
364
|
+
const element = node;
|
365
|
+
if (this.elements.has(element)) {
|
366
|
+
if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
|
367
|
+
this.delegate.elementAttributeChanged(element, attributeName);
|
368
|
+
}
|
369
|
+
else {
|
370
|
+
this.removeElement(element);
|
371
|
+
}
|
372
|
+
}
|
373
|
+
else if (this.matchElement(element)) {
|
374
|
+
this.addElement(element);
|
375
|
+
}
|
376
|
+
}
|
377
|
+
processRemovedNodes(nodes) {
|
378
|
+
for (const node of Array.from(nodes)) {
|
379
|
+
const element = this.elementFromNode(node);
|
380
|
+
if (element) {
|
381
|
+
this.processTree(element, this.removeElement);
|
382
|
+
}
|
383
|
+
}
|
384
|
+
}
|
385
|
+
processAddedNodes(nodes) {
|
386
|
+
for (const node of Array.from(nodes)) {
|
387
|
+
const element = this.elementFromNode(node);
|
388
|
+
if (element && this.elementIsActive(element)) {
|
389
|
+
this.processTree(element, this.addElement);
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
matchElement(element) {
|
394
|
+
return this.delegate.matchElement(element);
|
395
|
+
}
|
396
|
+
matchElementsInTree(tree = this.element) {
|
397
|
+
return this.delegate.matchElementsInTree(tree);
|
398
|
+
}
|
399
|
+
processTree(tree, processor) {
|
400
|
+
for (const element of this.matchElementsInTree(tree)) {
|
401
|
+
processor.call(this, element);
|
402
|
+
}
|
403
|
+
}
|
404
|
+
elementFromNode(node) {
|
405
|
+
if (node.nodeType == Node.ELEMENT_NODE) {
|
406
|
+
return node;
|
407
|
+
}
|
408
|
+
}
|
409
|
+
elementIsActive(element) {
|
410
|
+
if (element.isConnected != this.element.isConnected) {
|
411
|
+
return false;
|
412
|
+
}
|
413
|
+
else {
|
414
|
+
return this.element.contains(element);
|
415
|
+
}
|
416
|
+
}
|
417
|
+
addElement(element) {
|
418
|
+
if (!this.elements.has(element)) {
|
419
|
+
if (this.elementIsActive(element)) {
|
420
|
+
this.elements.add(element);
|
421
|
+
if (this.delegate.elementMatched) {
|
422
|
+
this.delegate.elementMatched(element);
|
423
|
+
}
|
424
|
+
}
|
425
|
+
}
|
426
|
+
}
|
427
|
+
removeElement(element) {
|
428
|
+
if (this.elements.has(element)) {
|
429
|
+
this.elements.delete(element);
|
430
|
+
if (this.delegate.elementUnmatched) {
|
431
|
+
this.delegate.elementUnmatched(element);
|
432
|
+
}
|
433
|
+
}
|
434
|
+
}
|
435
|
+
}
|
436
|
+
|
437
|
+
class AttributeObserver {
|
438
|
+
constructor(element, attributeName, delegate) {
|
439
|
+
this.attributeName = attributeName;
|
440
|
+
this.delegate = delegate;
|
441
|
+
this.elementObserver = new ElementObserver(element, this);
|
442
|
+
}
|
443
|
+
get element() {
|
444
|
+
return this.elementObserver.element;
|
445
|
+
}
|
446
|
+
get selector() {
|
447
|
+
return `[${this.attributeName}]`;
|
448
|
+
}
|
449
|
+
start() {
|
450
|
+
this.elementObserver.start();
|
451
|
+
}
|
452
|
+
stop() {
|
453
|
+
this.elementObserver.stop();
|
454
|
+
}
|
455
|
+
refresh() {
|
456
|
+
this.elementObserver.refresh();
|
457
|
+
}
|
458
|
+
get started() {
|
459
|
+
return this.elementObserver.started;
|
460
|
+
}
|
461
|
+
matchElement(element) {
|
462
|
+
return element.hasAttribute(this.attributeName);
|
463
|
+
}
|
464
|
+
matchElementsInTree(tree) {
|
465
|
+
const match = this.matchElement(tree) ? [tree] : [];
|
466
|
+
const matches = Array.from(tree.querySelectorAll(this.selector));
|
467
|
+
return match.concat(matches);
|
468
|
+
}
|
469
|
+
elementMatched(element) {
|
470
|
+
if (this.delegate.elementMatchedAttribute) {
|
471
|
+
this.delegate.elementMatchedAttribute(element, this.attributeName);
|
472
|
+
}
|
473
|
+
}
|
474
|
+
elementUnmatched(element) {
|
475
|
+
if (this.delegate.elementUnmatchedAttribute) {
|
476
|
+
this.delegate.elementUnmatchedAttribute(element, this.attributeName);
|
477
|
+
}
|
478
|
+
}
|
479
|
+
elementAttributeChanged(element, attributeName) {
|
480
|
+
if (this.delegate.elementAttributeValueChanged && this.attributeName == attributeName) {
|
481
|
+
this.delegate.elementAttributeValueChanged(element, attributeName);
|
482
|
+
}
|
483
|
+
}
|
484
|
+
}
|
485
|
+
|
486
|
+
class StringMapObserver {
|
487
|
+
constructor(element, delegate) {
|
488
|
+
this.element = element;
|
489
|
+
this.delegate = delegate;
|
490
|
+
this.started = false;
|
491
|
+
this.stringMap = new Map;
|
492
|
+
this.mutationObserver = new MutationObserver(mutations => this.processMutations(mutations));
|
493
|
+
}
|
494
|
+
start() {
|
495
|
+
if (!this.started) {
|
496
|
+
this.started = true;
|
497
|
+
this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true });
|
498
|
+
this.refresh();
|
499
|
+
}
|
500
|
+
}
|
501
|
+
stop() {
|
502
|
+
if (this.started) {
|
503
|
+
this.mutationObserver.takeRecords();
|
504
|
+
this.mutationObserver.disconnect();
|
505
|
+
this.started = false;
|
506
|
+
}
|
507
|
+
}
|
508
|
+
refresh() {
|
509
|
+
if (this.started) {
|
510
|
+
for (const attributeName of this.knownAttributeNames) {
|
511
|
+
this.refreshAttribute(attributeName, null);
|
512
|
+
}
|
513
|
+
}
|
514
|
+
}
|
515
|
+
processMutations(mutations) {
|
516
|
+
if (this.started) {
|
517
|
+
for (const mutation of mutations) {
|
518
|
+
this.processMutation(mutation);
|
519
|
+
}
|
520
|
+
}
|
521
|
+
}
|
522
|
+
processMutation(mutation) {
|
523
|
+
const attributeName = mutation.attributeName;
|
524
|
+
if (attributeName) {
|
525
|
+
this.refreshAttribute(attributeName, mutation.oldValue);
|
526
|
+
}
|
527
|
+
}
|
528
|
+
refreshAttribute(attributeName, oldValue) {
|
529
|
+
const key = this.delegate.getStringMapKeyForAttribute(attributeName);
|
530
|
+
if (key != null) {
|
531
|
+
if (!this.stringMap.has(attributeName)) {
|
532
|
+
this.stringMapKeyAdded(key, attributeName);
|
533
|
+
}
|
534
|
+
const value = this.element.getAttribute(attributeName);
|
535
|
+
if (this.stringMap.get(attributeName) != value) {
|
536
|
+
this.stringMapValueChanged(value, key, oldValue);
|
537
|
+
}
|
538
|
+
if (value == null) {
|
539
|
+
const oldValue = this.stringMap.get(attributeName);
|
540
|
+
this.stringMap.delete(attributeName);
|
541
|
+
if (oldValue)
|
542
|
+
this.stringMapKeyRemoved(key, attributeName, oldValue);
|
543
|
+
}
|
544
|
+
else {
|
545
|
+
this.stringMap.set(attributeName, value);
|
546
|
+
}
|
547
|
+
}
|
548
|
+
}
|
549
|
+
stringMapKeyAdded(key, attributeName) {
|
550
|
+
if (this.delegate.stringMapKeyAdded) {
|
551
|
+
this.delegate.stringMapKeyAdded(key, attributeName);
|
552
|
+
}
|
553
|
+
}
|
554
|
+
stringMapValueChanged(value, key, oldValue) {
|
555
|
+
if (this.delegate.stringMapValueChanged) {
|
556
|
+
this.delegate.stringMapValueChanged(value, key, oldValue);
|
557
|
+
}
|
558
|
+
}
|
559
|
+
stringMapKeyRemoved(key, attributeName, oldValue) {
|
560
|
+
if (this.delegate.stringMapKeyRemoved) {
|
561
|
+
this.delegate.stringMapKeyRemoved(key, attributeName, oldValue);
|
562
|
+
}
|
563
|
+
}
|
564
|
+
get knownAttributeNames() {
|
565
|
+
return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
|
566
|
+
}
|
567
|
+
get currentAttributeNames() {
|
568
|
+
return Array.from(this.element.attributes).map(attribute => attribute.name);
|
569
|
+
}
|
570
|
+
get recordedAttributeNames() {
|
571
|
+
return Array.from(this.stringMap.keys());
|
572
|
+
}
|
573
|
+
}
|
574
|
+
|
575
|
+
function add(map, key, value) {
|
576
|
+
fetch(map, key).add(value);
|
577
|
+
}
|
578
|
+
function del(map, key, value) {
|
579
|
+
fetch(map, key).delete(value);
|
580
|
+
prune(map, key);
|
581
|
+
}
|
582
|
+
function fetch(map, key) {
|
583
|
+
let values = map.get(key);
|
584
|
+
if (!values) {
|
585
|
+
values = new Set();
|
586
|
+
map.set(key, values);
|
587
|
+
}
|
588
|
+
return values;
|
589
|
+
}
|
590
|
+
function prune(map, key) {
|
591
|
+
const values = map.get(key);
|
592
|
+
if (values != null && values.size == 0) {
|
593
|
+
map.delete(key);
|
594
|
+
}
|
595
|
+
}
|
596
|
+
|
597
|
+
class Multimap {
|
598
|
+
constructor() {
|
599
|
+
this.valuesByKey = new Map();
|
600
|
+
}
|
601
|
+
get keys() {
|
602
|
+
return Array.from(this.valuesByKey.keys());
|
603
|
+
}
|
604
|
+
get values() {
|
605
|
+
const sets = Array.from(this.valuesByKey.values());
|
606
|
+
return sets.reduce((values, set) => values.concat(Array.from(set)), []);
|
607
|
+
}
|
608
|
+
get size() {
|
609
|
+
const sets = Array.from(this.valuesByKey.values());
|
610
|
+
return sets.reduce((size, set) => size + set.size, 0);
|
611
|
+
}
|
612
|
+
add(key, value) {
|
613
|
+
add(this.valuesByKey, key, value);
|
614
|
+
}
|
615
|
+
delete(key, value) {
|
616
|
+
del(this.valuesByKey, key, value);
|
617
|
+
}
|
618
|
+
has(key, value) {
|
619
|
+
const values = this.valuesByKey.get(key);
|
620
|
+
return values != null && values.has(value);
|
621
|
+
}
|
622
|
+
hasKey(key) {
|
623
|
+
return this.valuesByKey.has(key);
|
624
|
+
}
|
625
|
+
hasValue(value) {
|
626
|
+
const sets = Array.from(this.valuesByKey.values());
|
627
|
+
return sets.some(set => set.has(value));
|
628
|
+
}
|
629
|
+
getValuesForKey(key) {
|
630
|
+
const values = this.valuesByKey.get(key);
|
631
|
+
return values ? Array.from(values) : [];
|
632
|
+
}
|
633
|
+
getKeysForValue(value) {
|
634
|
+
return Array.from(this.valuesByKey)
|
635
|
+
.filter(([key, values]) => values.has(value))
|
636
|
+
.map(([key, values]) => key);
|
637
|
+
}
|
638
|
+
}
|
639
|
+
|
640
|
+
class IndexedMultimap extends Multimap {
|
641
|
+
constructor() {
|
642
|
+
super();
|
643
|
+
this.keysByValue = new Map;
|
644
|
+
}
|
645
|
+
get values() {
|
646
|
+
return Array.from(this.keysByValue.keys());
|
647
|
+
}
|
648
|
+
add(key, value) {
|
649
|
+
super.add(key, value);
|
650
|
+
add(this.keysByValue, value, key);
|
651
|
+
}
|
652
|
+
delete(key, value) {
|
653
|
+
super.delete(key, value);
|
654
|
+
del(this.keysByValue, value, key);
|
655
|
+
}
|
656
|
+
hasValue(value) {
|
657
|
+
return this.keysByValue.has(value);
|
658
|
+
}
|
659
|
+
getKeysForValue(value) {
|
660
|
+
const set = this.keysByValue.get(value);
|
661
|
+
return set ? Array.from(set) : [];
|
662
|
+
}
|
663
|
+
}
|
664
|
+
|
665
|
+
class TokenListObserver {
|
666
|
+
constructor(element, attributeName, delegate) {
|
667
|
+
this.attributeObserver = new AttributeObserver(element, attributeName, this);
|
668
|
+
this.delegate = delegate;
|
669
|
+
this.tokensByElement = new Multimap;
|
670
|
+
}
|
671
|
+
get started() {
|
672
|
+
return this.attributeObserver.started;
|
673
|
+
}
|
674
|
+
start() {
|
675
|
+
this.attributeObserver.start();
|
676
|
+
}
|
677
|
+
stop() {
|
678
|
+
this.attributeObserver.stop();
|
679
|
+
}
|
680
|
+
refresh() {
|
681
|
+
this.attributeObserver.refresh();
|
682
|
+
}
|
683
|
+
get element() {
|
684
|
+
return this.attributeObserver.element;
|
685
|
+
}
|
686
|
+
get attributeName() {
|
687
|
+
return this.attributeObserver.attributeName;
|
688
|
+
}
|
689
|
+
elementMatchedAttribute(element) {
|
690
|
+
this.tokensMatched(this.readTokensForElement(element));
|
691
|
+
}
|
692
|
+
elementAttributeValueChanged(element) {
|
693
|
+
const [unmatchedTokens, matchedTokens] = this.refreshTokensForElement(element);
|
694
|
+
this.tokensUnmatched(unmatchedTokens);
|
695
|
+
this.tokensMatched(matchedTokens);
|
696
|
+
}
|
697
|
+
elementUnmatchedAttribute(element) {
|
698
|
+
this.tokensUnmatched(this.tokensByElement.getValuesForKey(element));
|
699
|
+
}
|
700
|
+
tokensMatched(tokens) {
|
701
|
+
tokens.forEach(token => this.tokenMatched(token));
|
702
|
+
}
|
703
|
+
tokensUnmatched(tokens) {
|
704
|
+
tokens.forEach(token => this.tokenUnmatched(token));
|
705
|
+
}
|
706
|
+
tokenMatched(token) {
|
707
|
+
this.delegate.tokenMatched(token);
|
708
|
+
this.tokensByElement.add(token.element, token);
|
709
|
+
}
|
710
|
+
tokenUnmatched(token) {
|
711
|
+
this.delegate.tokenUnmatched(token);
|
712
|
+
this.tokensByElement.delete(token.element, token);
|
713
|
+
}
|
714
|
+
refreshTokensForElement(element) {
|
715
|
+
const previousTokens = this.tokensByElement.getValuesForKey(element);
|
716
|
+
const currentTokens = this.readTokensForElement(element);
|
717
|
+
const firstDifferingIndex = zip(previousTokens, currentTokens)
|
718
|
+
.findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
|
719
|
+
if (firstDifferingIndex == -1) {
|
720
|
+
return [[], []];
|
721
|
+
}
|
722
|
+
else {
|
723
|
+
return [previousTokens.slice(firstDifferingIndex), currentTokens.slice(firstDifferingIndex)];
|
724
|
+
}
|
725
|
+
}
|
726
|
+
readTokensForElement(element) {
|
727
|
+
const attributeName = this.attributeName;
|
728
|
+
const tokenString = element.getAttribute(attributeName) || "";
|
729
|
+
return parseTokenString(tokenString, element, attributeName);
|
730
|
+
}
|
731
|
+
}
|
732
|
+
function parseTokenString(tokenString, element, attributeName) {
|
733
|
+
return tokenString.trim().split(/\s+/).filter(content => content.length)
|
734
|
+
.map((content, index) => ({ element, attributeName, content, index }));
|
735
|
+
}
|
736
|
+
function zip(left, right) {
|
737
|
+
const length = Math.max(left.length, right.length);
|
738
|
+
return Array.from({ length }, (_, index) => [left[index], right[index]]);
|
739
|
+
}
|
740
|
+
function tokensAreEqual(left, right) {
|
741
|
+
return left && right && left.index == right.index && left.content == right.content;
|
742
|
+
}
|
743
|
+
|
744
|
+
class ValueListObserver {
|
745
|
+
constructor(element, attributeName, delegate) {
|
746
|
+
this.tokenListObserver = new TokenListObserver(element, attributeName, this);
|
747
|
+
this.delegate = delegate;
|
748
|
+
this.parseResultsByToken = new WeakMap;
|
749
|
+
this.valuesByTokenByElement = new WeakMap;
|
750
|
+
}
|
751
|
+
get started() {
|
752
|
+
return this.tokenListObserver.started;
|
753
|
+
}
|
754
|
+
start() {
|
755
|
+
this.tokenListObserver.start();
|
756
|
+
}
|
757
|
+
stop() {
|
758
|
+
this.tokenListObserver.stop();
|
759
|
+
}
|
760
|
+
refresh() {
|
761
|
+
this.tokenListObserver.refresh();
|
762
|
+
}
|
763
|
+
get element() {
|
764
|
+
return this.tokenListObserver.element;
|
765
|
+
}
|
766
|
+
get attributeName() {
|
767
|
+
return this.tokenListObserver.attributeName;
|
768
|
+
}
|
769
|
+
tokenMatched(token) {
|
770
|
+
const { element } = token;
|
771
|
+
const { value } = this.fetchParseResultForToken(token);
|
772
|
+
if (value) {
|
773
|
+
this.fetchValuesByTokenForElement(element).set(token, value);
|
774
|
+
this.delegate.elementMatchedValue(element, value);
|
775
|
+
}
|
776
|
+
else {
|
777
|
+
this.delegate.elementMatchedNoValue(token);
|
778
|
+
}
|
779
|
+
}
|
780
|
+
tokenUnmatched(token) {
|
781
|
+
const { element } = token;
|
782
|
+
const { value } = this.fetchParseResultForToken(token);
|
783
|
+
if (value) {
|
784
|
+
this.fetchValuesByTokenForElement(element).delete(token);
|
785
|
+
this.delegate.elementUnmatchedValue(element, value);
|
786
|
+
}
|
787
|
+
}
|
788
|
+
fetchParseResultForToken(token) {
|
789
|
+
let parseResult = this.parseResultsByToken.get(token);
|
790
|
+
if (!parseResult) {
|
791
|
+
parseResult = this.parseToken(token);
|
792
|
+
this.parseResultsByToken.set(token, parseResult);
|
793
|
+
}
|
794
|
+
return parseResult;
|
795
|
+
}
|
796
|
+
fetchValuesByTokenForElement(element) {
|
797
|
+
let valuesByToken = this.valuesByTokenByElement.get(element);
|
798
|
+
if (!valuesByToken) {
|
799
|
+
valuesByToken = new Map;
|
800
|
+
this.valuesByTokenByElement.set(element, valuesByToken);
|
801
|
+
}
|
802
|
+
return valuesByToken;
|
803
|
+
}
|
804
|
+
parseToken(token) {
|
805
|
+
try {
|
806
|
+
const value = this.delegate.parseValueForToken(token);
|
807
|
+
return { value };
|
808
|
+
}
|
809
|
+
catch (error) {
|
810
|
+
return { error };
|
811
|
+
}
|
812
|
+
}
|
813
|
+
}
|
814
|
+
|
815
|
+
class BindingObserver {
|
816
|
+
constructor(context, delegate) {
|
817
|
+
this.context = context;
|
818
|
+
this.delegate = delegate;
|
819
|
+
this.bindingsByAction = new Map;
|
820
|
+
}
|
821
|
+
start() {
|
822
|
+
if (!this.valueListObserver) {
|
823
|
+
this.valueListObserver = new ValueListObserver(this.element, this.actionAttribute, this);
|
824
|
+
this.valueListObserver.start();
|
825
|
+
}
|
826
|
+
}
|
827
|
+
stop() {
|
828
|
+
if (this.valueListObserver) {
|
829
|
+
this.valueListObserver.stop();
|
830
|
+
delete this.valueListObserver;
|
831
|
+
this.disconnectAllActions();
|
832
|
+
}
|
833
|
+
}
|
834
|
+
get element() {
|
835
|
+
return this.context.element;
|
836
|
+
}
|
837
|
+
get identifier() {
|
838
|
+
return this.context.identifier;
|
839
|
+
}
|
840
|
+
get actionAttribute() {
|
841
|
+
return this.schema.actionAttribute;
|
842
|
+
}
|
843
|
+
get schema() {
|
844
|
+
return this.context.schema;
|
845
|
+
}
|
846
|
+
get bindings() {
|
847
|
+
return Array.from(this.bindingsByAction.values());
|
848
|
+
}
|
849
|
+
connectAction(action) {
|
850
|
+
const binding = new Binding(this.context, action);
|
851
|
+
const { controller } = binding.context;
|
852
|
+
const method = controller[action.methodName];
|
853
|
+
this.bindingsByAction.set(action, binding);
|
854
|
+
this.delegate.bindingConnected(binding);
|
855
|
+
if (typeof method != "function") {
|
856
|
+
this.context.handleWarning(`Action "${action.toString()}" references undefined method "${action.methodName}" on controller "${action.identifier}"`, `connecting action "${action.toString()}"`);
|
857
|
+
}
|
858
|
+
}
|
859
|
+
disconnectAction(action) {
|
860
|
+
const binding = this.bindingsByAction.get(action);
|
861
|
+
if (binding) {
|
862
|
+
this.bindingsByAction.delete(action);
|
863
|
+
this.delegate.bindingDisconnected(binding);
|
864
|
+
}
|
865
|
+
}
|
866
|
+
disconnectAllActions() {
|
867
|
+
this.bindings.forEach(binding => this.delegate.bindingDisconnected(binding));
|
868
|
+
this.bindingsByAction.clear();
|
869
|
+
}
|
870
|
+
parseValueForToken(token) {
|
871
|
+
const action = Action.forToken(token);
|
872
|
+
if (action.identifier == this.identifier) {
|
873
|
+
return action;
|
874
|
+
}
|
875
|
+
}
|
876
|
+
elementMatchedValue(element, action) {
|
877
|
+
this.connectAction(action);
|
878
|
+
}
|
879
|
+
elementUnmatchedValue(element, action) {
|
880
|
+
this.disconnectAction(action);
|
881
|
+
}
|
882
|
+
elementMatchedNoValue(token) {
|
883
|
+
const action = Action.forToken(token);
|
884
|
+
this.context.handleWarning(`Action "${token.content}" references undefined controller "${action.identifier}"`, `connecting action "${token.content}"`);
|
885
|
+
}
|
886
|
+
}
|
887
|
+
|
888
|
+
function readInheritableStaticArrayValues(constructor, propertyName) {
|
889
|
+
const ancestors = getAncestorsForConstructor(constructor);
|
890
|
+
return Array.from(ancestors.reduce((values, constructor) => {
|
891
|
+
getOwnStaticArrayValues(constructor, propertyName).forEach(name => values.add(name));
|
892
|
+
return values;
|
893
|
+
}, new Set));
|
894
|
+
}
|
895
|
+
function readInheritableStaticObjectPairs(constructor, propertyName) {
|
896
|
+
const ancestors = getAncestorsForConstructor(constructor);
|
897
|
+
return ancestors.reduce((pairs, constructor) => {
|
898
|
+
pairs.push(...getOwnStaticObjectPairs(constructor, propertyName));
|
899
|
+
return pairs;
|
900
|
+
}, []);
|
901
|
+
}
|
902
|
+
function getAncestorsForConstructor(constructor) {
|
903
|
+
const ancestors = [];
|
904
|
+
while (constructor) {
|
905
|
+
ancestors.push(constructor);
|
906
|
+
constructor = Object.getPrototypeOf(constructor);
|
907
|
+
}
|
908
|
+
return ancestors.reverse();
|
909
|
+
}
|
910
|
+
function getOwnStaticArrayValues(constructor, propertyName) {
|
911
|
+
const definition = constructor[propertyName];
|
912
|
+
return Array.isArray(definition) ? definition : [];
|
913
|
+
}
|
914
|
+
function getOwnStaticObjectPairs(constructor, propertyName) {
|
915
|
+
const definition = constructor[propertyName];
|
916
|
+
return definition ? Object.keys(definition).map(key => [key, definition[key]]) : [];
|
917
|
+
}
|
918
|
+
|
919
|
+
class TargetGuide {
|
920
|
+
constructor(scope, controller) {
|
921
|
+
this.scope = scope;
|
922
|
+
this.controller = controller;
|
923
|
+
this.definedTargets = readInheritableStaticArrayValues(this.controller.constructor, "targets");
|
924
|
+
this.searchForUndefinedTargets();
|
925
|
+
}
|
926
|
+
get identifier() {
|
927
|
+
return this.scope.identifier;
|
928
|
+
}
|
929
|
+
get targets() {
|
930
|
+
return this.scope.targets;
|
931
|
+
}
|
932
|
+
get registeredControllers() {
|
933
|
+
return this.controller.application.router.modules.map((c) => c.identifier);
|
934
|
+
}
|
935
|
+
controllerRegistered(controllerName) {
|
936
|
+
return this.registeredControllers.includes(controllerName);
|
937
|
+
}
|
938
|
+
targetDefined(targetName) {
|
939
|
+
return this.definedTargets.includes(targetName);
|
940
|
+
}
|
941
|
+
getAllTargets(element) {
|
942
|
+
const attribute = `data-${this.identifier}-target`;
|
943
|
+
const selector = `[${attribute}]`;
|
944
|
+
return Array.from(element.querySelectorAll(selector)).map((element) => {
|
945
|
+
const target = element.getAttribute(attribute);
|
946
|
+
return {
|
947
|
+
identifier: this.identifier,
|
948
|
+
target,
|
949
|
+
element,
|
950
|
+
attribute,
|
951
|
+
legacy: false
|
952
|
+
};
|
953
|
+
});
|
954
|
+
}
|
955
|
+
getAllLegacyTargets(element) {
|
956
|
+
const attribute = "data-target";
|
957
|
+
const selector = `[${attribute}]`;
|
958
|
+
return Array.from(element.querySelectorAll(selector)).map((element) => {
|
959
|
+
const value = element.getAttribute(attribute);
|
960
|
+
const parts = value ? value.split(".") : [];
|
961
|
+
return {
|
962
|
+
identifier: parts[0],
|
963
|
+
target: parts[1],
|
964
|
+
element,
|
965
|
+
attribute,
|
966
|
+
legacy: true
|
967
|
+
};
|
968
|
+
});
|
969
|
+
}
|
970
|
+
searchForUndefinedTargets() {
|
971
|
+
const { element } = this.scope;
|
972
|
+
const targets = [
|
973
|
+
...this.getAllTargets(element),
|
974
|
+
...this.getAllLegacyTargets(element)
|
975
|
+
];
|
976
|
+
targets.forEach((descriptor) => {
|
977
|
+
const { identifier, attribute, target, legacy, element } = descriptor;
|
978
|
+
if (identifier && target) {
|
979
|
+
this.handleWarningForUndefinedTarget(descriptor);
|
980
|
+
}
|
981
|
+
else if (identifier && !target) {
|
982
|
+
this.controller.context.handleWarning(`The "${attribute}" attribute of the Element doesn't include a target. Please specify a target for the "${identifier}" controller.`, `connecting target for "${identifier}"`, { identifier, target, attribute, element });
|
983
|
+
}
|
984
|
+
else if (legacy && !target) {
|
985
|
+
this.controller.context.handleWarning(`The "${attribute}" attribute of the Element doesn't include a value. Please specify a controller and target value in the right format.`, `connecting target`, { identifier, target, attribute, element });
|
986
|
+
}
|
987
|
+
});
|
988
|
+
}
|
989
|
+
handleWarningForUndefinedTarget(descriptor) {
|
990
|
+
const { identifier, target, element, attribute } = descriptor;
|
991
|
+
if (identifier === this.identifier) {
|
992
|
+
if (!this.targetDefined(target)) {
|
993
|
+
this.controller.context.handleWarning(`Element references undefined target "${target}" for controller "${identifier}". Make sure you defined the target "${target}" in the "static targets" array of your controller.`, `connecting target "${identifier}.${target}"`, { identifier, target, element, attribute });
|
994
|
+
}
|
995
|
+
}
|
996
|
+
else {
|
997
|
+
if (!this.controllerRegistered(identifier)) {
|
998
|
+
this.controller.context.handleWarning(`Target "${target}" references undefined controller "${identifier}". Make sure you registered the "${identifier}" controller.`, `connecting target "${identifier}.${target}"`, { identifier, target, element, attribute });
|
999
|
+
}
|
1000
|
+
}
|
1001
|
+
}
|
1002
|
+
}
|
1003
|
+
|
1004
|
+
class ValueObserver {
|
1005
|
+
constructor(context, receiver) {
|
1006
|
+
this.context = context;
|
1007
|
+
this.receiver = receiver;
|
1008
|
+
this.stringMapObserver = new StringMapObserver(this.element, this);
|
1009
|
+
this.valueDescriptorMap = this.controller.valueDescriptorMap;
|
1010
|
+
this.invokeChangedCallbacksForDefaultValues();
|
1011
|
+
}
|
1012
|
+
start() {
|
1013
|
+
this.stringMapObserver.start();
|
1014
|
+
}
|
1015
|
+
stop() {
|
1016
|
+
this.stringMapObserver.stop();
|
1017
|
+
}
|
1018
|
+
get element() {
|
1019
|
+
return this.context.element;
|
1020
|
+
}
|
1021
|
+
get controller() {
|
1022
|
+
return this.context.controller;
|
1023
|
+
}
|
1024
|
+
getStringMapKeyForAttribute(attributeName) {
|
1025
|
+
if (attributeName in this.valueDescriptorMap) {
|
1026
|
+
return this.valueDescriptorMap[attributeName].name;
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
stringMapKeyAdded(key, attributeName) {
|
1030
|
+
const descriptor = this.valueDescriptorMap[attributeName];
|
1031
|
+
if (!this.hasValue(key)) {
|
1032
|
+
this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), descriptor.writer(descriptor.defaultValue));
|
1033
|
+
}
|
1034
|
+
}
|
1035
|
+
stringMapValueChanged(value, name, oldValue) {
|
1036
|
+
const descriptor = this.valueDescriptorNameMap[name];
|
1037
|
+
if (value === null)
|
1038
|
+
return;
|
1039
|
+
if (oldValue === null) {
|
1040
|
+
oldValue = descriptor.writer(descriptor.defaultValue);
|
1041
|
+
}
|
1042
|
+
this.invokeChangedCallback(name, value, oldValue);
|
1043
|
+
}
|
1044
|
+
stringMapKeyRemoved(key, attributeName, oldValue) {
|
1045
|
+
const descriptor = this.valueDescriptorNameMap[key];
|
1046
|
+
if (this.hasValue(key)) {
|
1047
|
+
this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), oldValue);
|
1048
|
+
}
|
1049
|
+
else {
|
1050
|
+
this.invokeChangedCallback(key, descriptor.writer(descriptor.defaultValue), oldValue);
|
1051
|
+
}
|
1052
|
+
}
|
1053
|
+
invokeChangedCallbacksForDefaultValues() {
|
1054
|
+
for (const { key, name, defaultValue, writer } of this.valueDescriptors) {
|
1055
|
+
if (defaultValue != undefined && !this.controller.data.has(key)) {
|
1056
|
+
this.invokeChangedCallback(name, writer(defaultValue), undefined);
|
1057
|
+
}
|
1058
|
+
}
|
1059
|
+
}
|
1060
|
+
invokeChangedCallback(name, rawValue, rawOldValue) {
|
1061
|
+
const changedMethodName = `${name}Changed`;
|
1062
|
+
const changedMethod = this.receiver[changedMethodName];
|
1063
|
+
if (typeof changedMethod == "function") {
|
1064
|
+
const descriptor = this.valueDescriptorNameMap[name];
|
1065
|
+
const value = descriptor.reader(rawValue);
|
1066
|
+
let oldValue = rawOldValue;
|
1067
|
+
if (rawOldValue) {
|
1068
|
+
oldValue = descriptor.reader(rawOldValue);
|
1069
|
+
}
|
1070
|
+
changedMethod.call(this.receiver, value, oldValue);
|
1071
|
+
}
|
1072
|
+
}
|
1073
|
+
get valueDescriptors() {
|
1074
|
+
const { valueDescriptorMap } = this;
|
1075
|
+
return Object.keys(valueDescriptorMap).map(key => valueDescriptorMap[key]);
|
1076
|
+
}
|
1077
|
+
get valueDescriptorNameMap() {
|
1078
|
+
const descriptors = {};
|
1079
|
+
Object.keys(this.valueDescriptorMap).forEach(key => {
|
1080
|
+
const descriptor = this.valueDescriptorMap[key];
|
1081
|
+
descriptors[descriptor.name] = descriptor;
|
1082
|
+
});
|
1083
|
+
return descriptors;
|
1084
|
+
}
|
1085
|
+
hasValue(attributeName) {
|
1086
|
+
const descriptor = this.valueDescriptorNameMap[attributeName];
|
1087
|
+
const hasMethodName = `has${capitalize(descriptor.name)}`;
|
1088
|
+
return this.receiver[hasMethodName];
|
1089
|
+
}
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
class TargetObserver {
|
1093
|
+
constructor(context, delegate) {
|
1094
|
+
this.context = context;
|
1095
|
+
this.delegate = delegate;
|
1096
|
+
this.targetsByName = new Multimap;
|
1097
|
+
}
|
1098
|
+
start() {
|
1099
|
+
if (!this.tokenListObserver) {
|
1100
|
+
this.tokenListObserver = new TokenListObserver(this.element, this.attributeName, this);
|
1101
|
+
this.tokenListObserver.start();
|
1102
|
+
}
|
1103
|
+
}
|
1104
|
+
stop() {
|
1105
|
+
if (this.tokenListObserver) {
|
1106
|
+
this.disconnectAllTargets();
|
1107
|
+
this.tokenListObserver.stop();
|
1108
|
+
delete this.tokenListObserver;
|
1109
|
+
}
|
1110
|
+
}
|
1111
|
+
tokenMatched({ element, content: name }) {
|
1112
|
+
if (this.scope.containsElement(element)) {
|
1113
|
+
this.connectTarget(element, name);
|
1114
|
+
}
|
1115
|
+
}
|
1116
|
+
tokenUnmatched({ element, content: name }) {
|
1117
|
+
this.disconnectTarget(element, name);
|
1118
|
+
}
|
1119
|
+
connectTarget(element, name) {
|
1120
|
+
if (!this.targetsByName.has(name, element)) {
|
1121
|
+
this.targetsByName.add(name, element);
|
1122
|
+
this.delegate.targetConnected(element, name);
|
1123
|
+
}
|
1124
|
+
}
|
1125
|
+
disconnectTarget(element, name) {
|
1126
|
+
if (this.targetsByName.has(name, element)) {
|
1127
|
+
this.targetsByName.delete(name, element);
|
1128
|
+
this.delegate.targetDisconnected(element, name);
|
1129
|
+
}
|
1130
|
+
}
|
1131
|
+
disconnectAllTargets() {
|
1132
|
+
for (const name of this.targetsByName.keys) {
|
1133
|
+
for (const element of this.targetsByName.getValuesForKey(name)) {
|
1134
|
+
this.disconnectTarget(element, name);
|
1135
|
+
}
|
1136
|
+
}
|
1137
|
+
}
|
1138
|
+
get attributeName() {
|
1139
|
+
return `data-${this.context.identifier}-target`;
|
1140
|
+
}
|
1141
|
+
get element() {
|
1142
|
+
return this.context.element;
|
1143
|
+
}
|
1144
|
+
get scope() {
|
1145
|
+
return this.context.scope;
|
1146
|
+
}
|
1147
|
+
}
|
1148
|
+
|
1149
|
+
class Context {
|
1150
|
+
constructor(module, scope) {
|
1151
|
+
this.logDebugActivity = (functionName, detail = {}) => {
|
1152
|
+
const { identifier, controller, element } = this;
|
1153
|
+
detail = Object.assign({ identifier, controller, element }, detail);
|
1154
|
+
this.application.logDebugActivity(this.identifier, functionName, detail);
|
1155
|
+
};
|
1156
|
+
this.module = module;
|
1157
|
+
this.scope = scope;
|
1158
|
+
this.controller = new module.controllerConstructor(this);
|
1159
|
+
this.targetGuide = new TargetGuide(this.scope, this.controller);
|
1160
|
+
this.bindingObserver = new BindingObserver(this, this.dispatcher);
|
1161
|
+
this.valueObserver = new ValueObserver(this, this.controller);
|
1162
|
+
this.targetObserver = new TargetObserver(this, this);
|
1163
|
+
try {
|
1164
|
+
this.controller.initialize();
|
1165
|
+
this.logDebugActivity("initialize");
|
1166
|
+
}
|
1167
|
+
catch (error) {
|
1168
|
+
this.handleError(error, "initializing controller");
|
1169
|
+
}
|
1170
|
+
}
|
1171
|
+
connect() {
|
1172
|
+
this.bindingObserver.start();
|
1173
|
+
this.valueObserver.start();
|
1174
|
+
this.targetObserver.start();
|
1175
|
+
try {
|
1176
|
+
this.controller.connect();
|
1177
|
+
this.logDebugActivity("connect");
|
1178
|
+
}
|
1179
|
+
catch (error) {
|
1180
|
+
this.handleError(error, "connecting controller");
|
1181
|
+
}
|
1182
|
+
}
|
1183
|
+
disconnect() {
|
1184
|
+
try {
|
1185
|
+
this.controller.disconnect();
|
1186
|
+
this.logDebugActivity("disconnect");
|
1187
|
+
}
|
1188
|
+
catch (error) {
|
1189
|
+
this.handleError(error, "disconnecting controller");
|
1190
|
+
}
|
1191
|
+
this.targetObserver.stop();
|
1192
|
+
this.valueObserver.stop();
|
1193
|
+
this.bindingObserver.stop();
|
1194
|
+
}
|
1195
|
+
get application() {
|
1196
|
+
return this.module.application;
|
1197
|
+
}
|
1198
|
+
get identifier() {
|
1199
|
+
return this.module.identifier;
|
1200
|
+
}
|
1201
|
+
get schema() {
|
1202
|
+
return this.application.schema;
|
1203
|
+
}
|
1204
|
+
get dispatcher() {
|
1205
|
+
return this.application.dispatcher;
|
1206
|
+
}
|
1207
|
+
get element() {
|
1208
|
+
return this.scope.element;
|
1209
|
+
}
|
1210
|
+
get parentElement() {
|
1211
|
+
return this.element.parentElement;
|
1212
|
+
}
|
1213
|
+
handleError(error, message, detail = {}) {
|
1214
|
+
const { identifier, controller, element } = this;
|
1215
|
+
detail = Object.assign({ identifier, controller, element }, detail);
|
1216
|
+
this.application.handleError(error, `Error ${message}`, detail);
|
1217
|
+
}
|
1218
|
+
handleWarning(warning, message, detail = {}) {
|
1219
|
+
const { identifier, controller, element } = this;
|
1220
|
+
detail = Object.assign({ identifier, controller, element }, detail);
|
1221
|
+
this.application.handleWarning(warning, `Warning ${message}`, detail);
|
1222
|
+
}
|
1223
|
+
targetConnected(element, name) {
|
1224
|
+
this.invokeControllerMethod(`${name}TargetConnected`, element);
|
1225
|
+
}
|
1226
|
+
targetDisconnected(element, name) {
|
1227
|
+
this.invokeControllerMethod(`${name}TargetDisconnected`, element);
|
1228
|
+
}
|
1229
|
+
invokeControllerMethod(methodName, ...args) {
|
1230
|
+
const controller = this.controller;
|
1231
|
+
if (typeof controller[methodName] == "function") {
|
1232
|
+
controller[methodName](...args);
|
1233
|
+
}
|
1234
|
+
}
|
1235
|
+
}
|
1236
|
+
|
1237
|
+
function bless(constructor) {
|
1238
|
+
return shadow(constructor, getBlessedProperties(constructor));
|
1239
|
+
}
|
1240
|
+
function shadow(constructor, properties) {
|
1241
|
+
const shadowConstructor = extend(constructor);
|
1242
|
+
const shadowProperties = getShadowProperties(constructor.prototype, properties);
|
1243
|
+
Object.defineProperties(shadowConstructor.prototype, shadowProperties);
|
1244
|
+
return shadowConstructor;
|
1245
|
+
}
|
1246
|
+
function getBlessedProperties(constructor) {
|
1247
|
+
const blessings = readInheritableStaticArrayValues(constructor, "blessings");
|
1248
|
+
return blessings.reduce((blessedProperties, blessing) => {
|
1249
|
+
const properties = blessing(constructor);
|
1250
|
+
for (const key in properties) {
|
1251
|
+
const descriptor = blessedProperties[key] || {};
|
1252
|
+
blessedProperties[key] = Object.assign(descriptor, properties[key]);
|
1253
|
+
}
|
1254
|
+
return blessedProperties;
|
1255
|
+
}, {});
|
1256
|
+
}
|
1257
|
+
function getShadowProperties(prototype, properties) {
|
1258
|
+
return getOwnKeys(properties).reduce((shadowProperties, key) => {
|
1259
|
+
const descriptor = getShadowedDescriptor(prototype, properties, key);
|
1260
|
+
if (descriptor) {
|
1261
|
+
Object.assign(shadowProperties, { [key]: descriptor });
|
1262
|
+
}
|
1263
|
+
return shadowProperties;
|
1264
|
+
}, {});
|
1265
|
+
}
|
1266
|
+
function getShadowedDescriptor(prototype, properties, key) {
|
1267
|
+
const shadowingDescriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
1268
|
+
const shadowedByValue = shadowingDescriptor && "value" in shadowingDescriptor;
|
1269
|
+
if (!shadowedByValue) {
|
1270
|
+
const descriptor = Object.getOwnPropertyDescriptor(properties, key).value;
|
1271
|
+
if (shadowingDescriptor) {
|
1272
|
+
descriptor.get = shadowingDescriptor.get || descriptor.get;
|
1273
|
+
descriptor.set = shadowingDescriptor.set || descriptor.set;
|
1274
|
+
}
|
1275
|
+
return descriptor;
|
1276
|
+
}
|
1277
|
+
}
|
1278
|
+
const getOwnKeys = (() => {
|
1279
|
+
if (typeof Object.getOwnPropertySymbols == "function") {
|
1280
|
+
return (object) => [
|
1281
|
+
...Object.getOwnPropertyNames(object),
|
1282
|
+
...Object.getOwnPropertySymbols(object)
|
1283
|
+
];
|
1284
|
+
}
|
1285
|
+
else {
|
1286
|
+
return Object.getOwnPropertyNames;
|
1287
|
+
}
|
1288
|
+
})();
|
1289
|
+
const extend = (() => {
|
1290
|
+
function extendWithReflect(constructor) {
|
1291
|
+
function extended() {
|
1292
|
+
return Reflect.construct(constructor, arguments, new.target);
|
1293
|
+
}
|
1294
|
+
extended.prototype = Object.create(constructor.prototype, {
|
1295
|
+
constructor: { value: extended }
|
1296
|
+
});
|
1297
|
+
Reflect.setPrototypeOf(extended, constructor);
|
1298
|
+
return extended;
|
1299
|
+
}
|
1300
|
+
function testReflectExtension() {
|
1301
|
+
const a = function () { this.a.call(this); };
|
1302
|
+
const b = extendWithReflect(a);
|
1303
|
+
b.prototype.a = function () { };
|
1304
|
+
return new b;
|
1305
|
+
}
|
1306
|
+
try {
|
1307
|
+
testReflectExtension();
|
1308
|
+
return extendWithReflect;
|
1309
|
+
}
|
1310
|
+
catch (error) {
|
1311
|
+
return (constructor) => class extended extends constructor {
|
1312
|
+
};
|
1313
|
+
}
|
1314
|
+
})();
|
1315
|
+
|
1316
|
+
function blessDefinition(definition) {
|
1317
|
+
return {
|
1318
|
+
identifier: definition.identifier,
|
1319
|
+
controllerConstructor: bless(definition.controllerConstructor)
|
1320
|
+
};
|
1321
|
+
}
|
1322
|
+
|
1323
|
+
class Module {
|
1324
|
+
constructor(application, definition) {
|
1325
|
+
this.application = application;
|
1326
|
+
this.definition = blessDefinition(definition);
|
1327
|
+
this.contextsByScope = new WeakMap;
|
1328
|
+
this.connectedContexts = new Set;
|
1329
|
+
}
|
1330
|
+
get identifier() {
|
1331
|
+
return this.definition.identifier;
|
1332
|
+
}
|
1333
|
+
get controllerConstructor() {
|
1334
|
+
return this.definition.controllerConstructor;
|
1335
|
+
}
|
1336
|
+
get contexts() {
|
1337
|
+
return Array.from(this.connectedContexts);
|
1338
|
+
}
|
1339
|
+
connectContextForScope(scope) {
|
1340
|
+
const context = this.fetchContextForScope(scope);
|
1341
|
+
this.connectedContexts.add(context);
|
1342
|
+
context.connect();
|
1343
|
+
}
|
1344
|
+
disconnectContextForScope(scope) {
|
1345
|
+
const context = this.contextsByScope.get(scope);
|
1346
|
+
if (context) {
|
1347
|
+
this.connectedContexts.delete(context);
|
1348
|
+
context.disconnect();
|
1349
|
+
}
|
1350
|
+
}
|
1351
|
+
fetchContextForScope(scope) {
|
1352
|
+
let context = this.contextsByScope.get(scope);
|
1353
|
+
if (!context) {
|
1354
|
+
context = new Context(this, scope);
|
1355
|
+
this.contextsByScope.set(scope, context);
|
1356
|
+
}
|
1357
|
+
return context;
|
1358
|
+
}
|
1359
|
+
}
|
1360
|
+
|
1361
|
+
class ClassMap {
|
1362
|
+
constructor(scope) {
|
1363
|
+
this.scope = scope;
|
1364
|
+
}
|
1365
|
+
has(name) {
|
1366
|
+
return this.data.has(this.getDataKey(name));
|
1367
|
+
}
|
1368
|
+
get(name) {
|
1369
|
+
return this.getAll(name)[0];
|
1370
|
+
}
|
1371
|
+
getAll(name) {
|
1372
|
+
const tokenString = this.data.get(this.getDataKey(name)) || "";
|
1373
|
+
return tokenize(tokenString);
|
1374
|
+
}
|
1375
|
+
getAttributeName(name) {
|
1376
|
+
return this.data.getAttributeNameForKey(this.getDataKey(name));
|
1377
|
+
}
|
1378
|
+
getDataKey(name) {
|
1379
|
+
return `${name}-class`;
|
1380
|
+
}
|
1381
|
+
get data() {
|
1382
|
+
return this.scope.data;
|
1383
|
+
}
|
1384
|
+
}
|
1385
|
+
|
1386
|
+
class DataMap {
|
1387
|
+
constructor(scope) {
|
1388
|
+
this.scope = scope;
|
1389
|
+
}
|
1390
|
+
get element() {
|
1391
|
+
return this.scope.element;
|
1392
|
+
}
|
1393
|
+
get identifier() {
|
1394
|
+
return this.scope.identifier;
|
1395
|
+
}
|
1396
|
+
get(key) {
|
1397
|
+
const name = this.getAttributeNameForKey(key);
|
1398
|
+
return this.element.getAttribute(name);
|
1399
|
+
}
|
1400
|
+
set(key, value) {
|
1401
|
+
const name = this.getAttributeNameForKey(key);
|
1402
|
+
this.element.setAttribute(name, value);
|
1403
|
+
return this.get(key);
|
1404
|
+
}
|
1405
|
+
has(key) {
|
1406
|
+
const name = this.getAttributeNameForKey(key);
|
1407
|
+
return this.element.hasAttribute(name);
|
1408
|
+
}
|
1409
|
+
delete(key) {
|
1410
|
+
if (this.has(key)) {
|
1411
|
+
const name = this.getAttributeNameForKey(key);
|
1412
|
+
this.element.removeAttribute(name);
|
1413
|
+
return true;
|
1414
|
+
}
|
1415
|
+
else {
|
1416
|
+
return false;
|
1417
|
+
}
|
1418
|
+
}
|
1419
|
+
getAttributeNameForKey(key) {
|
1420
|
+
return `data-${this.identifier}-${dasherize(key)}`;
|
1421
|
+
}
|
1422
|
+
}
|
1423
|
+
|
1424
|
+
class Guide {
|
1425
|
+
constructor(logger) {
|
1426
|
+
this.warnedKeysByObject = new WeakMap;
|
1427
|
+
this.logger = logger;
|
1428
|
+
}
|
1429
|
+
warn(object, key, message) {
|
1430
|
+
let warnedKeys = this.warnedKeysByObject.get(object);
|
1431
|
+
if (!warnedKeys) {
|
1432
|
+
warnedKeys = new Set;
|
1433
|
+
this.warnedKeysByObject.set(object, warnedKeys);
|
1434
|
+
}
|
1435
|
+
if (!warnedKeys.has(key)) {
|
1436
|
+
warnedKeys.add(key);
|
1437
|
+
this.logger.warn(message, object);
|
1438
|
+
}
|
1439
|
+
}
|
1440
|
+
}
|
1441
|
+
|
1442
|
+
function attributeValueContainsToken(attributeName, token) {
|
1443
|
+
return `[${attributeName}~="${token}"]`;
|
1444
|
+
}
|
1445
|
+
|
1446
|
+
class TargetSet {
|
1447
|
+
constructor(scope) {
|
1448
|
+
this.scope = scope;
|
1449
|
+
}
|
1450
|
+
get element() {
|
1451
|
+
return this.scope.element;
|
1452
|
+
}
|
1453
|
+
get identifier() {
|
1454
|
+
return this.scope.identifier;
|
1455
|
+
}
|
1456
|
+
get schema() {
|
1457
|
+
return this.scope.schema;
|
1458
|
+
}
|
1459
|
+
has(targetName) {
|
1460
|
+
return this.find(targetName) != null;
|
1461
|
+
}
|
1462
|
+
find(...targetNames) {
|
1463
|
+
return targetNames.reduce((target, targetName) => target
|
1464
|
+
|| this.findTarget(targetName)
|
1465
|
+
|| this.findLegacyTarget(targetName), undefined);
|
1466
|
+
}
|
1467
|
+
findAll(...targetNames) {
|
1468
|
+
return targetNames.reduce((targets, targetName) => [
|
1469
|
+
...targets,
|
1470
|
+
...this.findAllTargets(targetName),
|
1471
|
+
...this.findAllLegacyTargets(targetName)
|
1472
|
+
], []);
|
1473
|
+
}
|
1474
|
+
findTarget(targetName) {
|
1475
|
+
const selector = this.getSelectorForTargetName(targetName);
|
1476
|
+
return this.scope.findElement(selector);
|
1477
|
+
}
|
1478
|
+
findAllTargets(targetName) {
|
1479
|
+
const selector = this.getSelectorForTargetName(targetName);
|
1480
|
+
return this.scope.findAllElements(selector);
|
1481
|
+
}
|
1482
|
+
getSelectorForTargetName(targetName) {
|
1483
|
+
const attributeName = this.schema.targetAttributeForScope(this.identifier);
|
1484
|
+
return attributeValueContainsToken(attributeName, targetName);
|
1485
|
+
}
|
1486
|
+
findLegacyTarget(targetName) {
|
1487
|
+
const selector = this.getLegacySelectorForTargetName(targetName);
|
1488
|
+
return this.deprecate(this.scope.findElement(selector), targetName);
|
1489
|
+
}
|
1490
|
+
findAllLegacyTargets(targetName) {
|
1491
|
+
const selector = this.getLegacySelectorForTargetName(targetName);
|
1492
|
+
return this.scope.findAllElements(selector).map(element => this.deprecate(element, targetName));
|
1493
|
+
}
|
1494
|
+
getLegacySelectorForTargetName(targetName) {
|
1495
|
+
const targetDescriptor = `${this.identifier}.${targetName}`;
|
1496
|
+
return attributeValueContainsToken(this.schema.targetAttribute, targetDescriptor);
|
1497
|
+
}
|
1498
|
+
deprecate(element, targetName) {
|
1499
|
+
if (element) {
|
1500
|
+
const { identifier } = this;
|
1501
|
+
const attributeName = this.schema.targetAttribute;
|
1502
|
+
const revisedAttributeName = this.schema.targetAttributeForScope(identifier);
|
1503
|
+
this.guide.warn(element, `target:${targetName}`, `Please replace ${attributeName}="${identifier}.${targetName}" with ${revisedAttributeName}="${targetName}". ` +
|
1504
|
+
`The ${attributeName} attribute is deprecated and will be removed in a future version of Stimulus.`);
|
1505
|
+
}
|
1506
|
+
return element;
|
1507
|
+
}
|
1508
|
+
get guide() {
|
1509
|
+
return this.scope.guide;
|
1510
|
+
}
|
1511
|
+
}
|
1512
|
+
|
1513
|
+
class Scope {
|
1514
|
+
constructor(schema, element, identifier, logger) {
|
1515
|
+
this.targets = new TargetSet(this);
|
1516
|
+
this.classes = new ClassMap(this);
|
1517
|
+
this.data = new DataMap(this);
|
1518
|
+
this.containsElement = (element) => {
|
1519
|
+
return element.closest(this.controllerSelector) === this.element;
|
1520
|
+
};
|
1521
|
+
this.schema = schema;
|
1522
|
+
this.element = element;
|
1523
|
+
this.identifier = identifier;
|
1524
|
+
this.guide = new Guide(logger);
|
1525
|
+
}
|
1526
|
+
findElement(selector) {
|
1527
|
+
return this.element.matches(selector)
|
1528
|
+
? this.element
|
1529
|
+
: this.queryElements(selector).find(this.containsElement);
|
1530
|
+
}
|
1531
|
+
findAllElements(selector) {
|
1532
|
+
return [
|
1533
|
+
...this.element.matches(selector) ? [this.element] : [],
|
1534
|
+
...this.queryElements(selector).filter(this.containsElement)
|
1535
|
+
];
|
1536
|
+
}
|
1537
|
+
queryElements(selector) {
|
1538
|
+
return Array.from(this.element.querySelectorAll(selector));
|
1539
|
+
}
|
1540
|
+
get controllerSelector() {
|
1541
|
+
return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier);
|
1542
|
+
}
|
1543
|
+
}
|
1544
|
+
|
1545
|
+
class ScopeObserver {
|
1546
|
+
constructor(element, schema, delegate) {
|
1547
|
+
this.element = element;
|
1548
|
+
this.schema = schema;
|
1549
|
+
this.delegate = delegate;
|
1550
|
+
this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this);
|
1551
|
+
this.scopesByIdentifierByElement = new WeakMap;
|
1552
|
+
this.scopeReferenceCounts = new WeakMap;
|
1553
|
+
}
|
1554
|
+
start() {
|
1555
|
+
this.valueListObserver.start();
|
1556
|
+
}
|
1557
|
+
stop() {
|
1558
|
+
this.valueListObserver.stop();
|
1559
|
+
}
|
1560
|
+
get controllerAttribute() {
|
1561
|
+
return this.schema.controllerAttribute;
|
1562
|
+
}
|
1563
|
+
parseValueForToken(token) {
|
1564
|
+
const { element, content: identifier } = token;
|
1565
|
+
const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
|
1566
|
+
let scope = scopesByIdentifier.get(identifier);
|
1567
|
+
if (!scope) {
|
1568
|
+
scope = this.delegate.createScopeForElementAndIdentifier(element, identifier);
|
1569
|
+
scopesByIdentifier.set(identifier, scope);
|
1570
|
+
}
|
1571
|
+
return scope;
|
1572
|
+
}
|
1573
|
+
elementMatchedValue(element, value) {
|
1574
|
+
const referenceCount = (this.scopeReferenceCounts.get(value) || 0) + 1;
|
1575
|
+
this.scopeReferenceCounts.set(value, referenceCount);
|
1576
|
+
if (referenceCount == 1) {
|
1577
|
+
this.delegate.scopeConnected(value);
|
1578
|
+
}
|
1579
|
+
}
|
1580
|
+
elementUnmatchedValue(element, value) {
|
1581
|
+
const referenceCount = this.scopeReferenceCounts.get(value);
|
1582
|
+
if (referenceCount) {
|
1583
|
+
this.scopeReferenceCounts.set(value, referenceCount - 1);
|
1584
|
+
if (referenceCount == 1) {
|
1585
|
+
this.delegate.scopeDisconnected(value);
|
1586
|
+
}
|
1587
|
+
}
|
1588
|
+
}
|
1589
|
+
elementMatchedNoValue(token) { }
|
1590
|
+
fetchScopesByIdentifierForElement(element) {
|
1591
|
+
let scopesByIdentifier = this.scopesByIdentifierByElement.get(element);
|
1592
|
+
if (!scopesByIdentifier) {
|
1593
|
+
scopesByIdentifier = new Map;
|
1594
|
+
this.scopesByIdentifierByElement.set(element, scopesByIdentifier);
|
1595
|
+
}
|
1596
|
+
return scopesByIdentifier;
|
1597
|
+
}
|
1598
|
+
}
|
1599
|
+
|
1600
|
+
class Router {
|
1601
|
+
constructor(application) {
|
1602
|
+
this.application = application;
|
1603
|
+
this.scopeObserver = new ScopeObserver(this.element, this.schema, this);
|
1604
|
+
this.scopesByIdentifier = new Multimap;
|
1605
|
+
this.modulesByIdentifier = new Map;
|
1606
|
+
}
|
1607
|
+
get element() {
|
1608
|
+
return this.application.element;
|
1609
|
+
}
|
1610
|
+
get schema() {
|
1611
|
+
return this.application.schema;
|
1612
|
+
}
|
1613
|
+
get logger() {
|
1614
|
+
return this.application.logger;
|
1615
|
+
}
|
1616
|
+
get controllerAttribute() {
|
1617
|
+
return this.schema.controllerAttribute;
|
1618
|
+
}
|
1619
|
+
get modules() {
|
1620
|
+
return Array.from(this.modulesByIdentifier.values());
|
1621
|
+
}
|
1622
|
+
get contexts() {
|
1623
|
+
return this.modules.reduce((contexts, module) => contexts.concat(module.contexts), []);
|
1624
|
+
}
|
1625
|
+
start() {
|
1626
|
+
this.scopeObserver.start();
|
1627
|
+
}
|
1628
|
+
stop() {
|
1629
|
+
this.scopeObserver.stop();
|
1630
|
+
}
|
1631
|
+
loadDefinition(definition) {
|
1632
|
+
this.unloadIdentifier(definition.identifier);
|
1633
|
+
const module = new Module(this.application, definition);
|
1634
|
+
this.connectModule(module);
|
1635
|
+
}
|
1636
|
+
unloadIdentifier(identifier) {
|
1637
|
+
const module = this.modulesByIdentifier.get(identifier);
|
1638
|
+
if (module) {
|
1639
|
+
this.disconnectModule(module);
|
1640
|
+
}
|
1641
|
+
}
|
1642
|
+
getContextForElementAndIdentifier(element, identifier) {
|
1643
|
+
const module = this.modulesByIdentifier.get(identifier);
|
1644
|
+
if (module) {
|
1645
|
+
return module.contexts.find(context => context.element == element);
|
1646
|
+
}
|
1647
|
+
}
|
1648
|
+
handleError(error, message, detail) {
|
1649
|
+
this.application.handleError(error, message, detail);
|
1650
|
+
}
|
1651
|
+
createScopeForElementAndIdentifier(element, identifier) {
|
1652
|
+
return new Scope(this.schema, element, identifier, this.logger);
|
1653
|
+
}
|
1654
|
+
scopeConnected(scope) {
|
1655
|
+
const { identifier, element } = scope;
|
1656
|
+
this.scopesByIdentifier.add(identifier, scope);
|
1657
|
+
const module = this.modulesByIdentifier.get(identifier);
|
1658
|
+
if (module) {
|
1659
|
+
module.connectContextForScope(scope);
|
1660
|
+
}
|
1661
|
+
else {
|
1662
|
+
this.application.handleWarning(`Element references undefined controller "${identifier}"`, `Warning connecting controller "${identifier}"`, { identifier, element });
|
1663
|
+
}
|
1664
|
+
}
|
1665
|
+
scopeDisconnected(scope) {
|
1666
|
+
this.scopesByIdentifier.delete(scope.identifier, scope);
|
1667
|
+
const module = this.modulesByIdentifier.get(scope.identifier);
|
1668
|
+
if (module) {
|
1669
|
+
module.disconnectContextForScope(scope);
|
1670
|
+
}
|
1671
|
+
}
|
1672
|
+
connectModule(module) {
|
1673
|
+
this.modulesByIdentifier.set(module.identifier, module);
|
1674
|
+
const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
|
1675
|
+
scopes.forEach(scope => module.connectContextForScope(scope));
|
1676
|
+
}
|
1677
|
+
disconnectModule(module) {
|
1678
|
+
this.modulesByIdentifier.delete(module.identifier);
|
1679
|
+
const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
|
1680
|
+
scopes.forEach(scope => module.disconnectContextForScope(scope));
|
1681
|
+
}
|
1682
|
+
}
|
1683
|
+
|
1684
|
+
const defaultSchema = {
|
1685
|
+
controllerAttribute: "data-controller",
|
1686
|
+
actionAttribute: "data-action",
|
1687
|
+
targetAttribute: "data-target",
|
1688
|
+
targetAttributeForScope: identifier => `data-${identifier}-target`
|
1689
|
+
};
|
1690
|
+
|
1691
|
+
class Application {
|
1692
|
+
constructor(element = document.documentElement, schema = defaultSchema) {
|
1693
|
+
this.logger = console;
|
1694
|
+
this.debug = false;
|
1695
|
+
this.warnings = true;
|
1696
|
+
this.logDebugActivity = (identifier, functionName, detail = {}) => {
|
1697
|
+
if (this.debug) {
|
1698
|
+
this.logFormattedMessage(identifier, functionName, detail);
|
1699
|
+
}
|
1700
|
+
};
|
1701
|
+
this.element = element;
|
1702
|
+
this.schema = schema;
|
1703
|
+
this.dispatcher = new Dispatcher(this);
|
1704
|
+
this.router = new Router(this);
|
1705
|
+
}
|
1706
|
+
static start(element, schema) {
|
1707
|
+
const application = new Application(element, schema);
|
1708
|
+
application.start();
|
1709
|
+
return application;
|
1710
|
+
}
|
1711
|
+
async start() {
|
1712
|
+
await domReady();
|
1713
|
+
this.logDebugActivity("application", "starting");
|
1714
|
+
this.dispatcher.start();
|
1715
|
+
this.router.start();
|
1716
|
+
this.logDebugActivity("application", "start");
|
1717
|
+
}
|
1718
|
+
stop() {
|
1719
|
+
this.logDebugActivity("application", "stopping");
|
1720
|
+
this.dispatcher.stop();
|
1721
|
+
this.router.stop();
|
1722
|
+
this.logDebugActivity("application", "stop");
|
1723
|
+
}
|
1724
|
+
register(identifier, controllerConstructor) {
|
1725
|
+
this.load({ identifier, controllerConstructor });
|
1726
|
+
}
|
1727
|
+
load(head, ...rest) {
|
1728
|
+
const definitions = Array.isArray(head) ? head : [head, ...rest];
|
1729
|
+
definitions.forEach(definition => this.router.loadDefinition(definition));
|
1730
|
+
}
|
1731
|
+
unload(head, ...rest) {
|
1732
|
+
const identifiers = Array.isArray(head) ? head : [head, ...rest];
|
1733
|
+
identifiers.forEach(identifier => this.router.unloadIdentifier(identifier));
|
1734
|
+
}
|
1735
|
+
get controllers() {
|
1736
|
+
return this.router.contexts.map(context => context.controller);
|
1737
|
+
}
|
1738
|
+
getControllerForElementAndIdentifier(element, identifier) {
|
1739
|
+
const context = this.router.getContextForElementAndIdentifier(element, identifier);
|
1740
|
+
return context ? context.controller : null;
|
1741
|
+
}
|
1742
|
+
handleWarning(warning, message, detail) {
|
1743
|
+
if (this.warnings) {
|
1744
|
+
this.logger.warn(`%s\n\n%s\n\n%o`, message, warning, detail);
|
1745
|
+
}
|
1746
|
+
}
|
1747
|
+
handleError(error, message, detail) {
|
1748
|
+
var _a;
|
1749
|
+
this.logger.error(`%s\n\n%o\n\n%o`, message, error, detail);
|
1750
|
+
(_a = window.onerror) === null || _a === void 0 ? void 0 : _a.call(window, message, "", 0, 0, error);
|
1751
|
+
}
|
1752
|
+
logFormattedMessage(identifier, functionName, detail = {}) {
|
1753
|
+
const darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
1754
|
+
const color = darkMode ? "#ffe000" : "#5D2F85";
|
1755
|
+
detail = Object.assign({ application: this }, detail);
|
1756
|
+
this.logger.groupCollapsed(`%c${identifier}%c #${functionName}`, `color: ${color}`, 'color: unset');
|
1757
|
+
this.logger.log("details:", Object.assign({}, detail));
|
1758
|
+
this.logger.groupEnd();
|
1759
|
+
}
|
1760
|
+
}
|
1761
|
+
function domReady() {
|
1762
|
+
return new Promise(resolve => {
|
1763
|
+
if (document.readyState == "loading") {
|
1764
|
+
document.addEventListener("DOMContentLoaded", () => resolve());
|
1765
|
+
}
|
1766
|
+
else {
|
1767
|
+
resolve();
|
1768
|
+
}
|
1769
|
+
});
|
1770
|
+
}
|
1771
|
+
|
1772
|
+
function ClassPropertiesBlessing(constructor) {
|
1773
|
+
const classes = readInheritableStaticArrayValues(constructor, "classes");
|
1774
|
+
return classes.reduce((properties, classDefinition) => {
|
1775
|
+
return Object.assign(properties, propertiesForClassDefinition(classDefinition));
|
1776
|
+
}, {});
|
1777
|
+
}
|
1778
|
+
function propertiesForClassDefinition(key) {
|
1779
|
+
return {
|
1780
|
+
[`${key}Class`]: {
|
1781
|
+
get() {
|
1782
|
+
const { classes } = this;
|
1783
|
+
if (classes.has(key)) {
|
1784
|
+
return classes.get(key);
|
1785
|
+
}
|
1786
|
+
else {
|
1787
|
+
const attribute = classes.getAttributeName(key);
|
1788
|
+
throw new Error(`Missing attribute "${attribute}"`);
|
1789
|
+
}
|
1790
|
+
}
|
1791
|
+
},
|
1792
|
+
[`${key}Classes`]: {
|
1793
|
+
get() {
|
1794
|
+
return this.classes.getAll(key);
|
1795
|
+
}
|
1796
|
+
},
|
1797
|
+
[`has${capitalize(key)}Class`]: {
|
1798
|
+
get() {
|
1799
|
+
return this.classes.has(key);
|
1800
|
+
}
|
1801
|
+
}
|
1802
|
+
};
|
1803
|
+
}
|
1804
|
+
|
1805
|
+
function TargetPropertiesBlessing(constructor) {
|
1806
|
+
const targets = readInheritableStaticArrayValues(constructor, "targets");
|
1807
|
+
return targets.reduce((properties, targetDefinition) => {
|
1808
|
+
return Object.assign(properties, propertiesForTargetDefinition(targetDefinition));
|
1809
|
+
}, {});
|
1810
|
+
}
|
1811
|
+
function propertiesForTargetDefinition(name) {
|
1812
|
+
return {
|
1813
|
+
[`${name}Target`]: {
|
1814
|
+
get() {
|
1815
|
+
const target = this.targets.find(name);
|
1816
|
+
if (target) {
|
1817
|
+
return target;
|
1818
|
+
}
|
1819
|
+
else {
|
1820
|
+
throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`);
|
1821
|
+
}
|
1822
|
+
}
|
1823
|
+
},
|
1824
|
+
[`${name}Targets`]: {
|
1825
|
+
get() {
|
1826
|
+
return this.targets.findAll(name);
|
1827
|
+
}
|
1828
|
+
},
|
1829
|
+
[`has${capitalize(name)}Target`]: {
|
1830
|
+
get() {
|
1831
|
+
return this.targets.has(name);
|
1832
|
+
}
|
1833
|
+
}
|
1834
|
+
};
|
1835
|
+
}
|
1836
|
+
|
1837
|
+
function ValuePropertiesBlessing(constructor) {
|
1838
|
+
const valueDefinitionPairs = readInheritableStaticObjectPairs(constructor, "values");
|
1839
|
+
const propertyDescriptorMap = {
|
1840
|
+
valueDescriptorMap: {
|
1841
|
+
get() {
|
1842
|
+
return valueDefinitionPairs.reduce((result, valueDefinitionPair) => {
|
1843
|
+
const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair);
|
1844
|
+
const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key);
|
1845
|
+
return Object.assign(result, { [attributeName]: valueDescriptor });
|
1846
|
+
}, {});
|
1847
|
+
}
|
1848
|
+
}
|
1849
|
+
};
|
1850
|
+
return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => {
|
1851
|
+
return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair));
|
1852
|
+
}, propertyDescriptorMap);
|
1853
|
+
}
|
1854
|
+
function propertiesForValueDefinitionPair(valueDefinitionPair) {
|
1855
|
+
const definition = parseValueDefinitionPair(valueDefinitionPair);
|
1856
|
+
const { key, name, reader: read, writer: write } = definition;
|
1857
|
+
return {
|
1858
|
+
[name]: {
|
1859
|
+
get() {
|
1860
|
+
const value = this.data.get(key);
|
1861
|
+
if (value !== null) {
|
1862
|
+
return read(value);
|
1863
|
+
}
|
1864
|
+
else {
|
1865
|
+
return definition.defaultValue;
|
1866
|
+
}
|
1867
|
+
},
|
1868
|
+
set(value) {
|
1869
|
+
if (value === undefined) {
|
1870
|
+
this.data.delete(key);
|
1871
|
+
}
|
1872
|
+
else {
|
1873
|
+
this.data.set(key, write(value));
|
1874
|
+
}
|
1875
|
+
}
|
1876
|
+
},
|
1877
|
+
[`has${capitalize(name)}`]: {
|
1878
|
+
get() {
|
1879
|
+
return this.data.has(key) || definition.hasCustomDefaultValue;
|
1880
|
+
}
|
1881
|
+
}
|
1882
|
+
};
|
1883
|
+
}
|
1884
|
+
function parseValueDefinitionPair([token, typeDefinition]) {
|
1885
|
+
return valueDescriptorForTokenAndTypeDefinition(token, typeDefinition);
|
1886
|
+
}
|
1887
|
+
function parseValueTypeConstant(constant) {
|
1888
|
+
switch (constant) {
|
1889
|
+
case Array: return "array";
|
1890
|
+
case Boolean: return "boolean";
|
1891
|
+
case Number: return "number";
|
1892
|
+
case Object: return "object";
|
1893
|
+
case String: return "string";
|
1894
|
+
}
|
1895
|
+
}
|
1896
|
+
function parseValueTypeDefault(defaultValue) {
|
1897
|
+
switch (typeof defaultValue) {
|
1898
|
+
case "boolean": return "boolean";
|
1899
|
+
case "number": return "number";
|
1900
|
+
case "string": return "string";
|
1901
|
+
}
|
1902
|
+
if (Array.isArray(defaultValue))
|
1903
|
+
return "array";
|
1904
|
+
if (Object.prototype.toString.call(defaultValue) === "[object Object]")
|
1905
|
+
return "object";
|
1906
|
+
}
|
1907
|
+
function parseValueTypeObject(typeObject) {
|
1908
|
+
const typeFromObject = parseValueTypeConstant(typeObject.type);
|
1909
|
+
if (typeFromObject) {
|
1910
|
+
const defaultValueType = parseValueTypeDefault(typeObject.default);
|
1911
|
+
if (typeFromObject !== defaultValueType) {
|
1912
|
+
throw new Error(`Type "${typeFromObject}" must match the type of the default value. Given default value: "${typeObject.default}" as "${defaultValueType}"`);
|
1913
|
+
}
|
1914
|
+
return typeFromObject;
|
1915
|
+
}
|
1916
|
+
}
|
1917
|
+
function parseValueTypeDefinition(typeDefinition) {
|
1918
|
+
const typeFromObject = parseValueTypeObject(typeDefinition);
|
1919
|
+
const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
|
1920
|
+
const typeFromConstant = parseValueTypeConstant(typeDefinition);
|
1921
|
+
const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
|
1922
|
+
if (type)
|
1923
|
+
return type;
|
1924
|
+
throw new Error(`Unknown value type "${typeDefinition}"`);
|
1925
|
+
}
|
1926
|
+
function defaultValueForDefinition(typeDefinition) {
|
1927
|
+
const constant = parseValueTypeConstant(typeDefinition);
|
1928
|
+
if (constant)
|
1929
|
+
return defaultValuesByType[constant];
|
1930
|
+
const defaultValue = typeDefinition.default;
|
1931
|
+
if (defaultValue !== undefined)
|
1932
|
+
return defaultValue;
|
1933
|
+
return typeDefinition;
|
1934
|
+
}
|
1935
|
+
function valueDescriptorForTokenAndTypeDefinition(token, typeDefinition) {
|
1936
|
+
const key = `${dasherize(token)}-value`;
|
1937
|
+
const type = parseValueTypeDefinition(typeDefinition);
|
1938
|
+
return {
|
1939
|
+
type,
|
1940
|
+
key,
|
1941
|
+
name: camelize(key),
|
1942
|
+
get defaultValue() { return defaultValueForDefinition(typeDefinition); },
|
1943
|
+
get hasCustomDefaultValue() { return parseValueTypeDefault(typeDefinition) !== undefined; },
|
1944
|
+
reader: readers[type],
|
1945
|
+
writer: writers[type] || writers.default
|
1946
|
+
};
|
1947
|
+
}
|
1948
|
+
const defaultValuesByType = {
|
1949
|
+
get array() { return []; },
|
1950
|
+
boolean: false,
|
1951
|
+
number: 0,
|
1952
|
+
get object() { return {}; },
|
1953
|
+
string: ""
|
1954
|
+
};
|
1955
|
+
const readers = {
|
1956
|
+
array(value) {
|
1957
|
+
const array = JSON.parse(value);
|
1958
|
+
if (!Array.isArray(array)) {
|
1959
|
+
throw new TypeError("Expected array");
|
1960
|
+
}
|
1961
|
+
return array;
|
1962
|
+
},
|
1963
|
+
boolean(value) {
|
1964
|
+
return !(value == "0" || value == "false");
|
1965
|
+
},
|
1966
|
+
number(value) {
|
1967
|
+
return Number(value);
|
1968
|
+
},
|
1969
|
+
object(value) {
|
1970
|
+
const object = JSON.parse(value);
|
1971
|
+
if (object === null || typeof object != "object" || Array.isArray(object)) {
|
1972
|
+
throw new TypeError("Expected object");
|
1973
|
+
}
|
1974
|
+
return object;
|
1975
|
+
},
|
1976
|
+
string(value) {
|
1977
|
+
return value;
|
1978
|
+
}
|
1979
|
+
};
|
1980
|
+
const writers = {
|
1981
|
+
default: writeString,
|
1982
|
+
array: writeJSON,
|
1983
|
+
object: writeJSON
|
1984
|
+
};
|
1985
|
+
function writeJSON(value) {
|
1986
|
+
return JSON.stringify(value);
|
1987
|
+
}
|
1988
|
+
function writeString(value) {
|
1989
|
+
return `${value}`;
|
1990
|
+
}
|
1991
|
+
|
1992
|
+
class Controller {
|
1993
|
+
constructor(context) {
|
1994
|
+
this.context = context;
|
1995
|
+
}
|
1996
|
+
get application() {
|
1997
|
+
return this.context.application;
|
1998
|
+
}
|
1999
|
+
get scope() {
|
2000
|
+
return this.context.scope;
|
2001
|
+
}
|
2002
|
+
get element() {
|
2003
|
+
return this.scope.element;
|
2004
|
+
}
|
2005
|
+
get identifier() {
|
2006
|
+
return this.scope.identifier;
|
2007
|
+
}
|
2008
|
+
get targets() {
|
2009
|
+
return this.scope.targets;
|
2010
|
+
}
|
2011
|
+
get classes() {
|
2012
|
+
return this.scope.classes;
|
2013
|
+
}
|
2014
|
+
get data() {
|
2015
|
+
return this.scope.data;
|
2016
|
+
}
|
2017
|
+
initialize() {
|
2018
|
+
}
|
2019
|
+
connect() {
|
2020
|
+
}
|
2021
|
+
disconnect() {
|
2022
|
+
}
|
2023
|
+
dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) {
|
2024
|
+
const type = prefix ? `${prefix}:${eventName}` : eventName;
|
2025
|
+
const event = new CustomEvent(type, { detail, bubbles, cancelable });
|
2026
|
+
target.dispatchEvent(event);
|
2027
|
+
return event;
|
2028
|
+
}
|
2029
|
+
}
|
2030
|
+
Controller.blessings = [ClassPropertiesBlessing, TargetPropertiesBlessing, ValuePropertiesBlessing];
|
2031
|
+
Controller.targets = [];
|
2032
|
+
Controller.values = {};
|
2033
|
+
|
2034
|
+
export { Application, AttributeObserver, Context, Controller, ElementObserver, IndexedMultimap, Multimap, StringMapObserver, TokenListObserver, ValueListObserver, add, defaultSchema, del, fetch, prune };
|