@unabridged/midwest 0.26.0 → 0.27.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Rails Tests](https://github.com/unabridged/midwest/actions/workflows/rails-tests.yml/badge.svg?branch=main)](https://github.com/unabridged/midwest/actions/workflows/rails-tests.yml)
4
4
  [![Bridgetown Tests](https://github.com/unabridged/midwest/actions/workflows/bridgetown-tests.yml/badge.svg?branch=main)](https://github.com/unabridged/midwest/actions/workflows/bridgetown-tests.yml)
5
5
  [![Ruby](https://img.shields.io/badge/Ruby-3.3_%7C_3.4-CC342D?logo=ruby&logoColor=white)](https://github.com/unabridged/midwest/actions/workflows/ci.yml)
6
- [![Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen)](https://github.com/unabridged/midwest/actions/workflows/ci.yml)
6
+ [![Coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)](https://github.com/unabridged/midwest/actions/workflows/ci.yml)
7
7
 
8
8
  Midwest Component Library is an implementation of the Midwest Design System using [ViewComponent](https://github.com/github/view_component).
9
9
 
@@ -0,0 +1,90 @@
1
+ // Shared runtime for the async (per-component) JS build.
2
+ //
3
+ // In bundled mode every controller is registered up front by
4
+ // `registerMidwestControllers(application)`. In async mode each component's
5
+ // module is fetched on its own, whenever that component first appears on a
6
+ // page — which may be long before or long after the host application calls
7
+ // `Application.start()`. So registration cannot assume an application exists
8
+ // yet: anything that arrives early is queued and flushed once one does.
9
+ //
10
+ // Wiring, for a host app that follows the Rails convention of exposing its
11
+ // instance as `window.Stimulus`, is nothing — the runtime adopts it. Otherwise:
12
+ //
13
+ // import { Application } from '@hotwired/stimulus'
14
+ // const application = Application.start()
15
+ // window.Midwest.useApplication(application)
16
+ //
17
+ // See ASYNC_ASSETS.md §6.2.
18
+
19
+ type ControllerConstructor = unknown
20
+
21
+ interface StimulusApplication {
22
+ register: (identifier: string, controllerConstructor: ControllerConstructor) => void
23
+ }
24
+
25
+ interface MidwestRuntime {
26
+ register: (identifier: string, controllerConstructor: ControllerConstructor) => void
27
+ useApplication: (application: StimulusApplication) => void
28
+ }
29
+
30
+ declare global {
31
+ interface Window {
32
+ Stimulus?: StimulusApplication
33
+ Midwest?: MidwestRuntime
34
+ }
35
+ }
36
+
37
+ const pending: [string, ControllerConstructor][] = []
38
+ const registered = new Set<string>()
39
+ let application: StimulusApplication | null = null
40
+
41
+ // Idempotent by identifier: a component's module can legitimately be requested
42
+ // twice (a Turbo Frame carrying a component the page already had), and while
43
+ // the browser's module map means it only *executes* once per URL, a rebuilt
44
+ // file with a new hash is a different URL and would execute again.
45
+ export function register (identifier: string, controllerConstructor: ControllerConstructor): void {
46
+ if (registered.has(identifier)) return
47
+ registered.add(identifier)
48
+
49
+ if (application) {
50
+ application.register(identifier, controllerConstructor)
51
+ } else {
52
+ pending.push([identifier, controllerConstructor])
53
+ }
54
+ }
55
+
56
+ export function useApplication (app: StimulusApplication): void {
57
+ application = app
58
+ while (pending.length > 0) {
59
+ const [identifier, controllerConstructor] = pending.shift() as [string, ControllerConstructor]
60
+ app.register(identifier, controllerConstructor)
61
+ }
62
+ }
63
+
64
+ // Adoption has to cope with either order, and the common one is the awkward
65
+ // one: `midwest_asset_tags` sits in <head>, so Midwest's modules are usually
66
+ // parsed — and executed — BEFORE the application's own Stimulus boot. Checking
67
+ // `window.Stimulus` once at load time therefore finds nothing, queues every
68
+ // controller, and never flushes. Nothing errors; the page is simply inert.
69
+ //
70
+ // So when there is no application yet, watch the property for one being
71
+ // assigned. Rails' convention of `window.Stimulus = Application.start()` then
72
+ // wires everything up on its own, whichever script happens to run first.
73
+ if (typeof window !== 'undefined') {
74
+ window.Midwest = { register, useApplication }
75
+
76
+ if (window.Stimulus) {
77
+ useApplication(window.Stimulus)
78
+ } else {
79
+ let assigned: StimulusApplication | undefined
80
+ Object.defineProperty(window, 'Stimulus', {
81
+ configurable: true,
82
+ enumerable: true,
83
+ get: () => assigned,
84
+ set (value: StimulusApplication) {
85
+ assigned = value
86
+ if (value) useApplication(value)
87
+ }
88
+ })
89
+ }
90
+ }
@@ -9428,21 +9428,19 @@ class SplitView extends Controller {
9428
9428
 
9429
9429
  class Assets extends Controller {
9430
9430
  static values = {
9431
- css: String,
9432
- js: String
9431
+ css: Array,
9432
+ js: Array
9433
9433
  };
9434
9434
  connect() {
9435
- if (this.cssValue)
9436
- this.ensureStylesheet(this.cssValue);
9437
- if (this.jsValue)
9438
- this.ensureScript(this.jsValue);
9435
+ this.cssValue.forEach((href) => {
9436
+ this.ensureStylesheet(href);
9437
+ });
9438
+ this.jsValue.forEach((src) => {
9439
+ this.ensureScript(src);
9440
+ });
9439
9441
  }
9440
9442
  ensureStylesheet(href) {
9441
- const url = this.absolute(href);
9442
- const present = Array.from(
9443
- document.querySelectorAll('link[rel="stylesheet"]')
9444
- ).some((link2) => link2.href === url);
9445
- if (present)
9443
+ if (!href || this.present('link[rel="stylesheet"]', "href", href))
9446
9444
  return;
9447
9445
  const link = document.createElement("link");
9448
9446
  link.rel = "stylesheet";
@@ -9450,17 +9448,19 @@ class Assets extends Controller {
9450
9448
  document.head.appendChild(link);
9451
9449
  }
9452
9450
  ensureScript(src) {
9453
- const url = this.absolute(src);
9454
- const present = Array.from(
9455
- document.querySelectorAll("script[src]")
9456
- ).some((script2) => script2.src === url);
9457
- if (present)
9451
+ if (!src || this.present("script[src]", "src", src))
9458
9452
  return;
9459
9453
  const script = document.createElement("script");
9460
9454
  script.type = "module";
9461
9455
  script.src = src;
9462
9456
  document.head.appendChild(script);
9463
9457
  }
9458
+ present(selector, attribute, url) {
9459
+ const absolute = this.absolute(url);
9460
+ return Array.from(document.querySelectorAll(selector)).some(
9461
+ (element) => element[attribute] === absolute
9462
+ );
9463
+ }
9464
9464
  // Resolve a possibly-relative URL to its absolute form for comparison.
9465
9465
  absolute(url) {
9466
9466
  const anchor = document.createElement("a");