@unabridged/midwest 0.6.2 → 0.7.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/app/assets/javascript/midwest/index.ts +4 -0
- package/app/assets/javascript/midwest.js +253 -0
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +4 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +4 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +126 -0
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +133 -0
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/summary_component/summary_component_controller.js +131 -0
- package/dist/javascript/collection/app/components/midwest/form/summary_component/summary_component_controller.js.map +1 -0
- package/dist/javascript/midwest.js +253 -0
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,8 @@ import Video from '../../../components/midwest/video_component/video_component_c
|
|
|
12
12
|
import DropdownItem from '../../../components/midwest/dropdown_item_component/dropdown_item_component_controller'
|
|
13
13
|
import Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'
|
|
14
14
|
import Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'
|
|
15
|
+
import Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'
|
|
16
|
+
import FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'
|
|
15
17
|
/* IMPORTS */
|
|
16
18
|
|
|
17
19
|
export { Card, Banner }
|
|
@@ -31,5 +33,7 @@ export function registerMidwestControllers (application: any) {
|
|
|
31
33
|
application.register('midwest-dropdown-item', DropdownItem)
|
|
32
34
|
application.register('midwest-tabs', Tabs)
|
|
33
35
|
application.register('midwest-tooltip', Tooltip)
|
|
36
|
+
application.register('midwest-carousel', Carousel)
|
|
37
|
+
application.register('midwest-form-live-summary', FormLiveSummary)
|
|
34
38
|
/* EXPORTS */
|
|
35
39
|
}
|
|
@@ -2973,6 +2973,257 @@ class Tooltip extends Controller {
|
|
|
2973
2973
|
}
|
|
2974
2974
|
}
|
|
2975
2975
|
|
|
2976
|
+
const NON_SUBMITTING_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
2977
|
+
"button",
|
|
2978
|
+
"checkbox",
|
|
2979
|
+
"color",
|
|
2980
|
+
"file",
|
|
2981
|
+
"hidden",
|
|
2982
|
+
"image",
|
|
2983
|
+
"radio",
|
|
2984
|
+
"range",
|
|
2985
|
+
"reset",
|
|
2986
|
+
"submit"
|
|
2987
|
+
]);
|
|
2988
|
+
class Carousel extends Controller {
|
|
2989
|
+
static targets = ["track", "prev", "next", "dot"];
|
|
2990
|
+
static values = {
|
|
2991
|
+
wizard: { type: Boolean, default: false }
|
|
2992
|
+
};
|
|
2993
|
+
observer = null;
|
|
2994
|
+
_currentIndex = 0;
|
|
2995
|
+
_maxUnlocked = 0;
|
|
2996
|
+
connect() {
|
|
2997
|
+
this.setupObserver();
|
|
2998
|
+
this.updateNavigation();
|
|
2999
|
+
if (this.wizardValue) {
|
|
3000
|
+
this.element.addEventListener("keydown", this.handleKeydown);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
disconnect() {
|
|
3004
|
+
this.observer?.disconnect();
|
|
3005
|
+
this.element.removeEventListener("keydown", this.handleKeydown);
|
|
3006
|
+
}
|
|
3007
|
+
next() {
|
|
3008
|
+
this.trackTarget.scrollBy({ left: this.trackTarget.clientWidth, behavior: "smooth" });
|
|
3009
|
+
}
|
|
3010
|
+
previous() {
|
|
3011
|
+
this.trackTarget.scrollBy({ left: -this.trackTarget.clientWidth, behavior: "smooth" });
|
|
3012
|
+
}
|
|
3013
|
+
goto(event) {
|
|
3014
|
+
const button = event.currentTarget;
|
|
3015
|
+
const index = parseInt(button.dataset.index ?? "0", 10);
|
|
3016
|
+
const slide = this.slides[index];
|
|
3017
|
+
slide?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
|
|
3018
|
+
}
|
|
3019
|
+
// Unlocks the next slide and advances to it. Intended for wizard mode: wire
|
|
3020
|
+
// a "Continue" button inside a slide to this action so users progress
|
|
3021
|
+
// linearly through steps before the carousel nav becomes available.
|
|
3022
|
+
//
|
|
3023
|
+
// <button type="button" data-action="click->midwest-carousel#unlockNext">
|
|
3024
|
+
// Continue
|
|
3025
|
+
// </button>
|
|
3026
|
+
unlockNext() {
|
|
3027
|
+
const nextIndex = this._currentIndex + 1;
|
|
3028
|
+
if (nextIndex > this._maxUnlocked) {
|
|
3029
|
+
this._maxUnlocked = nextIndex;
|
|
3030
|
+
}
|
|
3031
|
+
this.next();
|
|
3032
|
+
}
|
|
3033
|
+
scrolled() {
|
|
3034
|
+
this.updateCurrentIndex();
|
|
3035
|
+
this.updateNavigation();
|
|
3036
|
+
}
|
|
3037
|
+
// Arrow function so `this` is stable for add/removeEventListener pairing.
|
|
3038
|
+
handleKeydown = (event) => {
|
|
3039
|
+
if (event.key !== "Enter")
|
|
3040
|
+
return;
|
|
3041
|
+
const target = event.target;
|
|
3042
|
+
if (target.tagName === "TEXTAREA" || target.tagName !== "INPUT")
|
|
3043
|
+
return;
|
|
3044
|
+
if (NON_SUBMITTING_INPUT_TYPES.has(target.type))
|
|
3045
|
+
return;
|
|
3046
|
+
if (this._currentIndex >= this.slides.length - 1)
|
|
3047
|
+
return;
|
|
3048
|
+
event.preventDefault();
|
|
3049
|
+
this.unlockNext();
|
|
3050
|
+
};
|
|
3051
|
+
get slides() {
|
|
3052
|
+
return Array.from(this.trackTarget.children);
|
|
3053
|
+
}
|
|
3054
|
+
updateCurrentIndex() {
|
|
3055
|
+
const track = this.trackTarget;
|
|
3056
|
+
if (track.clientWidth > 0) {
|
|
3057
|
+
this._currentIndex = Math.round(track.scrollLeft / track.clientWidth);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
setupObserver() {
|
|
3061
|
+
if (!this.hasDotTarget)
|
|
3062
|
+
return;
|
|
3063
|
+
if (!("IntersectionObserver" in window))
|
|
3064
|
+
return;
|
|
3065
|
+
this.observer = new IntersectionObserver(
|
|
3066
|
+
(entries) => {
|
|
3067
|
+
entries.forEach((entry) => {
|
|
3068
|
+
const index = this.slides.indexOf(entry.target);
|
|
3069
|
+
if (index >= 0) {
|
|
3070
|
+
this.dotTargets[index]?.classList.toggle("active", entry.isIntersecting);
|
|
3071
|
+
}
|
|
3072
|
+
});
|
|
3073
|
+
},
|
|
3074
|
+
{ root: this.trackTarget, threshold: 0.5 }
|
|
3075
|
+
);
|
|
3076
|
+
this.slides.forEach((slide) => this.observer?.observe(slide));
|
|
3077
|
+
}
|
|
3078
|
+
updateNavigation() {
|
|
3079
|
+
const track = this.trackTarget;
|
|
3080
|
+
const atStart = track.scrollLeft <= 0;
|
|
3081
|
+
const atEnd = track.scrollLeft >= track.scrollWidth - track.clientWidth - 1;
|
|
3082
|
+
if (this.hasPrevTarget)
|
|
3083
|
+
this.prevTarget.classList.toggle("hide", atStart);
|
|
3084
|
+
if (this.hasNextTarget) {
|
|
3085
|
+
const frontierReached = this.wizardValue && this._currentIndex >= this._maxUnlocked;
|
|
3086
|
+
this.nextTarget.classList.toggle("hide", atEnd || frontierReached);
|
|
3087
|
+
}
|
|
3088
|
+
if (this.wizardValue && this.hasDotTarget) {
|
|
3089
|
+
this.dotTargets.forEach((dot, i) => {
|
|
3090
|
+
const locked = i > this._maxUnlocked;
|
|
3091
|
+
dot.disabled = locked;
|
|
3092
|
+
dot.classList.toggle("locked", locked);
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
const SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
3099
|
+
"hidden",
|
|
3100
|
+
"submit",
|
|
3101
|
+
"reset",
|
|
3102
|
+
"button",
|
|
3103
|
+
"image"
|
|
3104
|
+
]);
|
|
3105
|
+
class FormLiveSummary extends Controller {
|
|
3106
|
+
static targets = ["list"];
|
|
3107
|
+
connect() {
|
|
3108
|
+
this.render();
|
|
3109
|
+
this.form?.addEventListener("input", this.render);
|
|
3110
|
+
this.form?.addEventListener("change", this.render);
|
|
3111
|
+
}
|
|
3112
|
+
disconnect() {
|
|
3113
|
+
this.form?.removeEventListener("input", this.render);
|
|
3114
|
+
this.form?.removeEventListener("change", this.render);
|
|
3115
|
+
}
|
|
3116
|
+
get form() {
|
|
3117
|
+
return this.element.closest("form");
|
|
3118
|
+
}
|
|
3119
|
+
render = () => {
|
|
3120
|
+
const fields = this.collectFields();
|
|
3121
|
+
if (fields.length === 0) {
|
|
3122
|
+
this.listTarget.replaceChildren(
|
|
3123
|
+
Object.assign(document.createElement("p"), {
|
|
3124
|
+
className: "midwest-form-live-summary-empty",
|
|
3125
|
+
textContent: "No values to display."
|
|
3126
|
+
})
|
|
3127
|
+
);
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
const dl = document.createElement("dl");
|
|
3131
|
+
dl.className = "midwest-form-live-summary-list";
|
|
3132
|
+
fields.forEach(({ label, value }) => {
|
|
3133
|
+
const dt = Object.assign(document.createElement("dt"), {
|
|
3134
|
+
className: "midwest-form-live-summary-term",
|
|
3135
|
+
textContent: label
|
|
3136
|
+
});
|
|
3137
|
+
const dd = Object.assign(document.createElement("dd"), {
|
|
3138
|
+
className: "midwest-form-live-summary-definition",
|
|
3139
|
+
textContent: value || "\u2014"
|
|
3140
|
+
});
|
|
3141
|
+
dl.append(dt, dd);
|
|
3142
|
+
});
|
|
3143
|
+
this.listTarget.replaceChildren(dl);
|
|
3144
|
+
};
|
|
3145
|
+
collectFields() {
|
|
3146
|
+
const form = this.form;
|
|
3147
|
+
if (!form)
|
|
3148
|
+
return [];
|
|
3149
|
+
const fields = [];
|
|
3150
|
+
const seenRadioNames = /* @__PURE__ */ new Set();
|
|
3151
|
+
Array.from(form.elements).forEach((el) => {
|
|
3152
|
+
const element = el;
|
|
3153
|
+
if (!element.name)
|
|
3154
|
+
return;
|
|
3155
|
+
if (this.element.contains(element))
|
|
3156
|
+
return;
|
|
3157
|
+
if (element instanceof HTMLButtonElement)
|
|
3158
|
+
return;
|
|
3159
|
+
if (element instanceof HTMLInputElement) {
|
|
3160
|
+
if (SKIPPED_INPUT_TYPES.has(element.type))
|
|
3161
|
+
return;
|
|
3162
|
+
if (element.type === "radio") {
|
|
3163
|
+
if (seenRadioNames.has(element.name))
|
|
3164
|
+
return;
|
|
3165
|
+
seenRadioNames.add(element.name);
|
|
3166
|
+
const checked = form.querySelector(
|
|
3167
|
+
`input[type="radio"][name="${CSS.escape(element.name)}"]:checked`
|
|
3168
|
+
);
|
|
3169
|
+
if (!checked)
|
|
3170
|
+
return;
|
|
3171
|
+
fields.push({
|
|
3172
|
+
label: this.groupLabelFor(element),
|
|
3173
|
+
value: this.labelFor(checked) || checked.value
|
|
3174
|
+
});
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
3177
|
+
if (element.type === "checkbox") {
|
|
3178
|
+
fields.push({
|
|
3179
|
+
label: this.labelFor(element),
|
|
3180
|
+
value: element.checked ? "Yes" : "No"
|
|
3181
|
+
});
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
if (element instanceof HTMLSelectElement) {
|
|
3186
|
+
fields.push({
|
|
3187
|
+
label: this.labelFor(element),
|
|
3188
|
+
value: Array.from(element.selectedOptions).map((o) => o.text).join(", ") || "\u2014"
|
|
3189
|
+
});
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
fields.push({
|
|
3193
|
+
label: this.labelFor(element),
|
|
3194
|
+
value: element.value
|
|
3195
|
+
});
|
|
3196
|
+
});
|
|
3197
|
+
return fields;
|
|
3198
|
+
}
|
|
3199
|
+
// Finds the label associated with a specific element via `for` attribute,
|
|
3200
|
+
// wrapping label, or falls back to a prettified version of the element name.
|
|
3201
|
+
labelFor(el) {
|
|
3202
|
+
const id = el.id;
|
|
3203
|
+
if (id) {
|
|
3204
|
+
const label = this.form?.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
3205
|
+
if (label)
|
|
3206
|
+
return label.textContent?.trim() ?? "";
|
|
3207
|
+
}
|
|
3208
|
+
const wrapper = el.closest("label");
|
|
3209
|
+
if (wrapper)
|
|
3210
|
+
return wrapper.textContent?.trim() ?? "";
|
|
3211
|
+
return this.prettifyName(el.name ?? "");
|
|
3212
|
+
}
|
|
3213
|
+
// For radio groups, prefer the containing fieldset legend over the
|
|
3214
|
+
// individual radio's label, then fall back to prettifying the name.
|
|
3215
|
+
groupLabelFor(el) {
|
|
3216
|
+
const legend = el.closest("fieldset")?.querySelector("legend");
|
|
3217
|
+
if (legend)
|
|
3218
|
+
return legend.textContent?.trim() ?? "";
|
|
3219
|
+
return this.prettifyName(el.name ?? "");
|
|
3220
|
+
}
|
|
3221
|
+
// Converts "user[first_name]" or "first_name" into "First name".
|
|
3222
|
+
prettifyName(name) {
|
|
3223
|
+
return name.replace(/\[.*?\]/g, "").replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase()).trim();
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
|
|
2976
3227
|
function registerMidwestControllers(application) {
|
|
2977
3228
|
application.register("midwest-card", Card);
|
|
2978
3229
|
application.register("midwest-banner", Banner);
|
|
@@ -2988,6 +3239,8 @@ function registerMidwestControllers(application) {
|
|
|
2988
3239
|
application.register("midwest-dropdown-item", DropdownItem);
|
|
2989
3240
|
application.register("midwest-tabs", Tabs);
|
|
2990
3241
|
application.register("midwest-tooltip", Tooltip);
|
|
3242
|
+
application.register("midwest-carousel", Carousel);
|
|
3243
|
+
application.register("midwest-form-live-summary", FormLiveSummary);
|
|
2991
3244
|
}
|
|
2992
3245
|
|
|
2993
3246
|
export { Banner, Card, registerMidwestControllers };
|