compex-sinatra 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8e6491a833f9a894746517767d7633e3e1e40e9cf7a17041546d149aadf67a43
4
+ data.tar.gz: 56eba6739cd4558f535a9d6da9f8ca4c886ba627059dfcb11f45a4e36eea1e5d
5
+ SHA512:
6
+ metadata.gz: 7c410f0e7f41ed38f380d0ac7568b43ee7452d8683c0bb37d41f1d9984d382b01a1c12b0d83d3062aad4cf2a9dba5f4793078df6ab92eda2eba3d0e01c65562c
7
+ data.tar.gz: 6f6d69564b820c8b577f01ef57acbe10975c8a1924e582b97ff36d971f97f819ba52c8d2f57ddf61db9691acedab4a5110de7fed760abaaacb3879019d6ac4fc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Vito Sartori
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Compex::Sinatra
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/compex/sinatra`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ ```bash
14
+ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
15
+ ```
16
+
17
+ If bundler is not being used to manage dependencies, install the gem by executing:
18
+
19
+ ```bash
20
+ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/compex-sinatra. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/compex-sinatra/blob/master/CODE_OF_CONDUCT.md).
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the Compex::Sinatra project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/compex-sinatra/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Compex
4
+ module Sinatra
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base'
4
+ require 'compex'
5
+ require 'debug'
6
+
7
+ require_relative "sinatra/version"
8
+
9
+
10
+ module Sinatra
11
+ module CompEx
12
+ SINATRA_REQUIRED_HASHES_KEY = '__compex_sinatra_required_hashes'
13
+
14
+ def _get_template_engine
15
+ settings.respond_to?(:template_engine) ? settings.template_engine : nil
16
+ end
17
+
18
+ def component(klass, **props)
19
+ layout_name = props.delete(:layout) || :layout
20
+ engine = props.delete(:engine) || _get_template_engine || :erb
21
+ layout_entry = settings.templates[layout_name]
22
+
23
+ # Do not include layouts when loading via XHR
24
+ layout_entry = nil unless request.env['HTTP_X_COMPEX_LOAD'].nil?
25
+
26
+ comp = klass.new(**props)
27
+ html = comp.render_html
28
+
29
+ request.env[SINATRA_REQUIRED_HASHES_KEY] =
30
+ if request.env.key?(SINATRA_REQUIRED_HASHES_KEY)
31
+ request.env[SINATRA_REQUIRED_HASHES_KEY]
32
+ .append(*comp.required_hashes)
33
+ .uniq
34
+ else
35
+ comp.required_hashes
36
+ end
37
+
38
+ if layout_entry
39
+ proc, _file, _line = layout_entry
40
+ source = proc.call
41
+ Tilt[engine].new { source }.render(self) { html }
42
+ else
43
+ html
44
+ end
45
+ end
46
+
47
+ module Helpers
48
+ end
49
+
50
+ class RequiredHashesInjector
51
+ def initialize(app)
52
+ @app = app
53
+ end
54
+
55
+ def call(env)
56
+ status, headers, body = @app.call(env)
57
+
58
+ if status == 200 && env[SINATRA_REQUIRED_HASHES_KEY]
59
+ headers['X-CompEx-RequiredHashes'] = env[SINATRA_REQUIRED_HASHES_KEY].join(',')
60
+ end
61
+
62
+ [status, headers, body]
63
+ end
64
+ end
65
+
66
+ class ScriptInjector
67
+ def initialize(app)
68
+ @app = app
69
+ end
70
+
71
+ def call(env)
72
+ status, headers, body = @app.call(env)
73
+ return [status, headers, body] unless headers["Content-Type"]&.include?("text/html")
74
+
75
+ content = +""
76
+ body.each { |chunk| content << chunk }
77
+ body.close if body.respond_to?(:close)
78
+
79
+ # Inject scripts before </body>
80
+ scripts = env.fetch(SINATRA_REQUIRED_HASHES_KEY, [])
81
+ .filter { |v| v.start_with? ("j") }
82
+ unless scripts.empty?
83
+ result = scripts.map do |id|
84
+ asset = ::CompEx::AssetProvider.provide(id)
85
+ next unless asset # TODO: Warn?
86
+
87
+ "<script type=\"text/javascript\" cx-asset=\"#{id}\">#{asset}</script>"
88
+ end
89
+ content.sub!(%r{</body>}i, "#{result.join}</body>")
90
+ end
91
+
92
+ headers["Content-Length"] = content.bytesize.to_s
93
+ body = [content]
94
+
95
+ [status, headers, body]
96
+ end
97
+ end
98
+
99
+ class StyleInjector
100
+ def initialize(app)
101
+ @app = app
102
+ end
103
+
104
+ def call(env)
105
+ status, headers, body = @app.call(env)
106
+ return [status, headers, body] unless headers["Content-Type"]&.include?("text/html")
107
+
108
+ content = +""
109
+ body.each { |chunk| content << chunk }
110
+ body.close if body.respond_to?(:close)
111
+
112
+ # Inject styles before </head>
113
+ styles = env.fetch(SINATRA_REQUIRED_HASHES_KEY, [])
114
+ .filter { |v| v.start_with? ("c") }
115
+ unless styles.empty?
116
+ result = styles.map do |id|
117
+ asset = ::CompEx::AssetProvider.provide(id)
118
+ next unless asset # TODO: Warn?
119
+
120
+ "<style cx-asset=\"#{id}\">#{asset}</style>"
121
+ end
122
+ content.sub!(%r{</head>}i, "#{result.join}</head>")
123
+ end
124
+
125
+ headers["Content-Length"] = content.bytesize.to_s
126
+ body = [content]
127
+
128
+ [status, headers, body]
129
+ end
130
+ end
131
+
132
+ def self.registered(app)
133
+ app.helpers Sinatra::CompEx::Helpers
134
+ app.use Sinatra::CompEx::RequiredHashesInjector
135
+ app.use Sinatra::CompEx::StyleInjector
136
+ app.use Sinatra::CompEx::ScriptInjector
137
+
138
+ app.get '/_compex/assets' do
139
+ hashes = request.params.fetch('h', '').split(',').map(&:strip)
140
+ result = []
141
+ hashes.each do |h|
142
+ asset = ::CompEx::AssetProvider.provide(h)
143
+ next result << nil unless asset
144
+
145
+ result << {
146
+ type: h.start_with?("j_") ? "script" : "style",
147
+ hash: h,
148
+ source: asset,
149
+ }
150
+ end
151
+
152
+ headers "Content-Type" => "application/json"
153
+ result.to_json
154
+ end
155
+ end
156
+ end
157
+
158
+ helpers Sinatra::CompEx
159
+ register Sinatra::CompEx
160
+ end
data/sample/app.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'compex/sinatra'
2
+
3
+ require 'sinatra'
4
+
5
+ class MyComponent < CompEx::Base
6
+ html <<~HTML
7
+ <div class="base">
8
+ <Counter />
9
+ <MyForm cx-on:namespace:eventName="customHandler" />
10
+ </div>
11
+ HTML
12
+
13
+ style <<~CSS
14
+ .base { font-family: sans-serif; }
15
+ CSS
16
+
17
+ js <<~JS
18
+ class Component {
19
+ onClick(ev) { console.log("OnClick", ev) }
20
+ customHandler(ev) { console.log("customHandler", ev) }
21
+ }
22
+ JS
23
+ end
24
+
25
+ class Counter < CompEx::Base
26
+ html <<~HTML
27
+ <div>
28
+ <div cx-ref="counter">0</div>
29
+ <button cx-click="onIncr">+</button><button cx-click="onDecr">-</button>
30
+ </div>
31
+ HTML
32
+
33
+ js <<~JS
34
+ class Component {
35
+ async mount() {
36
+ this.value = 0;
37
+ this.updateText();
38
+ }
39
+
40
+ updateText() {
41
+ this.counter.innerText = `${this.value}`
42
+ }
43
+
44
+ onIncr() {
45
+ this.value++;
46
+ this.updateText();
47
+ }
48
+
49
+ onDecr() {
50
+ this.value--;
51
+ this.updateText();
52
+ }
53
+ }
54
+ JS
55
+ end
56
+
57
+ class MyForm < CompEx::Base
58
+ html <<~HTML
59
+ Accepted? <input type="checkbox" cx-bind="accepted" cx-change="onChange" /> <br/>
60
+ <input type="checkbox" cx-bind="bla" value="a" />
61
+ <input type="checkbox" cx-bind="bla" value="b" />
62
+ <input type="checkbox" cx-bind="bla" value="c" /> <br/>
63
+ <input type="radio" cx-bind="ble" value="a" />
64
+ <input type="radio" cx-bind="ble" value="b" />
65
+ <input type="radio" cx-bind="ble" value="c" /> <br />
66
+ <select cx-bind="bli">
67
+ <option value="foo">Foo</option>
68
+ <option value="bar">Bar</option>
69
+ <option value="baz">Baz</option>
70
+ </select> <br />
71
+ <select multiple cx-bind="blo">
72
+ <option value="foo">Foo</option>
73
+ <option value="bar">Bar</option>
74
+ <option value="baz">Baz</option>
75
+ </select><br/>
76
+ <button cx-click="dispatchCustomEvent">Custom Event</button>
77
+ HTML
78
+
79
+ js <<~JS
80
+ class Component {
81
+ async mount() {
82
+ window.xxx = this;
83
+ }
84
+
85
+ onChange(el) {
86
+ debugger;
87
+ }
88
+
89
+ dispatchCustomEvent() {
90
+ this.emit("namespace:eventName", { test: true });
91
+ }
92
+ }
93
+ JS
94
+ end
95
+
96
+ set :template_engine, :erb
97
+
98
+ template(:layout) do
99
+ <<~HTML
100
+ <!DOCTYPE html>
101
+ <html>
102
+ <head>
103
+ <meta charset="utf-8">
104
+ <meta name="viewport" content="width=device-width, initial-scale=1">
105
+ <title>My App</title>
106
+ <script type="text/javascript" src="bundle.js"></script>
107
+ </head>
108
+ <body>
109
+ <%= yield %>
110
+ </body>
111
+ </html>
112
+ HTML
113
+ end
114
+
115
+
116
+ get '/' do
117
+ component(MyComponent)
118
+ end
@@ -0,0 +1,591 @@
1
+ (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/utils.js
24
+ var utils_exports = {};
25
+ __export(utils_exports, {
26
+ default: () => utils_default
27
+ });
28
+ var Utils, utils_default;
29
+ var init_utils = __esm({
30
+ "src/utils.js"() {
31
+ Utils = {
32
+ isCheckable(node) {
33
+ const name = node.nodeName;
34
+ const type = node.type;
35
+ return name && name.toLowerCase() === "input" && (type === "checkbox" || type === "radio");
36
+ },
37
+ getValueForNode(node) {
38
+ let value = "";
39
+ if (!node) return value;
40
+ value = this.isCheckable(node) ? node.checked : node.value;
41
+ return value;
42
+ },
43
+ setValueForNode(node, value) {
44
+ if (!node) return;
45
+ if (this.isCheckable(node)) {
46
+ node.checked = value !== "false" && !!value;
47
+ } else {
48
+ node.value = value;
49
+ }
50
+ },
51
+ getSelectionForNode(node) {
52
+ if ("selectionStart" in node) {
53
+ return {
54
+ start: node.selectionStart,
55
+ end: node.selectionEnd
56
+ };
57
+ }
58
+ return { start: 0, end: 0 };
59
+ },
60
+ setSelectionForNode(node, bounds) {
61
+ if (!("selectionStart" in node)) return;
62
+ const start = bounds.start;
63
+ let end = bounds.end;
64
+ if (end === void 0) end = start;
65
+ node.selectionStart = start;
66
+ node.selectionEnd = Math.min(end, node.value.length);
67
+ },
68
+ randomID() {
69
+ const bytes = new Uint8Array(8);
70
+ crypto.getRandomValues(bytes);
71
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
72
+ }
73
+ };
74
+ utils_default = Utils;
75
+ }
76
+ });
77
+
78
+ // src/eventing.js
79
+ var eventing_exports = {};
80
+ __export(eventing_exports, {
81
+ default: () => eventing_default
82
+ });
83
+ var globalEventsListener, SyntheticEvent, Eventing, eventing_default;
84
+ var init_eventing = __esm({
85
+ "src/eventing.js"() {
86
+ globalEventsListener = "_compex_install" + Math.random().toString(36).slice(2);
87
+ SyntheticEvent = class {
88
+ constructor(type, target = null, options = {}) {
89
+ const {
90
+ detail = null,
91
+ bubbles = false,
92
+ cancelable = false,
93
+ composed = false
94
+ } = options || {};
95
+ this.type = String(type);
96
+ this.target = target;
97
+ this.currentTarget = null;
98
+ this.eventPhase = 0;
99
+ this.bubbles = !!bubbles;
100
+ this.cancelable = !!cancelable;
101
+ this.composed = !!composed;
102
+ this.detail = detail;
103
+ this.defaultPrevented = false;
104
+ this.cancelBubble = false;
105
+ this.timeStamp = performance.now();
106
+ this._path = null;
107
+ }
108
+ stopPropagation() {
109
+ this.cancelBubble = true;
110
+ }
111
+ stopImmediatePropagation() {
112
+ this.cancelBubble = true;
113
+ this._immediateStopped = true;
114
+ }
115
+ preventDefault() {
116
+ if (this.cancelable) {
117
+ this.defaultPrevented = true;
118
+ this.returnValue = false;
119
+ }
120
+ }
121
+ composedPath() {
122
+ if (this._path) return [...this._path];
123
+ const path = [];
124
+ let node = this.target;
125
+ while (node) {
126
+ path.push(node);
127
+ node = node.parent || node.host || null;
128
+ }
129
+ return path;
130
+ }
131
+ };
132
+ Eventing = class _Eventing {
133
+ static eventListenerSet = /* @__PURE__ */ new Set();
134
+ static domEventTypes = ["abort", "beforeinput", "beforetoggle", "blur", "canplay", "canplaythrough", "cancel", "change", "click", "close", "compositionend", "compositionstart", "compositionupdate", "contextmenu", "copy", "cut", "dblclick", "auxclick", "drag", "dragend", "dragenter", "dragexit", "dragleave", "dragover", "dragstart", "drop", "durationchange", "emptied", "encrypted", "ended", "error", "focus", "focusin", "focusout", "fullscreenchange", "gotpointercapture", "hashchange", "input", "invalid", "keydown", "keypress", "keyup", "load", "loadstart", "loadeddata", "loadedmetadata", "lostpointercapture", "message", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "paste", "pause", "play", "playing", "pointercancel", "pointerdown", "pointerenter", "pointerleave", "pointermove", "pointerout", "pointerover", "pointerup", "popstate", "progress", "ratechange", "reset", "resize", "scroll", "scrollend", "seeked", "seeking", "select", "selectstart", "selectionchange", "stalled", "submit", "suspend", "timeupdate", "toggle", "touchcancel", "touchend", "touchmove", "touchstart", "volumechange", "waiting", "wheel"];
135
+ // List of events that need to be individually attached to media elements.
136
+ static mediaEventTypes = ["abort", "canplay", "canplaythrough", "durationchange", "emptied", "encrypted", "ended", "error", "loadeddata", "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", "ratechange", "resize", "seeked", "seeking", "stalled", "suspend", "timeupdate", "volumechange", "waiting"];
137
+ // We should not delegate these events to the container, but rather
138
+ // set them on the actual target element itself. This is primarily
139
+ // because these events do not consistently bubble in the DOM.
140
+ static nonDelegatedEvents = /* @__PURE__ */ new Set([
141
+ "beforetoggle",
142
+ "cancel",
143
+ "close",
144
+ "invalid",
145
+ "load",
146
+ "scroll",
147
+ "scrollend",
148
+ "toggle",
149
+ // In order to reduce bytes, we insert the above array of media events
150
+ // into this Set. Note: the "error" event isn't an exclusive media event,
151
+ // and can occur on other elements too. Rather than duplicate that event,
152
+ // we just take it from the media events array.
153
+ ...this.mediaEventTypes
154
+ ]);
155
+ static cxEventAttributes = new Set(_Eventing.domEventTypes.map((i) => `cx-${i}`));
156
+ static cxEventSelector = _Eventing.cxEventAttributes.values().map((i) => `:scope > :not([cx-controller]) [${i}], :scope > [${i}]`).toArray().join(",");
157
+ static controllerHandles = /* @__PURE__ */ new Map();
158
+ static eventHandles = /* @__PURE__ */ new Map();
159
+ static attachHandler(ctrl, el, name, handler) {
160
+ if (this.controllerHandles.has(ctrl)) {
161
+ this.controllerHandles.get(ctrl).push(handler);
162
+ } else {
163
+ this.controllerHandles.set(ctrl, [handler]);
164
+ }
165
+ this.registerHandler(el, name, handler);
166
+ }
167
+ static registerHandler(el, name, handler) {
168
+ if (this.eventHandles.has(name)) {
169
+ if (this.eventHandles.get(name).has(el)) {
170
+ this.eventHandles.get(name).get(el).push(handler);
171
+ } else {
172
+ this.eventHandles.get(name).set(el, [handler]);
173
+ }
174
+ } else {
175
+ this.eventHandles.set(name, /* @__PURE__ */ new Map([[el, [handler]]]));
176
+ }
177
+ }
178
+ static installEventHandlers() {
179
+ if (document[globalEventsListener]) {
180
+ return;
181
+ }
182
+ document[globalEventsListener] = true;
183
+ for (const ev of CompEx.Eventing.domEventTypes) {
184
+ const isCapturePhaseListener = CompEx.Eventing.nonDelegatedEvents.has(ev);
185
+ this.installNativeEventHandler(ev, isCapturePhaseListener);
186
+ }
187
+ }
188
+ static installNativeEventHandler(ev, isCapturePhaseListener) {
189
+ const listenerSetKey = this.eventListenerKey(ev, isCapturePhaseListener);
190
+ if (this.eventListenerSet.has(listenerSetKey)) return;
191
+ document.addEventListener(ev, this.eventHandler(isCapturePhaseListener), isCapturePhaseListener);
192
+ this.eventListenerSet.add(listenerSetKey);
193
+ }
194
+ static eventHandler(isCapturePhaseListener) {
195
+ return isCapturePhaseListener ? this.captureEventHandler.bind(this) : this.bubbleEventHandler.bind(this);
196
+ }
197
+ static captureEventHandler(ev) {
198
+ debugger;
199
+ }
200
+ static bubbleEventHandler(ev) {
201
+ const handles = this.eventHandles.get(ev.type);
202
+ if (!handles) return;
203
+ const elHandlers = handles.get(ev.target);
204
+ if (!elHandlers) return;
205
+ for (const handler of elHandlers) {
206
+ handler(ev);
207
+ if (ev.defaultPrevented) return;
208
+ }
209
+ }
210
+ static dispatchSyntheticEvent(type, target, detail = null) {
211
+ const ev = new SyntheticEvent(type, target, detail);
212
+ const handles = this.eventHandles.get(ev.type);
213
+ if (!handles) return;
214
+ for (const handler of handles.values().toArray().flat()) {
215
+ handler(ev);
216
+ if (ev.defaultPrevented) return;
217
+ }
218
+ }
219
+ static eventListenerKey(name, isCapturePhaseListener) {
220
+ return `${name}__${isCapturePhaseListener ? "capture" : "bubble"}`;
221
+ }
222
+ };
223
+ eventing_default = Eventing;
224
+ }
225
+ });
226
+
227
+ // src/runtime.js
228
+ var runtime_exports = {};
229
+ __export(runtime_exports, {
230
+ default: () => runtime_default
231
+ });
232
+ var Runtime, runtime_default;
233
+ var init_runtime = __esm({
234
+ "src/runtime.js"() {
235
+ Runtime = class {
236
+ static registry = {};
237
+ static instances = /* @__PURE__ */ new WeakMap();
238
+ static observer = new MutationObserver((muts) => {
239
+ for (const mut of muts) {
240
+ for (const node of mut.addedNodes) {
241
+ if (node.nodeType !== Node.ELEMENT_NODE || !node.getAttribute("cx-controller")) continue;
242
+ this.attachController(node);
243
+ }
244
+ for (const node of mut.removedNodes) {
245
+ if (node.nodeType !== Node.ELEMENT_NODE || !node.getAttribute("cx-controller")) continue;
246
+ this.detachController(node);
247
+ }
248
+ }
249
+ });
250
+ static loadedHashesSet = /* @__PURE__ */ new Set();
251
+ static attachController(node) {
252
+ const ctrlName = node.getAttribute("cx-controller");
253
+ const ctrl = this.registry[ctrlName];
254
+ if (!ctrl) {
255
+ console.warn(`Missing JS controller for element ${node}: ${ctrlName}`);
256
+ return;
257
+ }
258
+ const inst = new ctrl(node);
259
+ this.instances.set(node, inst);
260
+ let mountRet = inst.mount();
261
+ if (mountRet instanceof Promise) {
262
+ mountRet.catch(console.error.bind(console));
263
+ }
264
+ }
265
+ static detachController(node) {
266
+ const inst = this.instances[node];
267
+ if (!inst) return;
268
+ inst.unmount().catch(console.error.bind(console)).finally(() => {
269
+ this.instances.delete(node);
270
+ });
271
+ }
272
+ static install() {
273
+ this.installed = true;
274
+ CompEx.Eventing.installEventHandlers();
275
+ for (const el of document.querySelectorAll("[cx-controller]")) this.attachController(el);
276
+ for (const el of document.querySelectorAll("[cx-asset]")) this.loadedHashesSet.add(el.getAttribute("cx-asset"));
277
+ this.observer.observe(document, { childList: true });
278
+ }
279
+ static register(name, klass) {
280
+ this.registry[name] = klass;
281
+ console.log(`CompEx: Registered ${name}`);
282
+ }
283
+ static async handleRequiredHashesHeader(value) {
284
+ if (!value) return;
285
+ const toRequire = value.split(",").filter((v) => !this.loadedHashesSet.has(v));
286
+ const requireParams = new URLSearchParams([["h", toRequire]]);
287
+ const resp = await fetch("/_compex/assets?" + requireParams.toString(), {
288
+ method: "GET"
289
+ });
290
+ if (!resp.ok) {
291
+ console.error(resp.message);
292
+ return;
293
+ }
294
+ const json = await resp.json();
295
+ for (const [idx, item] of json.entries()) {
296
+ if (!item) {
297
+ console.warn(`CompEx: Could not retrieve asset for hash ${toRequire[idx]}`);
298
+ continue;
299
+ }
300
+ switch (item.type) {
301
+ case "style": {
302
+ const st = document.createElement("style");
303
+ st.setAttribute("cx-asset", item.hash);
304
+ st.textContent = item.source;
305
+ document.head.append(st);
306
+ this.loadedHashesSet.add(item.hash);
307
+ break;
308
+ }
309
+ case "script": {
310
+ const sc = document.createElement("script");
311
+ sc.setAttribute("cx-asset", item.hash);
312
+ sc.innerHTML = item.source;
313
+ document.head.append(sc);
314
+ this.loadedHashesSet.add(item.hash);
315
+ break;
316
+ }
317
+ default: {
318
+ console.error(`Unknown asset of type ${item.type}`);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ };
324
+ runtime_default = Runtime;
325
+ }
326
+ });
327
+
328
+ // src/multibind.js
329
+ var multibind_exports = {};
330
+ __export(multibind_exports, {
331
+ default: () => multibind_default
332
+ });
333
+ var MultiBind, multibind_default;
334
+ var init_multibind = __esm({
335
+ "src/multibind.js"() {
336
+ MultiBind = class {
337
+ constructor(els) {
338
+ this.els = els;
339
+ this.isSingle = els[0].type.toLowerCase() === "radio";
340
+ }
341
+ get values() {
342
+ if (this.isSingle) {
343
+ const sel = this.els.find((el) => el.checked);
344
+ return sel ? sel.value : void 0;
345
+ }
346
+ return this.els.filter((el) => el.checked).map((el) => el.value);
347
+ }
348
+ set values(values) {
349
+ if (this.isSingle) {
350
+ if (Array.isArray(values)) {
351
+ console.warn("CompEx: Setting an array to a radio group binding has no effect.");
352
+ return;
353
+ }
354
+ const toSel = this.els.find((el) => el.value === values);
355
+ if (toSel) toSel.checked = true;
356
+ return;
357
+ }
358
+ if (!Array.isArray(values)) {
359
+ values = [values];
360
+ }
361
+ const vals = new Set(values);
362
+ for (const el of this.els) {
363
+ el.checked = vals.has(el.value);
364
+ }
365
+ }
366
+ };
367
+ multibind_default = MultiBind;
368
+ }
369
+ });
370
+
371
+ // src/component.js
372
+ var component_exports = {};
373
+ __export(component_exports, {
374
+ default: () => component_default
375
+ });
376
+ var Component, component_default;
377
+ var init_component = __esm({
378
+ "src/component.js"() {
379
+ Component = class {
380
+ constructor(root) {
381
+ this.props = {};
382
+ this.root = root;
383
+ this.bindings = /* @__PURE__ */ new Map();
384
+ this.unsafeAttachRefs();
385
+ this.unsafeAttachProps();
386
+ this.unsafeAttachBinds();
387
+ this.unsafeAttachEvents();
388
+ }
389
+ unsafeAttachRefs() {
390
+ const els = this.root.querySelectorAll(":scope > :not([cx-controller]) [cx-ref], :scope > [cx-ref]");
391
+ for (const el of els) {
392
+ this[el.getAttribute("cx-ref")] = el;
393
+ }
394
+ if (this.root.getAttribute("cx-ref")) {
395
+ this[this.root.getAttribute("cx-ref")] = this.root;
396
+ }
397
+ }
398
+ unsafeAttachProps() {
399
+ const bag = this.root.querySelector("script[type='vendor/compex-data-bag']");
400
+ if (!bag) return;
401
+ try {
402
+ this.props = JSON.parse(bag.textContent);
403
+ } catch (ex) {
404
+ console.error("CompEx: Failed parsing data bag", ex);
405
+ }
406
+ }
407
+ unsafeAttachEvents() {
408
+ for (const el of this.root.querySelectorAll(CompEx.Eventing.cxEventSelector)) {
409
+ this.unsafeAttachEventsForNode(el);
410
+ }
411
+ for (const el of this.root.querySelectorAll(":scope > :not([cx-controller]) [cx-custom-handlers], :scope > [cx-custom-handlers]")) {
412
+ this.unsafeAttachCustomEventsForNode(el);
413
+ }
414
+ }
415
+ unsafeAttachCustomEventsForNode(el) {
416
+ const descriptor = el.getAttribute("cx-custom-handlers");
417
+ if (!descriptor) return;
418
+ const descriptors = descriptor.split(",").map((i) => i.split("->"));
419
+ for (const [name, handler] of descriptors) {
420
+ this.unsafeAttachSingleEvent(this.root, name, handler);
421
+ }
422
+ }
423
+ unsafeAttachEventsForNode(el) {
424
+ for (const attr of el.getAttributeNames()) {
425
+ if (!CompEx.Eventing.cxEventAttributes.has(attr)) return;
426
+ this.unsafeAttachSingleEvent(el, attr.replace("cx-", ""), el.getAttribute(attr));
427
+ }
428
+ }
429
+ unsafeAttachSingleEvent(root, name, handlerName) {
430
+ const handler = this[handlerName];
431
+ if (!handler) {
432
+ console.warn(`Ignored attaching event: Handler ${handlerName} is not defined on`, root);
433
+ return;
434
+ }
435
+ if (typeof handler !== "function") {
436
+ console.warn(`Ignored attaching event: Handler ${handlerName} is not a function on`, root);
437
+ return;
438
+ }
439
+ CompEx.Eventing.attachHandler(this, root, name, handler.bind(this));
440
+ }
441
+ unsafeAttachBinds() {
442
+ for (const el of this.root.querySelectorAll(":scope > :not([cx-controller]) [cx-bind], :scope > [cx-bind]")) {
443
+ const name = el.getAttribute("cx-bind");
444
+ if (this.bindings.has(name)) {
445
+ this.bindings.get(name).push(el);
446
+ } else {
447
+ this.bindings.set(name, [el]);
448
+ }
449
+ }
450
+ for (const [k, v] of this.bindings.entries()) {
451
+ if (v.length > 1) {
452
+ this.unsafeAttachMultiInputBind(k, v);
453
+ } else if (v[0].tagName.toLowerCase() === "select") {
454
+ this.unsafeAttachSelectBind(k, v[0]);
455
+ } else {
456
+ this.unsafeAttachSingleInputBind(k, v[0]);
457
+ }
458
+ }
459
+ }
460
+ unsafeAttachSelectBind(k, el) {
461
+ const opts = [...el.querySelectorAll("option")];
462
+ const isMultiple = el.hasAttribute("multiple");
463
+ console.log("unsafeAttachSelectBind", k, el, isMultiple);
464
+ const set = (val) => {
465
+ if (!isMultiple && Array.isArray(val)) {
466
+ console.warn(`CompEx: Setting an array to a non-multiple Select (cx-bind="${k}") has no effect.`);
467
+ } else if (isMultiple) {
468
+ val = Array.isArray(val) ? val : [val];
469
+ const selOpts = new Set(val);
470
+ for (const el2 of opts) {
471
+ el2.selected = selOpts.has(el2.value);
472
+ }
473
+ } else {
474
+ for (const el2 of opts) {
475
+ el2.selected = el2.value === val;
476
+ }
477
+ }
478
+ };
479
+ const get = () => {
480
+ const selected = opts.filter((el2) => el2.selected);
481
+ if (!isMultiple) return selected.length === 1 ? selected[0].value : void 0;
482
+ return selected.map((el2) => el2.value);
483
+ };
484
+ this.unsafeCreateLocalProperty(k, get, set);
485
+ }
486
+ unsafeAttachMultiInputBind(k, els) {
487
+ const nodeTags = els.map((el) => el.tagName.toLowerCase());
488
+ const isInput = nodeTags.includes("input");
489
+ if (isInput) {
490
+ console.log(nodeTags);
491
+ if (!nodeTags.every((tag) => tag === "input")) {
492
+ throw new Error(`cx-bind="${k}": All bindings under the same name are required to be inputs.`);
493
+ }
494
+ } else {
495
+ }
496
+ if (isInput) {
497
+ const inputTypes = els.map((el) => el.type.toLowerCase());
498
+ if (inputTypes.some((t) => t !== "checkbox" && t !== "radio")) {
499
+ throw new Error(`cx-bind="${k}": Only checkbox and radio inputs are supported in multi-bind input elements.`);
500
+ }
501
+ const firstType = inputTypes[0];
502
+ if (inputTypes.some((t) => t !== firstType)) {
503
+ throw new Error(`cx-bind="${k}": Inputs must have the same type "${firstType}"`);
504
+ }
505
+ if (els.some((el) => !el.getAttribute("value"))) {
506
+ throw new Error(`cx-bind="${k}": All inputs in a group must have a value`);
507
+ }
508
+ if (inputTypes[0] === "radio" && els.some((el) => !el.name)) {
509
+ let name;
510
+ const nameEl = els.find((el) => Boolean(el.name));
511
+ name = nameEl ? nameEl.name : `compex-radio-${CompEx.Utils.randomID()}`;
512
+ for (const el of els) el.name = name;
513
+ }
514
+ const mb = new CompEx.MultiBind(els);
515
+ this.unsafeCreateLocalProperty(k, () => mb.values, (v) => mb.values = v);
516
+ }
517
+ }
518
+ unsafeAttachSingleInputBind(name, el) {
519
+ const getter = () => CompEx.Utils.getValueForNode(el);
520
+ const setter = (val) => CompEx.Utils.setValueForNode(el, val);
521
+ this.unsafeCreateLocalProperty(name, getter, setter);
522
+ }
523
+ unsafeCreateLocalProperty(name, getter, setter) {
524
+ Object.defineProperty(this, name, {
525
+ get() {
526
+ return getter();
527
+ },
528
+ set(newValue) {
529
+ setter(newValue);
530
+ },
531
+ enumerable: true,
532
+ configurable: true
533
+ });
534
+ console.log(`CompEx: Registered prop ${name}`);
535
+ }
536
+ async mount() {
537
+ }
538
+ async unmount() {
539
+ }
540
+ emit(name, detail) {
541
+ detail = detail || {};
542
+ detail.controller = this;
543
+ CompEx.Eventing.dispatchSyntheticEvent(name, this.root, detail);
544
+ }
545
+ async loadComponent(path) {
546
+ const result = await fetch(path, {
547
+ method: "GET",
548
+ mode: "cors",
549
+ credentials: "same-origin",
550
+ headers: {
551
+ "Accept": "text/html",
552
+ "X-CompEx-Load": "true"
553
+ }
554
+ });
555
+ if (result.ok) {
556
+ const loadProm = CompEx.Runtime.handleRequiredHashesHeader(result.headers.get("X-CompEx-RequiredHashes"));
557
+ const div = document.createElement("div");
558
+ div.innerHTML = await result.text();
559
+ await loadProm;
560
+ return div.firstChild;
561
+ } else {
562
+ throw result.error;
563
+ }
564
+ }
565
+ };
566
+ component_default = Component;
567
+ }
568
+ });
569
+
570
+ // src/main.js
571
+ ((w, d) => {
572
+ const Utils2 = (init_utils(), __toCommonJS(utils_exports)).default;
573
+ const Eventing2 = (init_eventing(), __toCommonJS(eventing_exports)).default;
574
+ const Runtime2 = (init_runtime(), __toCommonJS(runtime_exports)).default;
575
+ const MultiBind2 = (init_multibind(), __toCommonJS(multibind_exports)).default;
576
+ const Component2 = (init_component(), __toCommonJS(component_exports)).default;
577
+ w.CompEx = class CompEx {
578
+ static Runtime = Runtime2;
579
+ static Component = Component2;
580
+ static Utils = Utils2;
581
+ static Eventing = Eventing2;
582
+ static MultiBind = MultiBind2;
583
+ };
584
+ if (d.readyState === "loading") {
585
+ addEventListener("DOMContentLoaded", () => CompEx.Runtime.install());
586
+ } else {
587
+ CompEx.Runtime.install();
588
+ }
589
+ })(globalThis, document);
590
+ })();
591
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/utils.js", "../src/eventing.js", "../src/runtime.js", "../src/multibind.js", "../src/component.js", "../src/main.js"],
4
+ "sourcesContent": ["const Utils = {\n isCheckable(node) {\n const name = node.nodeName;\n const type = node.type;\n\n return name && name.toLowerCase() === 'input' &&\n (type === 'checkbox' || type === 'radio');\n },\n\n getValueForNode(node) {\n let value = '';\n if (!node) return value;\n\n value = this.isCheckable(node) ? node.checked : node.value;\n\n return value;\n },\n\n setValueForNode(node, value) {\n if (!node) return;\n\n if (this.isCheckable(node)) {\n node.checked = value !== \"false\" && !!value;\n } else {\n node.value = value;\n }\n },\n\n getSelectionForNode(node) {\n if ('selectionStart' in node) {\n return {\n start: node.selectionStart,\n end: node.selectionEnd,\n };\n }\n\n return {start: 0, end: 0};\n },\n setSelectionForNode(node, bounds) {\n if (!('selectionStart' in node)) return;\n\n const start = bounds.start;\n let end = bounds.end;\n if (end === undefined) end = start;\n\n node.selectionStart = start;\n node.selectionEnd = Math.min(end, node.value.length);\n },\n\n randomID() {\n const bytes = new Uint8Array(8);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');\n },\n};\n\nexport default Utils;\n", "const globalEventsListener = '_compex_install' + Math.random().toString(36).slice(2);\n\nclass SyntheticEvent {\n constructor(type, target = null, options = {}) {\n\n const {\n detail = null,\n bubbles = false,\n cancelable = false,\n composed = false\n } = options || {};\n\n // Core readonly-like properties\n this.type = String(type);\n this.target = target;\n this.currentTarget = null; // set by dispatch system\n this.eventPhase = 0; // NONE = 0, CAPTURING_PHASE = 1, AT_TARGET = 2, BUBBLING_PHASE = 3\n\n // EventInit\n this.bubbles = !!bubbles;\n this.cancelable = !!cancelable;\n this.composed = !!composed;\n this.detail = detail;\n\n // State\n this.defaultPrevented = false;\n this.cancelBubble = false; // used by legacy code (stopPropagation)\n this.timeStamp = performance.now();\n this._path = null;\n }\n\n stopPropagation() {\n this.cancelBubble = true;\n }\n\n stopImmediatePropagation() {\n this.cancelBubble = true;\n this._immediateStopped = true;\n }\n\n preventDefault() {\n if (this.cancelable) {\n this.defaultPrevented = true;\n this.returnValue = false;\n }\n }\n\n composedPath() {\n // If a custom path was set by the dispatch system, return it.\n if (this._path) return [...this._path];\n\n // Otherwise, derive it from target hierarchy.\n const path = [];\n let node = this.target;\n while (node) {\n path.push(node);\n node = node.parent || node.host || null;\n }\n return path;\n }\n}\n\nclass Eventing {\n static eventListenerSet = new Set();\n static domEventTypes = ['abort', 'beforeinput', 'beforetoggle', 'blur', 'canplay', 'canplaythrough', 'cancel', 'change', 'click', 'close', 'compositionend', 'compositionstart', 'compositionupdate', 'contextmenu', 'copy', 'cut', 'dblclick', 'auxclick', 'drag', 'dragend', 'dragenter', 'dragexit', 'dragleave', 'dragover', 'dragstart', 'drop', 'durationchange', 'emptied', 'encrypted', 'ended', 'error', 'focus', 'focusin', 'focusout', 'fullscreenchange', 'gotpointercapture', 'hashchange', 'input', 'invalid', 'keydown', 'keypress', 'keyup', 'load', 'loadstart', 'loadeddata', 'loadedmetadata', 'lostpointercapture', 'message', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'paste', 'pause', 'play', 'playing', 'pointercancel', 'pointerdown', 'pointerenter', 'pointerleave', 'pointermove', 'pointerout', 'pointerover', 'pointerup', 'popstate', 'progress', 'ratechange', 'reset', 'resize', 'scroll', 'scrollend', 'seeked', 'seeking', 'select', 'selectstart', 'selectionchange', 'stalled', 'submit', 'suspend', 'timeupdate', 'toggle', 'touchcancel', 'touchend', 'touchmove', 'touchstart', 'volumechange', 'waiting', 'wheel'];\n // List of events that need to be individually attached to media elements.\n static mediaEventTypes = ['abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'encrypted', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 'resize', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting'];\n\n // We should not delegate these events to the container, but rather\n // set them on the actual target element itself. This is primarily\n // because these events do not consistently bubble in the DOM.\n static nonDelegatedEvents = new Set([\n 'beforetoggle',\n 'cancel',\n 'close',\n 'invalid',\n 'load',\n 'scroll',\n 'scrollend',\n 'toggle',\n // In order to reduce bytes, we insert the above array of media events\n // into this Set. Note: the \"error\" event isn't an exclusive media event,\n // and can occur on other elements too. Rather than duplicate that event,\n // we just take it from the media events array.\n ...this.mediaEventTypes,\n ]);\n static cxEventAttributes = new Set(Eventing.domEventTypes.map(i => `cx-${i}`));\n static cxEventSelector = Eventing.cxEventAttributes.values().map(i => `:scope > :not([cx-controller]) [${i}], :scope > [${i}]`).toArray().join(\",\");\n static controllerHandles = new Map();\n static eventHandles = new Map();\n\n static attachHandler(ctrl, el, name, handler) {\n if (this.controllerHandles.has(ctrl)) {\n this.controllerHandles.get(ctrl).push(handler);\n } else {\n this.controllerHandles.set(ctrl, [handler]);\n }\n this.registerHandler(el, name, handler);\n }\n\n static registerHandler(el, name, handler) {\n if (this.eventHandles.has(name)) {\n if (this.eventHandles.get(name).has(el)) {\n this.eventHandles.get(name).get(el).push(handler);\n } else {\n this.eventHandles.get(name).set(el, [handler]);\n }\n } else {\n this.eventHandles.set(name, new Map([[el, [handler]]]));\n }\n }\n\n static installEventHandlers() {\n if (document[globalEventsListener]) {\n return;\n }\n document[globalEventsListener] = true;\n\n for (const ev of CompEx.Eventing.domEventTypes) {\n const isCapturePhaseListener = CompEx.Eventing.nonDelegatedEvents.has(ev);\n this.installNativeEventHandler(ev, isCapturePhaseListener);\n }\n }\n\n static installNativeEventHandler(ev, isCapturePhaseListener) {\n // Ensure we don\u2019t add duplicate listeners\n const listenerSetKey = this.eventListenerKey(ev, isCapturePhaseListener);\n if (this.eventListenerSet.has(listenerSetKey)) return;\n\n document.addEventListener(ev, this.eventHandler(isCapturePhaseListener), isCapturePhaseListener);\n this.eventListenerSet.add(listenerSetKey);\n }\n\n static eventHandler(isCapturePhaseListener) {\n return isCapturePhaseListener ? this.captureEventHandler.bind(this) : this.bubbleEventHandler.bind(this);\n }\n\n static captureEventHandler(ev) {\n debugger;\n }\n\n static bubbleEventHandler(ev) {\n const handles = this.eventHandles.get(ev.type);\n if (!handles) return;\n const elHandlers = handles.get(ev.target);\n if (!elHandlers) return;\n for (const handler of elHandlers) {\n handler(ev);\n if (ev.defaultPrevented) return;\n }\n }\n\n static dispatchSyntheticEvent(type, target, detail = null) {\n const ev = new SyntheticEvent(type, target, detail);\n const handles = this.eventHandles.get(ev.type);\n if (!handles) return;\n\n for (const handler of handles.values().toArray().flat()) {\n handler(ev);\n if (ev.defaultPrevented) return;\n }\n }\n\n static eventListenerKey(name, isCapturePhaseListener) {\n return `${name}__${isCapturePhaseListener ? 'capture' : 'bubble'}`;\n }\n}\n\nexport default Eventing;\n", "class Runtime {\n static registry = {};\n static instances = new WeakMap();\n static observer = new MutationObserver(muts => {\n for (const mut of muts) {\n for (const node of mut.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE || !node.getAttribute(\"cx-controller\")) continue;\n this.attachController(node);\n }\n\n for (const node of mut.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE || !node.getAttribute(\"cx-controller\")) continue;\n this.detachController(node);\n }\n }\n });\n static loadedHashesSet = new Set();\n\n static attachController(node) {\n const ctrlName = node.getAttribute('cx-controller');\n const ctrl = this.registry[ctrlName];\n if (!ctrl) {\n console.warn(`Missing JS controller for element ${node}: ${ctrlName}`);\n return;\n }\n const inst = new ctrl(node);\n this.instances.set(node, inst);\n let mountRet = inst.mount();\n if (mountRet instanceof Promise) {\n mountRet.catch(console.error.bind(console));\n }\n }\n\n static detachController(node) {\n const inst = this.instances[node];\n if (!inst) return;\n\n inst.unmount()\n .catch(console.error.bind(console))\n .finally(() => {\n this.instances.delete(node);\n });\n }\n\n static install() {\n this.installed = true;\n CompEx.Eventing.installEventHandlers();\n for (const el of document.querySelectorAll('[cx-controller]')) this.attachController(el);\n\n for (const el of document.querySelectorAll('[cx-asset]')) this.loadedHashesSet.add(el.getAttribute('cx-asset'));\n\n this.observer.observe(document, {childList: true});\n }\n\n static register(name, klass) {\n this.registry[name] = klass;\n console.log(`CompEx: Registered ${name}`);\n }\n\n static async handleRequiredHashesHeader(value) {\n if (!value) return;\n const toRequire = value\n .split(\",\")\n .filter(v => !this.loadedHashesSet.has(v));\n const requireParams = new URLSearchParams([[\"h\", toRequire]]);\n const resp = await fetch(\"/_compex/assets?\" + requireParams.toString(), {\n method: \"GET\",\n });\n if (!resp.ok) {\n console.error(resp.message);\n return;\n }\n\n // /_assets returns a one-dimensional array containing data for each of the required\n // hashes.\n const json = await resp.json();\n for (const [idx, item] of json.entries()) {\n if (!item) {\n console.warn(`CompEx: Could not retrieve asset for hash ${toRequire[idx]}`)\n continue;\n }\n\n switch (item.type) {\n case \"style\": {\n const st = document.createElement(\"style\");\n st.setAttribute(\"cx-asset\", item.hash);\n st.textContent = item.source;\n document.head.append(st);\n this.loadedHashesSet.add(item.hash);\n break;\n }\n case \"script\": {\n const sc = document.createElement(\"script\");\n sc.setAttribute(\"cx-asset\", item.hash);\n sc.innerHTML = item.source;\n document.head.append(sc);\n this.loadedHashesSet.add(item.hash);\n break;\n }\n default: {\n console.error(`Unknown asset of type ${item.type}`);\n }\n }\n }\n }\n}\n\nexport default Runtime;\n", "class MultiBind {\n constructor(els) {\n this.els = els;\n this.isSingle = els[0].type.toLowerCase() === 'radio';\n }\n\n get values() {\n if (this.isSingle) {\n const sel = this.els.find(el => el.checked);\n return sel ? sel.value : undefined;\n }\n\n return this.els\n .filter(el => el.checked)\n .map(el => el.value);\n }\n\n set values(values) {\n if (this.isSingle) {\n if (Array.isArray(values)) {\n console.warn(\"CompEx: Setting an array to a radio group binding has no effect.\");\n return;\n }\n const toSel = this.els.find(el => el.value === values);\n if (toSel) toSel.checked = true;\n return;\n }\n\n if (!Array.isArray(values)) {\n values = [values];\n }\n const vals = new Set(values);\n for (const el of this.els) {\n el.checked = vals.has(el.value);\n }\n }\n}\n\nexport default MultiBind;\n", "class Component {\n constructor(root) {\n this.props = {};\n this.root = root;\n this.bindings = new Map();\n this.unsafeAttachRefs();\n this.unsafeAttachProps();\n this.unsafeAttachBinds();\n this.unsafeAttachEvents();\n }\n\n unsafeAttachRefs() {\n const els = this.root.querySelectorAll(':scope > :not([cx-controller]) [cx-ref], :scope > [cx-ref]');\n for (const el of els) {\n this[el.getAttribute('cx-ref')] = el;\n }\n if (this.root.getAttribute('cx-ref')) {\n this[this.root.getAttribute('cx-ref')] = this.root;\n }\n }\n\n unsafeAttachProps() {\n const bag = this.root.querySelector(\"script[type='vendor/compex-data-bag']\")\n if (!bag) return;\n\n try {\n this.props = JSON.parse(bag.textContent);\n } catch (ex) {\n console.error(\"CompEx: Failed parsing data bag\", ex);\n }\n }\n\n unsafeAttachEvents() {\n for (const el of this.root.querySelectorAll(CompEx.Eventing.cxEventSelector)) {\n this.unsafeAttachEventsForNode(el);\n }\n for (const el of this.root.querySelectorAll(':scope > :not([cx-controller]) [cx-custom-handlers], :scope > [cx-custom-handlers]')) {\n this.unsafeAttachCustomEventsForNode(el)\n }\n }\n\n unsafeAttachCustomEventsForNode(el) {\n const descriptor = el.getAttribute(\"cx-custom-handlers\");\n if (!descriptor) return;\n\n const descriptors = descriptor.split(\",\").map(i => i.split(\"->\"));\n for (const [name, handler] of descriptors) {\n this.unsafeAttachSingleEvent(this.root, name, handler)\n }\n }\n\n unsafeAttachEventsForNode(el) {\n for (const attr of el.getAttributeNames()) {\n if (!CompEx.Eventing.cxEventAttributes.has(attr)) return;\n this.unsafeAttachSingleEvent(el, attr.replace(\"cx-\", \"\"), el.getAttribute(attr))\n }\n }\n\n unsafeAttachSingleEvent(root, name, handlerName) {\n const handler = this[handlerName];\n if (!handler) {\n console.warn(`Ignored attaching event: Handler ${handlerName} is not defined on`, root);\n return;\n }\n if (typeof handler !== 'function') {\n console.warn(`Ignored attaching event: Handler ${handlerName} is not a function on`, root);\n return;\n }\n\n CompEx.Eventing.attachHandler(this, root, name, handler.bind(this));\n }\n\n unsafeAttachBinds() {\n // First, collect all bindings, so we can decide what to do when we have multiple options.\n for (const el of this.root.querySelectorAll(':scope > :not([cx-controller]) [cx-bind], :scope > [cx-bind]')) {\n const name = el.getAttribute('cx-bind');\n if (this.bindings.has(name)) {\n this.bindings.get(name).push(el);\n } else {\n this.bindings.set(name, [el]);\n }\n }\n\n // TODO: Support namespaced bindings like login.username, login.password.\n\n // Then, process every set of bindings. For instance, bindings with multiple elements like\n // radios and checkboxes can be handled through their own values instead of a reference to\n // the element itself. This also changes how we set values.\n\n // TODO: Warn on conflicting bindings\n\n for (const [k, v] of this.bindings.entries()) {\n if (v.length > 1) {\n this.unsafeAttachMultiInputBind(k, v);\n } else if (v[0].tagName.toLowerCase() === 'select') {\n this.unsafeAttachSelectBind(k, v[0]);\n } else {\n this.unsafeAttachSingleInputBind(k, v[0]);\n }\n }\n }\n\n unsafeAttachSelectBind(k, el) {\n const opts = [...el.querySelectorAll('option')];\n const isMultiple = el.hasAttribute('multiple');\n\n console.log(\"unsafeAttachSelectBind\", k, el, isMultiple);\n\n const set = (val) => {\n if (!isMultiple && Array.isArray(val)) {\n console.warn(`CompEx: Setting an array to a non-multiple Select (cx-bind=\"${k}\") has no effect.`);\n } else if (isMultiple) {\n val = Array.isArray(val) ? val : [val];\n const selOpts = new Set(val);\n for (const el of opts) {\n el.selected = selOpts.has(el.value)\n }\n } else {\n for (const el of opts) {\n el.selected = el.value === val;\n }\n }\n }\n\n const get = () => {\n const selected = opts.filter(el => el.selected);\n if (!isMultiple) return selected.length === 1 ? selected[0].value : undefined;\n return selected.map(el => el.value);\n }\n\n this.unsafeCreateLocalProperty(k, get, set);\n }\n\n unsafeAttachMultiInputBind(k, els) {\n // Collect all element types, all input types, all values.\n const nodeTags = els.map(el => el.tagName.toLowerCase());\n const isInput = nodeTags.includes(\"input\");\n if (isInput) {\n // If one tag is an input, every tag must be an input.\n console.log(nodeTags);\n if (!nodeTags.every(tag => tag === \"input\")) {\n throw new Error(`cx-bind=\"${k}\": All bindings under the same name are required to be inputs.`);\n }\n } else {\n // TODO: Expect all nodes to be the same type?\n }\n\n if (isInput) {\n const inputTypes = els.map(el => el.type.toLowerCase());\n // Here's some checks:\n // 1. All inputs must be of the same type.\n // 2. All inputs must have a value.\n // 3. Only radio and checkboxes can be part of a group of binds.\n if (inputTypes.some(t => t !== \"checkbox\" && t !== \"radio\")) {\n throw new Error(`cx-bind=\"${k}\": Only checkbox and radio inputs are supported in multi-bind input elements.`);\n }\n const firstType = inputTypes[0];\n if (inputTypes.some(t => t !== firstType)) {\n throw new Error(`cx-bind=\"${k}\": Inputs must have the same type \"${firstType}\"`);\n }\n\n if (els.some(el => !el.getAttribute(\"value\"))) {\n throw new Error(`cx-bind=\"${k}\": All inputs in a group must have a value`);\n }\n\n if (inputTypes[0] === \"radio\" && els.some(el => !el.name)) {\n // All radios must have the same name. Either synthesize or reuse one.\n let name;\n // Make sure we don't have a name for the group. In case at least one element\n // has a name, reuse it.\n const nameEl = els.find(el => Boolean(el.name));\n name = nameEl ? nameEl.name : `compex-radio-${CompEx.Utils.randomID()}`;\n for (const el of els) el.name = name\n }\n\n const mb = new CompEx.MultiBind(els);\n this.unsafeCreateLocalProperty(k, () => mb.values, (v) => mb.values = v);\n }\n }\n\n unsafeAttachSingleInputBind(name, el) {\n const getter = () => CompEx.Utils.getValueForNode(el);\n const setter = (val) => CompEx.Utils.setValueForNode(el, val);\n this.unsafeCreateLocalProperty(name, getter, setter);\n }\n\n unsafeCreateLocalProperty(name, getter, setter) {\n Object.defineProperty(this, name, {\n get() {\n return getter()\n },\n set(newValue) {\n setter(newValue)\n },\n enumerable: true,\n configurable: true,\n });\n console.log(`CompEx: Registered prop ${name}`);\n }\n\n async mount() {}\n\n async unmount() {}\n\n emit(name, detail) {\n detail = detail || {};\n detail.controller = this;\n CompEx.Eventing.dispatchSyntheticEvent(name, this.root, detail);\n }\n\n async loadComponent(path) {\n const result = await fetch(path, {\n method: 'GET',\n mode: 'cors',\n credentials: 'same-origin',\n headers: {\n 'Accept': 'text/html',\n 'X-CompEx-Load': 'true',\n }\n });\n if (result.ok) {\n const loadProm = CompEx.Runtime.handleRequiredHashesHeader(result.headers.get('X-CompEx-RequiredHashes'));\n const div = document.createElement('div');\n div.innerHTML = await result.text();\n await loadProm;\n return div.firstChild;\n } else {\n throw result.error;\n }\n }\n}\n\nexport default Component;\n", "((w, d) => {\n\n const Utils = require(\"./utils.js\").default;\n const Eventing = require(\"./eventing.js\").default;\n\n const Runtime = require(\"./runtime.js\").default;\n const MultiBind = require(\"./multibind.js\").default;\n const Component = require(\"./component.js\").default;\n\n w.CompEx = class CompEx {\n static Runtime = Runtime;\n static Component = Component;\n static Utils = Utils;\n static Eventing = Eventing;\n static MultiBind = MultiBind;\n }\n\n if (d.readyState === 'loading') {\n addEventListener(\"DOMContentLoaded\", () => CompEx.Runtime.install());\n } else {\n CompEx.Runtime.install();\n }\n})(globalThis, document);\n"],
5
+ "mappings": "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",
6
+ "names": ["el", "Utils", "Eventing", "Runtime", "MultiBind", "Component"]
7
+ }
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: compex-sinatra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vito Sartori
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-10-23 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: CompEx bindings for Sinatra
13
+ email:
14
+ - hey@vito.io
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".rspec"
20
+ - ".rubocop.yml"
21
+ - LICENSE.txt
22
+ - README.md
23
+ - Rakefile
24
+ - lib/compex/sinatra.rb
25
+ - lib/compex/sinatra/version.rb
26
+ - sample/app.rb
27
+ - sample/public/bundle.js
28
+ - sample/public/bundle.js.map
29
+ homepage: https://github.com/heyvito/compex
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ allowed_push_host: https://rubygems.org
34
+ homepage_uri: https://github.com/heyvito/compex
35
+ source_code_uri: https://github.com/heyvito/compex/tree/master/compex-sinatra
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 3.1.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.6.2
51
+ specification_version: 4
52
+ summary: CompEx bindings for Sinatra
53
+ test_files: []