Skip to content

Commit 04e94fa

Browse files
authored
Merge branch 'main' into rails-8
2 parents bd0cfe3 + 8f4c88a commit 04e94fa

File tree

28 files changed

+829
-737
lines changed

28 files changed

+829
-737
lines changed

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
33

44
gemspec
55

6+
# Assets for the dummy app (can also be sprockets, but propshaft is the new default)
7+
gem "propshaft"
8+
69
group :development do
710
gem "letter_opener"
811
end

Gemfile.lock

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ PATH
1616
pg
1717
rack-rewrite (>= 1.5.0)
1818
rails (>= 6.0, < 9.0)
19-
sprockets-rails
2019
stimulus-rails (>= 0.7.0)
2120
tailwindcss-ruby
2221
turbo-rails (>= 0.9, < 3.0)
@@ -228,6 +227,11 @@ GEM
228227
nokogiri (1.16.7-x86_64-linux)
229228
racc (~> 1.4)
230229
pg (1.5.9)
230+
propshaft (1.1.0)
231+
actionpack (>= 7.0.0)
232+
activesupport (>= 7.0.0)
233+
rack
234+
railties (>= 7.0.0)
231235
pry (0.15.0)
232236
coderay (~> 1.1)
233237
method_source (~> 1.0)
@@ -305,13 +309,6 @@ GEM
305309
json (>= 1.8, < 3)
306310
simplecov-html (~> 0.10.0)
307311
simplecov-html (0.10.2)
308-
sprockets (4.2.1)
309-
concurrent-ruby (~> 1.0)
310-
rack (>= 2.2.4, < 4)
311-
sprockets-rails (3.5.2)
312-
actionpack (>= 6.1)
313-
activesupport (>= 6.1)
314-
sprockets (>= 3.0.0)
315312
stimulus-rails (1.3.4)
316313
railties (>= 6.0.0)
317314
stringio (3.1.2)
@@ -364,6 +361,7 @@ DEPENDENCIES
364361
letter_opener
365362
minitest-reporters
366363
mocha
364+
propshaft
367365
pry-rails
368366
puma
369367
rails-controller-testing

app/assets/config/spina/manifest.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//= link_directory ../../javascripts/spina/libraries
66

77
//= link spina/animate.css
8-
//= link spina/fonts.css
8+
//= link spina/fonts-sprockets.css
99
//= link spina/tailwind.css
1010

11-
//= link spina/application.js
11+
//= link spina/application.js
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import "@hotwired/turbo-rails"
22
import "libraries/trix"
3-
import "controllers"
3+
import "controllers"

app/assets/javascripts/spina/controllers/confetti_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ export default class extends Controller {
4040
return document.querySelector("#confetti")
4141
}
4242

43-
}
43+
}
Lines changed: 234 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,234 @@
1-
//= require ../libraries/[email protected]
1+
import { Controller } from '@hotwired/stimulus'
2+
3+
/**
4+
* One way data and visibility bindings for inputs
5+
* @extends Controller
6+
*/
7+
export default class DataBindingController extends Controller {
8+
/**
9+
* Initialize bindings on connection to the DOM
10+
*/
11+
connect() {
12+
if (this.element.dataset.bindingDebug === "true") {
13+
this.debugMode = true
14+
}
15+
16+
this._debug("stimulus-data-binding: connecting to wrapper:", this.element)
17+
18+
const sourceElements = Array.from(this.element.querySelectorAll('[data-binding-target]'))
19+
if (this.element.dataset.bindingTarget) sourceElements.unshift(this.element)
20+
21+
if (sourceElements.length === 0) this._debug("No source elements found. Did you set data-binding-target on your source elements?")
22+
23+
for (const sourceElement of sourceElements) {
24+
if (this.debugMode) console.group("stimulus-data-binding: Source element")
25+
this._debug("Source element found", sourceElement)
26+
27+
if (sourceElement.dataset.bindingInitial !== 'false') {
28+
this._debug("Running initial binding on source element")
29+
this._runBindings(sourceElement)
30+
} else {
31+
this._debug("%cNot running initial binding on source element as binding-initial is set to false", "color: rgba(150,150,150,0.8);")
32+
}
33+
34+
if (this.debugMode) console.groupEnd()
35+
}
36+
}
37+
38+
/**
39+
* Updates bindings for the current element.
40+
* @param {Event} e - an event with a currentTarget DOMElement
41+
*/
42+
update(e) {
43+
this._runBindings(e.currentTarget)
44+
}
45+
46+
/**
47+
* @private
48+
* @param {DOMElement} source
49+
*/
50+
_runBindings(source) {
51+
this._debug("Searching for targets for source: ", source)
52+
for (const targetRef of source.dataset.bindingTarget.split(' ')) {
53+
const targetElements = this._bindingElements(targetRef)
54+
55+
if (targetElements.length === 0) this._debug(`Could not find any target elements for ref ${targetRef}. Have you set data-target-ref="${targetRef}" on your target elements?`)
56+
57+
for (const target of targetElements) {
58+
if (this.debugMode) console.group("stimulus-data-binding: Target Element")
59+
this._debug("Target found. Running bindings for target: ", target)
60+
61+
const bindingCondition = this._getDatum('bindingCondition', source, target)
62+
63+
if (bindingCondition) {
64+
this._debug(`Evaluating binding condition: '${bindingCondition}'`)
65+
} else {
66+
this._debug(`%cNo binding condition set. Evaluating as true. To add a condition set 'data-binding-condition="..."'`, "color: rgba(150,150,150,0.8);")
67+
}
68+
69+
const conditionPassed = this._evaluate(
70+
bindingCondition,
71+
{
72+
source,
73+
target
74+
}
75+
)
76+
77+
if (conditionPassed) {
78+
this._debug(`Condition evaluated to: `, conditionPassed)
79+
} else {
80+
this._debug(`Condition evaluated to: `, conditionPassed)
81+
}
82+
83+
const bindingValue = this._getDatum('bindingValue', source, target)
84+
85+
if (bindingValue) {
86+
this._debug(`Evaluating binding value: '${bindingValue}'`)
87+
} else {
88+
this._debug(`%cNo binding value set, evaluating as true. to set a value for the attribute / property on your target elements set 'data-binding-value="..."'`, "color: rgba(150,150,150,0.8);")
89+
}
90+
91+
const value = this._evaluate(
92+
bindingValue,
93+
{
94+
source,
95+
target
96+
}
97+
)
98+
99+
this._debug(`Value evaluated to: '${value}'`)
100+
101+
const bindingAttribute = this._getDatum(
102+
'bindingAttribute',
103+
source,
104+
target
105+
)
106+
107+
if (!bindingAttribute) {
108+
this._debug(`%cNo binding attribute set. To add attributes to your target element set 'data-binding-attribute="..."'`, "color: rgba(150,150,150,0.8);")
109+
}
110+
111+
if (bindingAttribute) {
112+
for (const attribute of bindingAttribute.split(' ')) {
113+
if (conditionPassed) {
114+
this._debug(`Condition passed so setting attribute '${attribute}' to '${value}'`)
115+
target.setAttribute(attribute, value)
116+
} else {
117+
this._debug(`Condition failed so removing attribute '${attribute}'`)
118+
target.removeAttribute(attribute)
119+
}
120+
}
121+
}
122+
123+
const bindingProperty = this._getDatum(
124+
'bindingProperty',
125+
source,
126+
target
127+
)
128+
129+
if (!bindingProperty) {
130+
this._debug(`%cNo binding property set. To add properties to your target element set 'data-binding-property="..."'`, "color: rgba(150,150,150,0.8);")
131+
}
132+
133+
if (bindingProperty) {
134+
for (const prop of bindingProperty.split(' ')) {
135+
const propertyValue = conditionPassed ? value : ''
136+
if (target[prop] != propertyValue) { target.dataset.hasChanged = true }
137+
138+
if (conditionPassed) {
139+
this._debug(`Condition passed so setting property '${prop}' from ${target[prop]} to '${value}'`)
140+
} else {
141+
this._debug(`Condition failed so setting property '${prop}' from ${target[prop]} to '' (empty string)`)
142+
}
143+
144+
target[prop] = propertyValue
145+
}
146+
}
147+
148+
const bindingClass = this._getDatum(
149+
'bindingClass',
150+
source,
151+
target
152+
)
153+
154+
if (!bindingClass) {
155+
this._debug(`%cNo binding class set. To add classes to your target element set 'data-binding-class="..."'`, "color: rgba(150,150,150,0.8);")
156+
}
157+
158+
if (bindingClass) {
159+
for (const klass of bindingClass.split(' ')) {
160+
if (conditionPassed) {
161+
this._debug(`Condition passed so adding class '${klass}'`)
162+
target.classList.add(klass)
163+
} else {
164+
this._debug(`Condition failed so removing class '${klass}'`)
165+
target.classList.remove(klass)
166+
}
167+
}
168+
}
169+
170+
const bindingEvent = this._getDatum('bindingEvent', source, target)
171+
172+
if (!bindingEvent) {
173+
this._debug(`%cNo binding event set. To dispatch events on property change to your target element set 'data-binding-event="..."'`, "color: rgba(150,150,150,0.8);")
174+
}
175+
176+
if (bindingEvent) {
177+
for (const event of bindingEvent.split(' ')) {
178+
if (target.dataset.hasChanged) {
179+
this._debug(`Target has changed so dispatching event ${event}`)
180+
target.dispatchEvent(new Event(event, { cancelable: true, bubbles: true }))
181+
delete target.dataset.hasChanged
182+
} else {
183+
this._debug(`No changes to target properties so not dispatching '${event}'`)
184+
}
185+
}
186+
}
187+
188+
if (this.debugMode) console.groupEnd()
189+
}
190+
}
191+
}
192+
193+
/**
194+
* @private
195+
* @param {String} name - the name of the binding reference
196+
*/
197+
_bindingElements(name) {
198+
return this.element.querySelectorAll(`[data-binding-ref="${name}"]`)
199+
}
200+
201+
/**
202+
* @private
203+
* @param {String} attribute - the attribute to fetch from the source / target dataaset
204+
* @param {String} source - The source element to get the attribute from, only loads if target doesnt have it
205+
* @param {String} target - The target element to get the attribute from, has precedence over the source
206+
*/
207+
_getDatum(attribute, source, target) {
208+
return target.dataset[attribute] || source.dataset[attribute]
209+
}
210+
211+
/**
212+
* @private
213+
* @param {String} expression - expression to safe eval
214+
* @param {Object} variables - variables to be present when evaluating the given expression
215+
*/
216+
_evaluate(expression, variables = {}) {
217+
if (!expression) return true
218+
return new Function(
219+
Object.keys(variables).map((v) => `$${v}`),
220+
`return ${expression.trim()}`
221+
)(...Object.values(variables))
222+
}
223+
224+
/**
225+
* @private
226+
* @param {String} expression - expression to safe eval
227+
* @param {Object} variables - variables to be present when evaluating the given expression
228+
*/
229+
_debug(...args) {
230+
if (this.debugMode) {
231+
console.log(...args)
232+
}
233+
}
234+
}

app/assets/javascripts/spina/controllers/form_controller.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Controller } from "@hotwired/stimulus"
22
import debounce from "libraries/debounce"
3-
import formRequestSubmitPolyfill from "libraries/form-request-submit-polyfill"
43

54
export default class extends Controller {
65

@@ -20,4 +19,4 @@ export default class extends Controller {
2019
return this.element.dataset.debounceTime || 0
2120
}
2221

23-
}
22+
}

app/assets/javascripts/spina/controllers/hotkeys_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export default class extends Controller {
1818
}
1919
}
2020

21-
}
21+
}

0 commit comments

Comments
 (0)