@stackoverflow/stacks 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/controllers/index.d.ts +7 -7
- package/dist/controllers/s-expandable-control.d.ts +1 -1
- package/dist/css/stacks.css +4 -15
- package/dist/css/stacks.min.css +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/js/stacks.js +174 -112
- package/dist/js/stacks.min.js +1 -1
- package/dist/stacks.d.ts +1 -1
- package/lib/css/atomic/gap.less +1 -1
- package/lib/css/components/buttons.less +3 -1
- package/lib/css/components/cards.less +1 -1
- package/lib/css/components/expandable.less +1 -1
- package/lib/css/components/inputs.less +4 -18
- package/lib/css/components/pagination.less +1 -1
- package/lib/css/components/popovers.less +6 -7
- package/lib/ts/controllers/index.ts +14 -7
- package/lib/ts/controllers/s-expandable-control.ts +79 -34
- package/lib/ts/controllers/s-modal.ts +116 -58
- package/lib/ts/controllers/s-navigation-tablist.ts +30 -20
- package/lib/ts/controllers/s-popover.ts +149 -73
- package/lib/ts/controllers/s-table.ts +69 -28
- package/lib/ts/controllers/s-tooltip.ts +87 -29
- package/lib/ts/controllers/s-uploader.ts +58 -39
- package/lib/ts/index.ts +11 -3
- package/lib/ts/stacks.ts +40 -19
- package/lib/tsconfig.json +1 -1
- package/package.json +8 -6
|
@@ -45,9 +45,13 @@ export class TooltipController extends BasePopoverController {
|
|
|
45
45
|
* Attempts to show the tooltip popover so long as no other Stacks-managed popover is
|
|
46
46
|
* present on the page.
|
|
47
47
|
*/
|
|
48
|
-
show(dispatcher: Event|Element|null = null) {
|
|
48
|
+
show(dispatcher: Event | Element | null = null) {
|
|
49
49
|
// check and see if this controller coexists with a popover
|
|
50
|
-
const controller =
|
|
50
|
+
const controller =
|
|
51
|
+
Stacks.application.getControllerForElementAndIdentifier(
|
|
52
|
+
this.element,
|
|
53
|
+
"s-popover"
|
|
54
|
+
);
|
|
51
55
|
|
|
52
56
|
// if the controller exists and already has a visible popover, don't show the tooltip
|
|
53
57
|
if (controller && (<PopoverController>controller).isVisible) {
|
|
@@ -62,7 +66,10 @@ export class TooltipController extends BasePopoverController {
|
|
|
62
66
|
*/
|
|
63
67
|
scheduleShow(dispatcher: Event | Element | null = null) {
|
|
64
68
|
window.clearTimeout(this.activeTimeout);
|
|
65
|
-
this.activeTimeout = window.setTimeout(
|
|
69
|
+
this.activeTimeout = window.setTimeout(
|
|
70
|
+
() => this.show(dispatcher),
|
|
71
|
+
300
|
|
72
|
+
);
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
/**
|
|
@@ -70,13 +77,16 @@ export class TooltipController extends BasePopoverController {
|
|
|
70
77
|
*/
|
|
71
78
|
scheduleHide(dispatcher: Event | Element | null = null) {
|
|
72
79
|
window.clearTimeout(this.activeTimeout);
|
|
73
|
-
this.activeTimeout = window.setTimeout(
|
|
80
|
+
this.activeTimeout = window.setTimeout(
|
|
81
|
+
() => super.hide(dispatcher),
|
|
82
|
+
100
|
|
83
|
+
);
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
/**
|
|
77
87
|
* Cancels the activeTimeout
|
|
78
88
|
*/
|
|
79
|
-
|
|
89
|
+
clearActiveTimeout() {
|
|
80
90
|
clearTimeout(this.activeTimeout);
|
|
81
91
|
}
|
|
82
92
|
|
|
@@ -84,13 +94,14 @@ export class TooltipController extends BasePopoverController {
|
|
|
84
94
|
* Applies data-s-tooltip-html-title and title attributes.
|
|
85
95
|
*/
|
|
86
96
|
applyTitleAttributes() {
|
|
87
|
-
|
|
88
97
|
let content: Node;
|
|
89
98
|
|
|
90
99
|
const htmlTitle = this.data.get("html-title");
|
|
91
100
|
if (htmlTitle) {
|
|
92
101
|
// eslint-disable-next-line no-unsanitized/method
|
|
93
|
-
content = document
|
|
102
|
+
content = document
|
|
103
|
+
.createRange()
|
|
104
|
+
.createContextualFragment(htmlTitle);
|
|
94
105
|
} else {
|
|
95
106
|
const plainTitle = this.element.getAttribute("title");
|
|
96
107
|
if (plainTitle) {
|
|
@@ -135,7 +146,10 @@ export class TooltipController extends BasePopoverController {
|
|
|
135
146
|
if (arrow) {
|
|
136
147
|
popover.appendChild(arrow);
|
|
137
148
|
} else {
|
|
138
|
-
popover.insertAdjacentHTML(
|
|
149
|
+
popover.insertAdjacentHTML(
|
|
150
|
+
"beforeend",
|
|
151
|
+
`<div class="s-popover--arrow"></div>`
|
|
152
|
+
);
|
|
139
153
|
}
|
|
140
154
|
|
|
141
155
|
this.scheduleUpdate();
|
|
@@ -148,7 +162,8 @@ export class TooltipController extends BasePopoverController {
|
|
|
148
162
|
* the page.
|
|
149
163
|
*/
|
|
150
164
|
protected bindDocumentEvents() {
|
|
151
|
-
this.boundHideIfWithin =
|
|
165
|
+
this.boundHideIfWithin =
|
|
166
|
+
this.boundHideIfWithin || this.hideIfWithin.bind(this);
|
|
152
167
|
|
|
153
168
|
document.addEventListener("s-popover:shown", this.boundHideIfWithin);
|
|
154
169
|
}
|
|
@@ -173,7 +188,7 @@ export class TooltipController extends BasePopoverController {
|
|
|
173
188
|
* @param event An event object from s-popover:shown
|
|
174
189
|
*/
|
|
175
190
|
private hideIfWithin(event: Event) {
|
|
176
|
-
if ((<Element>event.target
|
|
191
|
+
if ((<Element>event.target).contains(this.referenceElement)) {
|
|
177
192
|
this.scheduleHide();
|
|
178
193
|
}
|
|
179
194
|
}
|
|
@@ -186,10 +201,13 @@ export class TooltipController extends BasePopoverController {
|
|
|
186
201
|
/**
|
|
187
202
|
* Binds mouse events to show/hide on reference element hover
|
|
188
203
|
*/
|
|
189
|
-
|
|
190
|
-
this.boundScheduleShow =
|
|
204
|
+
private bindKeyboardEvents() {
|
|
205
|
+
this.boundScheduleShow =
|
|
206
|
+
this.boundScheduleShow || this.scheduleShow.bind(this);
|
|
191
207
|
this.boundHide = this.boundHide || this.scheduleHide.bind(this);
|
|
192
|
-
this.boundHideOnEscapeKeyEvent =
|
|
208
|
+
this.boundHideOnEscapeKeyEvent =
|
|
209
|
+
this.boundHideOnEscapeKeyEvent ||
|
|
210
|
+
this.hideOnEscapeKeyEvent.bind(this);
|
|
193
211
|
|
|
194
212
|
this.referenceElement.addEventListener("focus", this.boundScheduleShow);
|
|
195
213
|
this.referenceElement.addEventListener("blur", this.boundHide);
|
|
@@ -198,24 +216,34 @@ export class TooltipController extends BasePopoverController {
|
|
|
198
216
|
/**
|
|
199
217
|
* Unbinds all mouse events
|
|
200
218
|
*/
|
|
201
|
-
|
|
202
|
-
this.referenceElement.removeEventListener(
|
|
219
|
+
private unbindKeyboardEvents() {
|
|
220
|
+
this.referenceElement.removeEventListener(
|
|
221
|
+
"focus",
|
|
222
|
+
this.boundScheduleShow
|
|
223
|
+
);
|
|
203
224
|
this.referenceElement.removeEventListener("blur", this.boundHide);
|
|
204
225
|
document.removeEventListener("keyup", this.boundHideOnEscapeKeyEvent);
|
|
205
226
|
}
|
|
206
227
|
|
|
207
|
-
|
|
208
228
|
/**
|
|
209
229
|
* Binds mouse events to show/hide on reference element hover
|
|
210
230
|
*/
|
|
211
231
|
private bindMouseEvents() {
|
|
212
|
-
this.boundScheduleShow =
|
|
232
|
+
this.boundScheduleShow =
|
|
233
|
+
this.boundScheduleShow || this.scheduleShow.bind(this);
|
|
213
234
|
this.boundHide = this.boundHide || this.scheduleHide.bind(this);
|
|
214
|
-
this.boundClearActiveTimeout =
|
|
235
|
+
this.boundClearActiveTimeout =
|
|
236
|
+
this.boundClearActiveTimeout || this.clearActiveTimeout.bind(this);
|
|
215
237
|
|
|
216
|
-
this.referenceElement.addEventListener(
|
|
238
|
+
this.referenceElement.addEventListener(
|
|
239
|
+
"mouseover",
|
|
240
|
+
this.boundScheduleShow
|
|
241
|
+
);
|
|
217
242
|
this.referenceElement.addEventListener("mouseout", this.boundHide);
|
|
218
|
-
this.popoverElement.addEventListener(
|
|
243
|
+
this.popoverElement.addEventListener(
|
|
244
|
+
"mouseover",
|
|
245
|
+
this.boundClearActiveTimeout
|
|
246
|
+
);
|
|
219
247
|
this.popoverElement.addEventListener("mouseout", this.boundHide);
|
|
220
248
|
}
|
|
221
249
|
|
|
@@ -223,11 +251,20 @@ export class TooltipController extends BasePopoverController {
|
|
|
223
251
|
* Unbinds all mouse events
|
|
224
252
|
*/
|
|
225
253
|
private unbindMouseEvents() {
|
|
226
|
-
this.referenceElement.removeEventListener(
|
|
254
|
+
this.referenceElement.removeEventListener(
|
|
255
|
+
"mouseover",
|
|
256
|
+
this.boundScheduleShow
|
|
257
|
+
);
|
|
227
258
|
this.referenceElement.removeEventListener("mouseout", this.boundHide);
|
|
228
|
-
this.referenceElement.removeEventListener(
|
|
259
|
+
this.referenceElement.removeEventListener(
|
|
260
|
+
"focus",
|
|
261
|
+
this.boundScheduleShow
|
|
262
|
+
);
|
|
229
263
|
this.referenceElement.removeEventListener("blur", this.boundHide);
|
|
230
|
-
this.popoverElement.removeEventListener(
|
|
264
|
+
this.popoverElement.removeEventListener(
|
|
265
|
+
"mouseover",
|
|
266
|
+
this.boundClearActiveTimeout
|
|
267
|
+
);
|
|
231
268
|
this.popoverElement.removeEventListener("mouseout", this.boundHide);
|
|
232
269
|
}
|
|
233
270
|
|
|
@@ -236,7 +273,9 @@ export class TooltipController extends BasePopoverController {
|
|
|
236
273
|
*/
|
|
237
274
|
private static generateId() {
|
|
238
275
|
// generate a random number, then convert to a well formatted string
|
|
239
|
-
return
|
|
276
|
+
return (
|
|
277
|
+
"--stacks-s-tooltip-" + Math.random().toString(36).substring(2, 10)
|
|
278
|
+
);
|
|
240
279
|
}
|
|
241
280
|
}
|
|
242
281
|
|
|
@@ -246,7 +285,11 @@ export class TooltipController extends BasePopoverController {
|
|
|
246
285
|
* @param html An HTML string to populate the tooltip with.
|
|
247
286
|
* @param options Options for rendering the tooltip.
|
|
248
287
|
*/
|
|
249
|
-
export function setTooltipHtml(
|
|
288
|
+
export function setTooltipHtml(
|
|
289
|
+
element: Element,
|
|
290
|
+
html: string,
|
|
291
|
+
options?: TooltipOptions
|
|
292
|
+
) {
|
|
250
293
|
element.setAttribute("data-s-tooltip-html-title", html);
|
|
251
294
|
element.removeAttribute("title");
|
|
252
295
|
applyOptionsAndTitleAttributes(element, options);
|
|
@@ -258,7 +301,11 @@ export function setTooltipHtml(element: Element, html: string, options?: Tooltip
|
|
|
258
301
|
* @param text A plain text string to populate the tooltip with.
|
|
259
302
|
* @param options Options for rendering the tooltip.
|
|
260
303
|
*/
|
|
261
|
-
export function setTooltipText(
|
|
304
|
+
export function setTooltipText(
|
|
305
|
+
element: Element,
|
|
306
|
+
text: string,
|
|
307
|
+
options?: TooltipOptions
|
|
308
|
+
) {
|
|
262
309
|
element.setAttribute("title", text);
|
|
263
310
|
element.removeAttribute("data-s-tooltip-html-title");
|
|
264
311
|
applyOptionsAndTitleAttributes(element, options);
|
|
@@ -269,17 +316,28 @@ export function setTooltipText(element: Element, text: string, options?: Tooltip
|
|
|
269
316
|
* @param element The element to add a tooltip to.
|
|
270
317
|
* @param options Options for rendering the tooltip.
|
|
271
318
|
*/
|
|
272
|
-
function applyOptionsAndTitleAttributes(
|
|
319
|
+
function applyOptionsAndTitleAttributes(
|
|
320
|
+
element: Element,
|
|
321
|
+
options?: TooltipOptions
|
|
322
|
+
) {
|
|
273
323
|
if (options && options.placement) {
|
|
274
324
|
element.setAttribute("data-s-tooltip-placement", options.placement);
|
|
275
325
|
}
|
|
276
326
|
|
|
277
|
-
const controller = <TooltipController>
|
|
327
|
+
const controller = <TooltipController>(
|
|
328
|
+
Stacks.application.getControllerForElementAndIdentifier(
|
|
329
|
+
element,
|
|
330
|
+
"s-tooltip"
|
|
331
|
+
)
|
|
332
|
+
);
|
|
278
333
|
|
|
279
334
|
if (controller) {
|
|
280
335
|
controller.applyTitleAttributes();
|
|
281
336
|
} else {
|
|
282
337
|
const dataController = element.getAttribute("data-controller");
|
|
283
|
-
element.setAttribute(
|
|
338
|
+
element.setAttribute(
|
|
339
|
+
"data-controller",
|
|
340
|
+
`${dataController ? dataController : ""} s-tooltip`
|
|
341
|
+
);
|
|
284
342
|
}
|
|
285
343
|
}
|
|
@@ -4,7 +4,7 @@ interface FilePreview {
|
|
|
4
4
|
data?: string | ArrayBuffer;
|
|
5
5
|
name: string;
|
|
6
6
|
type: string;
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
8
|
|
|
9
9
|
export class UploaderController extends Stacks.StacksController {
|
|
10
10
|
static targets = ["input", "previews", "uploader"];
|
|
@@ -44,33 +44,40 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const count = this.inputTarget.files.length;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
47
|
+
this.getDataURLs(
|
|
48
|
+
this.inputTarget.files,
|
|
49
|
+
UploaderController.FILE_DISPLAY_LIMIT
|
|
50
|
+
)
|
|
51
|
+
.then((res: FilePreview[]) => {
|
|
52
|
+
this.handleVisible(true);
|
|
53
|
+
const hasMultipleFiles = res.length > 1;
|
|
54
|
+
|
|
55
|
+
if (hasMultipleFiles) {
|
|
56
|
+
const headingElement = document.createElement("div");
|
|
57
|
+
headingElement.classList.add(
|
|
58
|
+
"s-uploader--previews-heading"
|
|
59
|
+
);
|
|
60
|
+
headingElement.innerText =
|
|
61
|
+
res.length < count
|
|
62
|
+
? `Showing ${res.length} of ${count} files`
|
|
63
|
+
: `${count} items`;
|
|
64
|
+
this.previewsTarget.appendChild(headingElement);
|
|
65
|
+
this.previewsTarget.classList.add("has-multiple");
|
|
66
|
+
} else {
|
|
67
|
+
this.previewsTarget.classList.remove("has-multiple");
|
|
68
|
+
}
|
|
69
|
+
res.forEach((file) => this.addFilePreview(file));
|
|
70
|
+
this.handleUploaderActive(true);
|
|
71
|
+
})
|
|
72
|
+
// TODO consider rendering an error message
|
|
73
|
+
.catch(() => null);
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
/**
|
|
70
77
|
* Resets the Uploader to initial state
|
|
71
78
|
*/
|
|
72
79
|
reset() {
|
|
73
|
-
this.inputTarget.value =
|
|
80
|
+
this.inputTarget.value = "";
|
|
74
81
|
this.previewsTarget.innerHTML = "";
|
|
75
82
|
this.handleVisible(false);
|
|
76
83
|
}
|
|
@@ -81,28 +88,34 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
81
88
|
*/
|
|
82
89
|
private handleVisible(shouldPreview: boolean) {
|
|
83
90
|
const { scope } = this.targets;
|
|
84
|
-
const hideElements = scope.findAllElements(
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
const hideElements = scope.findAllElements(
|
|
92
|
+
"[data-s-uploader-hide-on-input]"
|
|
93
|
+
);
|
|
94
|
+
const showElements = scope.findAllElements(
|
|
95
|
+
"[data-s-uploader-show-on-input]"
|
|
96
|
+
);
|
|
97
|
+
const enableElements = scope.findAllElements(
|
|
98
|
+
"[data-s-uploader-enable-on-input]"
|
|
99
|
+
);
|
|
87
100
|
|
|
88
101
|
if (shouldPreview) {
|
|
89
|
-
hideElements.forEach(el => {
|
|
102
|
+
hideElements.forEach((el) => {
|
|
90
103
|
el.classList.add("d-none");
|
|
91
104
|
});
|
|
92
|
-
showElements.forEach(el => {
|
|
105
|
+
showElements.forEach((el) => {
|
|
93
106
|
el.classList.remove("d-none");
|
|
94
107
|
});
|
|
95
|
-
enableElements.forEach(el => {
|
|
108
|
+
enableElements.forEach((el) => {
|
|
96
109
|
el.removeAttribute("disabled");
|
|
97
110
|
});
|
|
98
111
|
} else {
|
|
99
|
-
hideElements.forEach(el => {
|
|
112
|
+
hideElements.forEach((el) => {
|
|
100
113
|
el.classList.remove("d-none");
|
|
101
114
|
});
|
|
102
|
-
showElements.forEach(el => {
|
|
115
|
+
showElements.forEach((el) => {
|
|
103
116
|
el.classList.add("d-none");
|
|
104
117
|
});
|
|
105
|
-
enableElements.forEach(el => {
|
|
118
|
+
enableElements.forEach((el) => {
|
|
106
119
|
el.setAttribute("disabled", "true");
|
|
107
120
|
});
|
|
108
121
|
this.handleUploaderActive(false);
|
|
@@ -121,7 +134,7 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
121
134
|
const previewElement = document.createElement("div");
|
|
122
135
|
let thumbElement;
|
|
123
136
|
|
|
124
|
-
if (file.type.match(
|
|
137
|
+
if (file.type.match("image/*") && file.data) {
|
|
125
138
|
thumbElement = document.createElement("img");
|
|
126
139
|
thumbElement.src = file.data.toString();
|
|
127
140
|
thumbElement.alt = file.name;
|
|
@@ -133,7 +146,7 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
133
146
|
thumbElement.classList.add("s-uploader--preview-thumbnail");
|
|
134
147
|
previewElement.appendChild(thumbElement);
|
|
135
148
|
previewElement.classList.add("s-uploader--preview");
|
|
136
|
-
previewElement.setAttribute(
|
|
149
|
+
previewElement.setAttribute("data-filename", file.name);
|
|
137
150
|
this.previewsTarget.appendChild(previewElement);
|
|
138
151
|
}
|
|
139
152
|
|
|
@@ -154,16 +167,19 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
154
167
|
const reader = new FileReader();
|
|
155
168
|
const { name, size, type } = file;
|
|
156
169
|
|
|
157
|
-
if (
|
|
170
|
+
if (
|
|
171
|
+
size < UploaderController.MAX_FILE_SIZE &&
|
|
172
|
+
type.indexOf("image") > -1
|
|
173
|
+
) {
|
|
158
174
|
return new Promise((resolve, reject) => {
|
|
159
|
-
reader.onload = evt => {
|
|
175
|
+
reader.onload = (evt) => {
|
|
160
176
|
const res = evt?.target?.result;
|
|
161
177
|
if (res) {
|
|
162
178
|
resolve({ data: res, name, type });
|
|
163
179
|
} else {
|
|
164
180
|
reject();
|
|
165
181
|
}
|
|
166
|
-
}
|
|
182
|
+
};
|
|
167
183
|
reader.readAsDataURL(file);
|
|
168
184
|
});
|
|
169
185
|
} else {
|
|
@@ -176,11 +192,14 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
176
192
|
* @param {FileList|[]} files
|
|
177
193
|
* @returns an array of FilePreview objects from a FileList
|
|
178
194
|
*/
|
|
179
|
-
private getDataURLs(
|
|
195
|
+
private getDataURLs(
|
|
196
|
+
files: FileList,
|
|
197
|
+
limit: number
|
|
198
|
+
): Promise<FilePreview[]> {
|
|
180
199
|
const promises = Array.from(files)
|
|
181
200
|
.slice(0, Math.min(limit, files.length))
|
|
182
|
-
.map(f => this.fileToDataURL(f));
|
|
201
|
+
.map((f) => this.fileToDataURL(f));
|
|
183
202
|
|
|
184
203
|
return Promise.all(promises);
|
|
185
204
|
}
|
|
186
|
-
}
|
|
205
|
+
}
|
package/lib/ts/index.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import "../css/stacks.less";
|
|
2
|
+
import {
|
|
3
|
+
ExpandableController,
|
|
4
|
+
ModalController,
|
|
5
|
+
PopoverController,
|
|
6
|
+
TableController,
|
|
7
|
+
TabListController,
|
|
8
|
+
TooltipController,
|
|
9
|
+
UploaderController,
|
|
10
|
+
} from "./controllers";
|
|
11
|
+
import { application, StacksApplication } from "./stacks";
|
|
4
12
|
|
|
5
13
|
// register all built-in controllers
|
|
6
14
|
application.register("s-expandable-control", ExpandableController);
|
package/lib/ts/stacks.ts
CHANGED
|
@@ -3,25 +3,31 @@ import * as Stimulus from "stimulus";
|
|
|
3
3
|
export class StacksApplication extends Stimulus.Application {
|
|
4
4
|
static _initializing = true;
|
|
5
5
|
|
|
6
|
-
load(...definitions: Stimulus.Definition[]): void
|
|
7
|
-
load(definitions: Stimulus.Definition[]): void
|
|
8
|
-
load(
|
|
6
|
+
load(...definitions: Stimulus.Definition[]): void;
|
|
7
|
+
load(definitions: Stimulus.Definition[]): void;
|
|
8
|
+
load(
|
|
9
|
+
head: Stimulus.Definition | Stimulus.Definition[],
|
|
10
|
+
...rest: Stimulus.Definition[]
|
|
11
|
+
) {
|
|
9
12
|
const definitions = Array.isArray(head) ? head : [head, ...rest];
|
|
10
13
|
|
|
11
14
|
for (const definition of definitions) {
|
|
12
15
|
const hasPrefix = /^s-/.test(definition.identifier);
|
|
13
16
|
if (StacksApplication._initializing && !hasPrefix) {
|
|
14
|
-
throw
|
|
17
|
+
throw 'Stacks-created Stimulus controller names must start with "s-".';
|
|
15
18
|
}
|
|
16
19
|
if (!StacksApplication._initializing && hasPrefix) {
|
|
17
|
-
throw
|
|
20
|
+
throw 'The "s-" prefix on Stimulus controller names is reserved for Stacks-created controllers.';
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
super.load(definitions);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
static start(
|
|
27
|
+
static start(
|
|
28
|
+
element?: Element,
|
|
29
|
+
schema?: Stimulus.Schema
|
|
30
|
+
): StacksApplication {
|
|
25
31
|
const application = new StacksApplication(element, schema);
|
|
26
32
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
27
33
|
application.start();
|
|
@@ -38,18 +44,26 @@ export const application: Stimulus.Application = StacksApplication.start();
|
|
|
38
44
|
export class StacksController extends Stimulus.Controller {
|
|
39
45
|
protected getElementData(element: Element, key: string) {
|
|
40
46
|
return element.getAttribute("data-" + this.identifier + "-" + key);
|
|
41
|
-
}
|
|
47
|
+
}
|
|
42
48
|
protected setElementData(element: Element, key: string, value: string) {
|
|
43
49
|
element.setAttribute("data-" + this.identifier + "-" + key, value);
|
|
44
|
-
}
|
|
50
|
+
}
|
|
45
51
|
protected removeElementData(element: Element, key: string) {
|
|
46
52
|
element.removeAttribute("data-" + this.identifier + "-" + key);
|
|
47
|
-
}
|
|
48
|
-
protected triggerEvent<T>(
|
|
53
|
+
}
|
|
54
|
+
protected triggerEvent<T>(
|
|
55
|
+
eventName: string,
|
|
56
|
+
detail?: T,
|
|
57
|
+
optionalElement?: Element
|
|
58
|
+
) {
|
|
49
59
|
const namespacedName = this.identifier + ":" + eventName;
|
|
50
|
-
let event
|
|
60
|
+
let event: CustomEvent<T>;
|
|
51
61
|
try {
|
|
52
|
-
event = new CustomEvent(namespacedName, {
|
|
62
|
+
event = new CustomEvent(namespacedName, {
|
|
63
|
+
bubbles: true,
|
|
64
|
+
cancelable: true,
|
|
65
|
+
detail: detail,
|
|
66
|
+
});
|
|
53
67
|
} catch (ex) {
|
|
54
68
|
// Internet Explorer
|
|
55
69
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
@@ -58,28 +72,35 @@ export class StacksController extends Stimulus.Controller {
|
|
|
58
72
|
}
|
|
59
73
|
(optionalElement || this.element).dispatchEvent(event);
|
|
60
74
|
return event;
|
|
61
|
-
}
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
// ControllerDefinition/createController/addController is here to make
|
|
65
79
|
// it easier to consume Stimulus from ES5 files (without classes)
|
|
66
80
|
export interface ControllerDefinition {
|
|
67
|
-
[name: string]:
|
|
81
|
+
[name: string]: unknown;
|
|
68
82
|
targets?: string[];
|
|
69
83
|
}
|
|
70
|
-
export function createController(
|
|
84
|
+
export function createController(
|
|
85
|
+
controllerDefinition: ControllerDefinition
|
|
86
|
+
): typeof StacksController {
|
|
71
87
|
// eslint-disable-next-line no-prototype-builtins
|
|
72
88
|
const Controller = controllerDefinition.hasOwnProperty("targets")
|
|
73
|
-
? class Controller extends StacksController {
|
|
89
|
+
? class Controller extends StacksController {
|
|
90
|
+
static targets = controllerDefinition.targets ?? [];
|
|
91
|
+
}
|
|
74
92
|
: class Controller extends StacksController {};
|
|
75
93
|
|
|
76
94
|
for (const prop in controllerDefinition) {
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
const ownPropDescriptor =
|
|
96
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
97
|
+
controllerDefinition.hasOwnProperty(prop) &&
|
|
98
|
+
Object.getOwnPropertyDescriptor(controllerDefinition, prop);
|
|
99
|
+
if (prop !== "targets" && ownPropDescriptor) {
|
|
79
100
|
Object.defineProperty(
|
|
80
101
|
Controller.prototype,
|
|
81
102
|
prop,
|
|
82
|
-
|
|
103
|
+
ownPropDescriptor
|
|
83
104
|
);
|
|
84
105
|
}
|
|
85
106
|
}
|
package/lib/tsconfig.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"strict": true,
|
|
4
4
|
"target": "es5",
|
|
5
|
-
"lib": ["dom", "es6", "dom.iterable", "scripthost"],
|
|
5
|
+
"lib": ["dom", "es6", "dom.iterable", "scripthost"], // es6 stuff is polyfilled by stimulus
|
|
6
6
|
"outDir": "../dist",
|
|
7
7
|
"sourceMap": true,
|
|
8
8
|
"moduleResolution": "node",
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/StackExchange/Stacks.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.6.
|
|
8
|
+
"version": "1.6.3",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"lib"
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
"test": "backstop test",
|
|
26
26
|
"approve": "backstop approve",
|
|
27
27
|
"prepublishOnly": "npm run build",
|
|
28
|
-
"lint
|
|
29
|
-
"lint:
|
|
28
|
+
"lint": "concurrently -n w: npm:lint:*",
|
|
29
|
+
"lint:ts": "eslint ./lib/ts",
|
|
30
|
+
"lint:css": "stylelint ./lib/css",
|
|
31
|
+
"lint:format": "prettier --check ./lib"
|
|
30
32
|
},
|
|
31
33
|
"license": "MIT",
|
|
32
34
|
"dependencies": {
|
|
@@ -47,7 +49,7 @@
|
|
|
47
49
|
"docsearch.js": "^2.6.3",
|
|
48
50
|
"eleventy-plugin-highlightjs": "^1.1.0",
|
|
49
51
|
"eleventy-plugin-nesting-toc": "^1.3.0",
|
|
50
|
-
"eslint": "^8.
|
|
52
|
+
"eslint": "^8.29.0",
|
|
51
53
|
"eslint-config-prettier": "^8.5.0",
|
|
52
54
|
"eslint-plugin-no-unsanitized": "^4.0.1",
|
|
53
55
|
"jquery": "^3.6.1",
|
|
@@ -58,12 +60,12 @@
|
|
|
58
60
|
"postcss-less": "^6.0.0",
|
|
59
61
|
"postcss-loader": "^7.0.1",
|
|
60
62
|
"prettier": "^2.7.1",
|
|
61
|
-
"stylelint": "^14.
|
|
63
|
+
"stylelint": "^14.16.0",
|
|
62
64
|
"stylelint-config-recommended": "^9.0.0",
|
|
63
65
|
"stylelint-config-standard": "^29.0.0",
|
|
64
66
|
"terser-webpack-plugin": "^5.3.6",
|
|
65
67
|
"ts-loader": "^9.4.1",
|
|
66
|
-
"typescript": "^4.
|
|
68
|
+
"typescript": "^4.9.3",
|
|
67
69
|
"webpack": "^5.75.0",
|
|
68
70
|
"webpack-cli": "^5.0.0",
|
|
69
71
|
"webpack-merge": "^5.8.0"
|