@stimulus-plumbers/controllers 0.3.2 → 0.4.0
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 +6 -6
- package/dist/controllers.manifest.json +273 -0
- package/dist/stimulus-plumbers-controllers.es.js +269 -160
- package/dist/stimulus-plumbers-controllers.umd.js +1 -1
- package/package.json +3 -4
- package/src/controllers/calendar_month_controller.js +30 -4
- package/src/controllers/clipboard_controller.js +2 -2
- package/src/controllers/combobox_date_controller.js +137 -3
- package/src/controllers/input_combobox_controller.js +4 -43
- package/src/controllers/{input_format_controller.js → input_formatter_controller.js} +8 -8
- package/src/controllers/popover_controller.js +34 -12
- package/src/index.js +3 -3
- package/src/plumbers/calendar.js +26 -4
- package/src/plumbers/plumber/date.js +13 -2
- /package/src/controllers/{calendar_month_observer_controller.js → calendar_observer_controller.js} +0 -0
- /package/src/controllers/{input_search_controller.js → input_clearable_controller.js} +0 -0
|
@@ -5,27 +5,27 @@ import { attachFormatter } from '../plumbers';
|
|
|
5
5
|
export default class extends Controller {
|
|
6
6
|
static targets = ['input', 'toggle'];
|
|
7
7
|
static values = {
|
|
8
|
-
|
|
8
|
+
format: { type: String, default: 'plain' },
|
|
9
9
|
options: { type: Object, default: {} },
|
|
10
10
|
revealed: { type: Boolean, default: false },
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
connect() {
|
|
14
|
-
attachFormatter(this, { type: this.
|
|
14
|
+
attachFormatter(this, { type: this.formatValue, options: this.optionsValue });
|
|
15
15
|
this.format(this.readValue());
|
|
16
16
|
this.drawToggle();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
formatValueChanged() {
|
|
20
20
|
if (!this.formatter) return;
|
|
21
|
-
attachFormatter(this, { type: this.
|
|
21
|
+
attachFormatter(this, { type: this.formatValue, options: this.optionsValue });
|
|
22
22
|
this.format(this.readValue());
|
|
23
23
|
this.drawToggle();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
optionsValueChanged() {
|
|
27
27
|
if (!this.formatter) return;
|
|
28
|
-
attachFormatter(this, { type: this.
|
|
28
|
+
attachFormatter(this, { type: this.formatValue, options: this.optionsValue });
|
|
29
29
|
this.format(this.readValue());
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -45,7 +45,7 @@ export default class extends Controller {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
toggle() {
|
|
48
|
-
if (!this.formatter.maskable() && this.
|
|
48
|
+
if (!this.formatter.maskable() && this.formatValue !== 'password') return;
|
|
49
49
|
this.revealedValue = !this.revealedValue;
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -59,7 +59,7 @@ export default class extends Controller {
|
|
|
59
59
|
|
|
60
60
|
drawToggle() {
|
|
61
61
|
if (!this.hasToggleTarget) return;
|
|
62
|
-
const hasToggleBehavior = this.formatter?.maskable() || this.
|
|
62
|
+
const hasToggleBehavior = this.formatter?.maskable() || this.formatValue === 'password';
|
|
63
63
|
this.toggleTarget.hidden = !hasToggleBehavior;
|
|
64
64
|
if (hasToggleBehavior) setPressed(this.toggleTarget, this.revealedValue);
|
|
65
65
|
}
|
|
@@ -72,7 +72,7 @@ export default class extends Controller {
|
|
|
72
72
|
onFormatting(raw) {
|
|
73
73
|
if (!this.formatter) return;
|
|
74
74
|
|
|
75
|
-
if (this.
|
|
75
|
+
if (this.formatValue === 'password') {
|
|
76
76
|
if (this.hasInputTarget) this.inputTarget.type = this.revealedValue ? 'text' : 'password';
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
@@ -1,47 +1,69 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
|
-
import {
|
|
2
|
+
import { focusFirst } from '../accessibility/focus';
|
|
3
|
+
import { attachContentLoader, attachDismisser, attachVisibility } from '../plumbers';
|
|
3
4
|
|
|
4
5
|
export default class extends Controller {
|
|
5
|
-
static targets = ['
|
|
6
|
+
static targets = ['trigger', 'panel', 'template', 'loader'];
|
|
6
7
|
static classes = ['hidden'];
|
|
7
8
|
static values = {
|
|
8
9
|
url: String,
|
|
9
10
|
loadedAt: String,
|
|
10
11
|
reload: { type: String, default: 'never' },
|
|
11
12
|
staleAfter: { type: Number, default: 3600 },
|
|
13
|
+
closeOnSelect: { type: Boolean, default: true },
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
connect() {
|
|
15
17
|
attachContentLoader(this, {
|
|
16
|
-
element: this.
|
|
18
|
+
element: this.hasPanelTarget ? this.panelTarget : null,
|
|
17
19
|
url: this.hasUrlValue ? this.urlValue : null,
|
|
18
20
|
});
|
|
19
21
|
|
|
20
|
-
if (this.
|
|
22
|
+
if (this.hasPanelTarget) {
|
|
21
23
|
attachVisibility(this, {
|
|
22
|
-
element: this.
|
|
23
|
-
activator: this.
|
|
24
|
+
element: this.panelTarget,
|
|
25
|
+
activator: this.hasTriggerTarget ? this.triggerTarget : null,
|
|
24
26
|
});
|
|
27
|
+
attachDismisser(this);
|
|
25
28
|
}
|
|
26
29
|
if (this.hasLoaderTarget)
|
|
27
30
|
attachVisibility(this, { element: this.loaderTarget, visibility: 'contentLoaderVisibility' });
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
async
|
|
33
|
+
async dismissed() {
|
|
34
|
+
await this.close();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async open() {
|
|
38
|
+
if (!this.hasPanelTarget) return;
|
|
31
39
|
await this.visibility.show();
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
async
|
|
42
|
+
async close() {
|
|
43
|
+
if (!this.hasPanelTarget) return;
|
|
35
44
|
await this.visibility.hide();
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
async toggle() {
|
|
48
|
+
this.visibility?.visible ? await this.close() : await this.open();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async closeOnSelect() {
|
|
52
|
+
if (this.closeOnSelectValue) await this.close();
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
async shown() {
|
|
39
56
|
await this.load();
|
|
57
|
+
if (this.hasPanelTarget) focusFirst(this.panelTarget);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async hidden() {
|
|
61
|
+
if (this.hasTriggerTarget) this.triggerTarget.focus();
|
|
40
62
|
}
|
|
41
63
|
|
|
42
64
|
canLoad() {
|
|
43
|
-
if (this.
|
|
44
|
-
if (this.hasUrlValue) this.
|
|
65
|
+
if (this.hasPanelTarget && this.panelTarget.tagName.toLowerCase() === 'turbo-frame') {
|
|
66
|
+
if (this.hasUrlValue) this.panelTarget.setAttribute('src', this.urlValue);
|
|
45
67
|
return false;
|
|
46
68
|
}
|
|
47
69
|
return true;
|
|
@@ -52,8 +74,8 @@ export default class extends Controller {
|
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
async contentLoaded({ content }) {
|
|
55
|
-
if (this.
|
|
56
|
-
this.
|
|
77
|
+
if (this.hasPanelTarget) {
|
|
78
|
+
this.panelTarget.replaceChildren(this.getContentNode(content));
|
|
57
79
|
}
|
|
58
80
|
if (this.hasLoaderTarget) await this.contentLoaderVisibility.hide();
|
|
59
81
|
}
|
package/src/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export { Formatter, FORMATTER_TYPES } from './plumbers/formatter.js';
|
|
|
18
18
|
|
|
19
19
|
// Export Stimulus controllers
|
|
20
20
|
export { default as CalendarMonthController } from './controllers/calendar_month_controller.js';
|
|
21
|
-
export { default as
|
|
21
|
+
export { default as CalendarObserverController } from './controllers/calendar_observer_controller.js';
|
|
22
22
|
export { default as ClipboardController } from './controllers/clipboard_controller.js';
|
|
23
23
|
export { default as ComboboxDateController } from './controllers/combobox_date_controller.js';
|
|
24
24
|
export { default as ComboboxDropdownController } from './controllers/combobox_dropdown_controller.js';
|
|
@@ -26,8 +26,8 @@ export { default as ComboboxTimeController } from './controllers/combobox_time_c
|
|
|
26
26
|
export { default as DismisserController } from './controllers/dismisser_controller.js';
|
|
27
27
|
export { default as FlipperController } from './controllers/flipper_controller.js';
|
|
28
28
|
export { default as InputComboboxController } from './controllers/input_combobox_controller.js';
|
|
29
|
-
export { default as
|
|
30
|
-
export { default as
|
|
29
|
+
export { default as InputFormatterController } from './controllers/input_formatter_controller.js';
|
|
30
|
+
export { default as InputClearableController } from './controllers/input_clearable_controller.js';
|
|
31
31
|
export { default as ModalController } from './controllers/modal_controller.js';
|
|
32
32
|
export { default as PannerController } from './controllers/panner_controller.js';
|
|
33
33
|
export { default as PopoverController } from './controllers/popover_controller.js';
|
package/src/plumbers/calendar.js
CHANGED
|
@@ -77,6 +77,7 @@ export class Calendar extends Plumber {
|
|
|
77
77
|
this.daysOfWeek = this.buildDaysOfWeek();
|
|
78
78
|
this.daysOfMonth = this.buildDaysOfMonth();
|
|
79
79
|
this.monthsOfYear = this.buildMonthsOfYear();
|
|
80
|
+
this.yearsOfDecade = this.buildYearsOfDecade();
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/**
|
|
@@ -162,6 +163,24 @@ export class Calendar extends Plumber {
|
|
|
162
163
|
return monthsOfYear;
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Builds array of year objects spanning the decade containing the current year.
|
|
168
|
+
* Returns 12 entries: decade start - 1 (buffer) through decade start + 10 (buffer).
|
|
169
|
+
* @returns {Array<Object>} Array of year objects
|
|
170
|
+
*/
|
|
171
|
+
buildYearsOfDecade() {
|
|
172
|
+
const decadeStart = Math.floor(this.year / 10) * 10;
|
|
173
|
+
const yearsOfDecade = [];
|
|
174
|
+
for (let i = decadeStart - 1; i <= decadeStart + 10; i++) {
|
|
175
|
+
yearsOfDecade.push({
|
|
176
|
+
value: i,
|
|
177
|
+
current: i === this.year,
|
|
178
|
+
outside: i < decadeStart || i > decadeStart + 9,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return yearsOfDecade;
|
|
182
|
+
}
|
|
183
|
+
|
|
165
184
|
/**
|
|
166
185
|
* Gets the current "today" reference date.
|
|
167
186
|
* @returns {Date} Today's date
|
|
@@ -177,11 +196,11 @@ export class Calendar extends Plumber {
|
|
|
177
196
|
set today(value) {
|
|
178
197
|
if (!isValidDate(value)) return;
|
|
179
198
|
|
|
180
|
-
const month = this.month
|
|
181
|
-
const year = this.year
|
|
199
|
+
const month = this.month ?? value.getMonth();
|
|
200
|
+
const year = this.year ?? value.getFullYear();
|
|
182
201
|
const sameMonthYear = month == value.getMonth() && year == value.getFullYear();
|
|
183
|
-
const day =
|
|
184
|
-
this.now = new Date(year, month, day)
|
|
202
|
+
const day = sameMonthYear ? value.getDate() : 1;
|
|
203
|
+
this.now = new Date(year, month, day);
|
|
185
204
|
}
|
|
186
205
|
|
|
187
206
|
/**
|
|
@@ -380,6 +399,9 @@ export class Calendar extends Plumber {
|
|
|
380
399
|
get monthsOfYear() {
|
|
381
400
|
return context.monthsOfYear;
|
|
382
401
|
},
|
|
402
|
+
get yearsOfDecade() {
|
|
403
|
+
return context.yearsOfDecade;
|
|
404
|
+
},
|
|
383
405
|
navigate: async (to) => await context.navigate(to),
|
|
384
406
|
step: async (type, value) => await context.step(type, value),
|
|
385
407
|
isDisabled: (date) => context.isDisabled(date),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
2
|
+
|
|
1
3
|
export function isValidDate(value) {
|
|
2
4
|
return value instanceof Date && !isNaN(value);
|
|
3
5
|
}
|
|
@@ -5,8 +7,17 @@ export function isValidDate(value) {
|
|
|
5
7
|
export function tryParseDate(...values) {
|
|
6
8
|
if (values.length === 0) throw 'Missing values to parse as date';
|
|
7
9
|
if (values.length === 1) {
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
10
|
+
const dateValue = values[0];
|
|
11
|
+
if (!dateValue) return undefined;
|
|
12
|
+
if (typeof dateValue === 'string' && DATE_ONLY_PATTERN.test(dateValue)) {
|
|
13
|
+
// YYYY-MM-DD strings are UTC by spec; use local constructor instead
|
|
14
|
+
const [year, month, day] = dateValue.split('-').map(Number);
|
|
15
|
+
const parsed = new Date(year, month - 1, day);
|
|
16
|
+
if (isValidDate(parsed)) return parsed;
|
|
17
|
+
} else {
|
|
18
|
+
const parsed = new Date(dateValue);
|
|
19
|
+
if (isValidDate(parsed)) return parsed;
|
|
20
|
+
}
|
|
10
21
|
} else {
|
|
11
22
|
const parsed = new Date(...values);
|
|
12
23
|
if (isValidDate(parsed)) return parsed;
|
/package/src/controllers/{calendar_month_observer_controller.js → calendar_observer_controller.js}
RENAMED
|
File without changes
|
|
File without changes
|