phlex-stimulus 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 +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +341 -0
- data/Rakefile +12 -0
- data/lib/generators/phlex/stimulus/controller/USAGE +5 -0
- data/lib/generators/phlex/stimulus/controller/controller_generator.rb +49 -0
- data/lib/generators/phlex/stimulus/controller/templates/component.rb.erb +30 -0
- data/lib/generators/phlex/stimulus/controller/templates/controller.ts.erb +23 -0
- data/lib/generators/phlex/stimulus/generator_mixin.rb +21 -0
- data/lib/generators/phlex/stimulus/install/USAGE +5 -0
- data/lib/generators/phlex/stimulus/install/install_generator.rb +111 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/base.rb.erb +29 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/chain_controller.rb.erb +29 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/classlist_controller.rb.erb +30 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/controller.rb.erb +10 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/controller_chain.rb.erb +32 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/controller_template.rb.erb +24 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/log_controller.rb.erb +33 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components/sleep_controller.rb.erb +30 -0
- data/lib/generators/phlex/stimulus/install/templates/app/components.rb.erb +7 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/application.ts.erb +5 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/application.ts.erb +10 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/chain_controller.ts.erb +32 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/chainable_controller.ts.erb +44 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/classlist_controller.ts.erb +30 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/index.ts.erb +4 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/log_controller.ts.erb +25 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/sleep_controller.ts.erb +19 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/typed_controller.ts.erb +53 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/utils/misc.ts.erb +37 -0
- data/lib/generators/phlex/stimulus/install/templates/app/javascript/utils/template.ts.erb +11 -0
- data/lib/phlex/stimulus/components/base.rb +81 -0
- data/lib/phlex/stimulus/components/controller.rb +170 -0
- data/lib/phlex/stimulus/components.rb +9 -0
- data/lib/phlex/stimulus/version.rb +8 -0
- data/lib/phlex/stimulus.rb +15 -0
- data/sorbet/config +7 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/actionmailer.rbi +15 -0
- data/sorbet/rbi/annotations/actionpack.rbi +430 -0
- data/sorbet/rbi/annotations/actionview.rbi +75 -0
- data/sorbet/rbi/annotations/activejob.rbi +50 -0
- data/sorbet/rbi/annotations/activemodel.rbi +119 -0
- data/sorbet/rbi/annotations/activerecord.rbi +172 -0
- data/sorbet/rbi/annotations/activesupport.rbi +561 -0
- data/sorbet/rbi/annotations/globalid.rbi +30 -0
- data/sorbet/rbi/annotations/minitest.rbi +116 -0
- data/sorbet/rbi/annotations/railties.rbi +104 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +21 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/action_text-trix@2.1.19.rbi +9 -0
- data/sorbet/rbi/gems/actioncable@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/actionmailbox@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/actionmailer@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/actionpack@8.1.3.rbi +20478 -0
- data/sorbet/rbi/gems/actiontext@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/actionview@8.1.3.rbi +14722 -0
- data/sorbet/rbi/gems/activejob@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/activemodel@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/activerecord@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/activestorage@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/activesupport@8.1.3.rbi +20153 -0
- data/sorbet/rbi/gems/ast@2.4.3.rbi +550 -0
- data/sorbet/rbi/gems/base64@0.3.0.rbi +545 -0
- data/sorbet/rbi/gems/benchmark@0.5.0.rbi +621 -0
- data/sorbet/rbi/gems/booleans@0.1.3.rbi +31 -0
- data/sorbet/rbi/gems/builder@3.3.0.rbi +9 -0
- data/sorbet/rbi/gems/byebug@12.0.0.rbi +49 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.3.8.rbi +10656 -0
- data/sorbet/rbi/gems/connection_pool@3.0.2.rbi +9 -0
- data/sorbet/rbi/gems/crass@1.0.7.rbi +648 -0
- data/sorbet/rbi/gems/date@3.5.1.rbi +391 -0
- data/sorbet/rbi/gems/drb@2.2.3.rbi +9 -0
- data/sorbet/rbi/gems/erb@6.0.6.rbi +1600 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sorbet/rbi/gems/globalid@1.4.0.rbi +9 -0
- data/sorbet/rbi/gems/i18n@1.15.2.rbi +2285 -0
- data/sorbet/rbi/gems/io-console@0.8.2.rbi +9 -0
- data/sorbet/rbi/gems/json@2.21.1.rbi +2289 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +189 -0
- data/sorbet/rbi/gems/logger@1.7.0.rbi +896 -0
- data/sorbet/rbi/gems/loofah@2.25.2.rbi +1134 -0
- data/sorbet/rbi/gems/mail@2.9.1.rbi +9 -0
- data/sorbet/rbi/gems/marcel@1.2.1.rbi +9 -0
- data/sorbet/rbi/gems/mini_mime@1.1.5.rbi +9 -0
- data/sorbet/rbi/gems/minitest@6.0.6.rbi +1482 -0
- data/sorbet/rbi/gems/net-imap@0.6.4.1.rbi +9 -0
- data/sorbet/rbi/gems/net-pop@0.1.2.rbi +9 -0
- data/sorbet/rbi/gems/net-protocol@0.2.2.rbi +244 -0
- data/sorbet/rbi/gems/net-smtp@0.5.1.rbi +9 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +147 -0
- data/sorbet/rbi/gems/nio4r@2.7.5.rbi +9 -0
- data/sorbet/rbi/gems/nokogiri@1.19.4.rbi +8168 -0
- data/sorbet/rbi/gems/parallel@1.28.0.rbi +270 -0
- data/sorbet/rbi/gems/phlex-rails@2.4.0.rbi +1945 -0
- data/sorbet/rbi/gems/phlex@2.4.1.rbi +1413 -0
- data/sorbet/rbi/gems/pp@0.6.4.rbi +387 -0
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +455 -0
- data/sorbet/rbi/gems/prism@1.9.0.rbi +42226 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +165 -0
- data/sorbet/rbi/gems/rack-session@2.1.2.rbi +756 -0
- data/sorbet/rbi/gems/rack-test@2.2.0.rbi +681 -0
- data/sorbet/rbi/gems/rack@3.2.6.rbi +4653 -0
- data/sorbet/rbi/gems/rackup@2.3.1.rbi +9 -0
- data/sorbet/rbi/gems/rails-dom-testing@2.3.0.rbi +517 -0
- data/sorbet/rbi/gems/rails-html-sanitizer@1.7.1.rbi +620 -0
- data/sorbet/rbi/gems/rails@8.1.3.rbi +9 -0
- data/sorbet/rbi/gems/railties@8.1.3.rbi +3507 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +362 -0
- data/sorbet/rbi/gems/rake@13.4.2.rbi +3130 -0
- data/sorbet/rbi/gems/rbi@0.4.0.rbi +5706 -0
- data/sorbet/rbi/gems/rbs@4.0.3.rbi +6908 -0
- data/sorbet/rbi/gems/refract@1.1.0.rbi +9 -0
- data/sorbet/rbi/gems/reline@0.6.3.rbi +2446 -0
- data/sorbet/rbi/gems/require-hooks@0.4.0.rbi +152 -0
- data/sorbet/rbi/gems/rexml@3.4.4.rbi +4905 -0
- data/sorbet/rbi/gems/rubocop-espago@1.3.0.rbi +9 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +988 -0
- data/sorbet/rbi/gems/rubydex@0.2.9.rbi +841 -0
- data/sorbet/rbi/gems/securerandom@0.4.1.rbi +75 -0
- data/sorbet/rbi/gems/shoulda-context@2.0.0.rbi +544 -0
- data/sorbet/rbi/gems/spoom@1.8.4.rbi +6738 -0
- data/sorbet/rbi/gems/tapioca@0.19.2.rbi +3599 -0
- data/sorbet/rbi/gems/thor@1.5.0.rbi +3870 -0
- data/sorbet/rbi/gems/timeout@0.6.1.rbi +200 -0
- data/sorbet/rbi/gems/tsort@0.2.0.rbi +389 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5474 -0
- data/sorbet/rbi/gems/unicode-display_width@3.2.0.rbi +130 -0
- data/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +332 -0
- data/sorbet/rbi/gems/uri@1.1.1.rbi +2402 -0
- data/sorbet/rbi/gems/useragent@0.16.11.rbi +9 -0
- data/sorbet/rbi/gems/websocket-driver@0.8.2.rbi +9 -0
- data/sorbet/rbi/gems/websocket-extensions@0.1.5.rbi +9 -0
- data/sorbet/rbi/gems/zeitwerk@2.8.2.rbi +9 -0
- data/sorbet/rbi/shims/gems/parser.rbi +12 -0
- data/sorbet/rbi/shims/gems/rails.rbi +55 -0
- data/sorbet/rbi/shims/gems/refract.rbi +13 -0
- data/sorbet/rbi/shims/gems/shoulda-context.rbi +16 -0
- data/sorbet/tapioca/compilers/phlex_controller.rb +80 -0
- data/sorbet/tapioca/config.yml +26 -0
- data/sorbet/tapioca/extensions/load_gem.rb +1 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +266 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Components
|
|
5
|
+
# Takes the names of controllers that will get connected
|
|
6
|
+
# in a particular order.
|
|
7
|
+
class ChainController < Controller
|
|
8
|
+
self.controller_name = 'chain'
|
|
9
|
+
|
|
10
|
+
#: (chain: String) -> void
|
|
11
|
+
def initialize(chain:)
|
|
12
|
+
@chain = chain
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @override
|
|
16
|
+
#: ?{ -> void } -> void
|
|
17
|
+
def view_template(&block)
|
|
18
|
+
div(
|
|
19
|
+
data: {
|
|
20
|
+
controller: self.class.controller_name,
|
|
21
|
+
controller_chain: @chain,
|
|
22
|
+
},
|
|
23
|
+
) do
|
|
24
|
+
block&.call
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/generators/phlex/stimulus/install/templates/app/components/classlist_controller.rb.erb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Components
|
|
5
|
+
# Adds or removes classes in HTML elements.
|
|
6
|
+
class ClasslistController < Controller
|
|
7
|
+
self.controller_name = 'classlist'
|
|
8
|
+
|
|
9
|
+
actions :add, :remove
|
|
10
|
+
targets :subject
|
|
11
|
+
|
|
12
|
+
#: (classlist: Array[String?]) -> void
|
|
13
|
+
def initialize(classlist:)
|
|
14
|
+
@classlist = classlist
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @override
|
|
18
|
+
#: ?{ -> void } -> void
|
|
19
|
+
def view_template(&block)
|
|
20
|
+
div(
|
|
21
|
+
data: {
|
|
22
|
+
controller: self.class.controller_name,
|
|
23
|
+
class_list: class_list!(@classlist),
|
|
24
|
+
},
|
|
25
|
+
) do
|
|
26
|
+
block&.call
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Abstract class for all Phlex components
|
|
5
|
+
# that wrap Stimulus.js controllers in this app.
|
|
6
|
+
#
|
|
7
|
+
# @abstract
|
|
8
|
+
class Components::Controller < Phlex::Stimulus::Components::Controller
|
|
9
|
+
# define any custom helpers for controller components here
|
|
10
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Components
|
|
5
|
+
# Takes instances of stimulus controller components
|
|
6
|
+
# and renders them in such a way that they
|
|
7
|
+
# will get attached in the given order.
|
|
8
|
+
#
|
|
9
|
+
# It will wait until the `connect` logic of one controller
|
|
10
|
+
# finishes before it attempts to connect another controller.
|
|
11
|
+
class ControllerChain < Base
|
|
12
|
+
#: (*Controller?) -> void
|
|
13
|
+
def initialize(*controllers)
|
|
14
|
+
@controllers = controllers.compact #: Array[Controller]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#: -> void
|
|
18
|
+
def view_template
|
|
19
|
+
# optimisation when only one controller is present
|
|
20
|
+
return render @controllers.first if @controllers.length == 1
|
|
21
|
+
|
|
22
|
+
render ChainController.new(
|
|
23
|
+
chain: strlist!(@controllers.map(&:controller_name)),
|
|
24
|
+
) do
|
|
25
|
+
@controllers.each.with_index do |controller, i|
|
|
26
|
+
render ControllerTemplate.new(controller: controller, index: i)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/generators/phlex/stimulus/install/templates/app/components/controller_template.rb.erb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Components
|
|
5
|
+
# Renders a template with a deferred Stimulus controller inside.
|
|
6
|
+
#
|
|
7
|
+
# Useful with `ChainController` to create a chain of controllers
|
|
8
|
+
# that will get attached in a particular order one after another.
|
|
9
|
+
class ControllerTemplate < Base
|
|
10
|
+
#: (controller: Controller, index: Integer) -> void
|
|
11
|
+
def initialize(controller:, index:)
|
|
12
|
+
@controller = controller
|
|
13
|
+
@index = index
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: -> void
|
|
17
|
+
def view_template
|
|
18
|
+
template(class: "#{@controller.controller_name}-template-#{@index}") do
|
|
19
|
+
render @controller
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Components
|
|
5
|
+
# Performs a `console.log` after connecting.
|
|
6
|
+
# Mainly used in development and for debugging.
|
|
7
|
+
class LogController < Controller
|
|
8
|
+
self.controller_name = 'log'
|
|
9
|
+
|
|
10
|
+
actions :log
|
|
11
|
+
|
|
12
|
+
#: (message: String, ?on_event: bool) -> void
|
|
13
|
+
def initialize(message:, on_event: false)
|
|
14
|
+
@message = message
|
|
15
|
+
@on_event = on_event
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @override
|
|
19
|
+
#: ?{ -> void } -> void
|
|
20
|
+
def view_template(&block)
|
|
21
|
+
div(
|
|
22
|
+
data: {
|
|
23
|
+
controller: self.class.controller_name,
|
|
24
|
+
message: @message,
|
|
25
|
+
on_event: attrbool(@on_event),
|
|
26
|
+
},
|
|
27
|
+
) do
|
|
28
|
+
block&.call
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Components
|
|
5
|
+
# Sleeps for the given amount of milliseconds.
|
|
6
|
+
# Useful in controller sequences to delay the execution
|
|
7
|
+
# of `connect` logic of some other controller.
|
|
8
|
+
class SleepController < Controller
|
|
9
|
+
self.controller_name = 'sleep'
|
|
10
|
+
|
|
11
|
+
#: (timeout: Integer) -> void
|
|
12
|
+
def initialize(timeout:)
|
|
13
|
+
@timeout = timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @override
|
|
17
|
+
#: ?{ -> void } -> void
|
|
18
|
+
def view_template(&block)
|
|
19
|
+
div(
|
|
20
|
+
data: {
|
|
21
|
+
controller: self.class.controller_name,
|
|
22
|
+
timeout: @timeout.to_s,
|
|
23
|
+
},
|
|
24
|
+
) do
|
|
25
|
+
block&.call
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/generators/phlex/stimulus/install/templates/app/javascript/controllers/application.ts.erb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
const application = Application.start()
|
|
4
|
+
|
|
5
|
+
// Configure Stimulus development experience
|
|
6
|
+
application.debug = false;
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
(window as any).Stimulus = application
|
|
9
|
+
|
|
10
|
+
export { application }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { addEventPromise } from "../utils/misc"
|
|
2
|
+
import { application } from "./application"
|
|
3
|
+
import { Controller } from "@hotwired/stimulus"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Takes the names of controllers that will be connected
|
|
7
|
+
* in a particular order.
|
|
8
|
+
* It waits for the logic of `connect` to finish before
|
|
9
|
+
* connecting another controller.
|
|
10
|
+
**/
|
|
11
|
+
class ChainController extends Controller<HTMLElement> {
|
|
12
|
+
controllerChain!: string
|
|
13
|
+
|
|
14
|
+
async connect() {
|
|
15
|
+
let dataset = this.element.dataset
|
|
16
|
+
this.controllerChain = dataset.controllerChain!
|
|
17
|
+
|
|
18
|
+
let i = 0
|
|
19
|
+
for (let controllerName of this.controllerChain.split(' ')) {
|
|
20
|
+
let controllerTemplate = this.element.querySelector(`template.${controllerName}-template-${i}`) as HTMLTemplateElement
|
|
21
|
+
let controllerElement = document.importNode(controllerTemplate.content, true)
|
|
22
|
+
let readyPromise = addEventPromise(this.element, `${controllerName}:ready`)
|
|
23
|
+
|
|
24
|
+
this.element.appendChild(controllerElement)
|
|
25
|
+
await readyPromise
|
|
26
|
+
|
|
27
|
+
i++
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
application.register("chain", ChainController)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { MakeControllerWithTargetsFactory } from "./typed_controller"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An abstract class for Stimulus controllers that can be chained
|
|
6
|
+
* using the `ChainController`.
|
|
7
|
+
*/
|
|
8
|
+
export abstract class ChainableController<ElementType extends Element> extends Controller<ElementType> {
|
|
9
|
+
async connect() {
|
|
10
|
+
await this.init()
|
|
11
|
+
this.dispatch("ready")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The `connect` logic in a chainable controller
|
|
16
|
+
* should be defined under `init`
|
|
17
|
+
*/
|
|
18
|
+
abstract init(): Promise<void>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An abstract class for Stimulus controllers that can be chained
|
|
23
|
+
* using the `ChainController` and supports fully typed targets.
|
|
24
|
+
*
|
|
25
|
+
* In order to use it specify the `Element` type and
|
|
26
|
+
* a record of target definitions.
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* class MyController extends TypedController<
|
|
30
|
+
* HTMLFormElement,
|
|
31
|
+
* {
|
|
32
|
+
* input: HTMLInputElement,
|
|
33
|
+
* submitButton: HTMLElement,
|
|
34
|
+
* }
|
|
35
|
+
* >() {
|
|
36
|
+
* static targets = [ "input", "submitButton" ]
|
|
37
|
+
*
|
|
38
|
+
* connect() {
|
|
39
|
+
* console.log("Hello!")
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
**/
|
|
44
|
+
export const TypedChainableController = MakeControllerWithTargetsFactory(ChainableController)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { application } from "./application"
|
|
2
|
+
import { TypedController } from "./typed_controller"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adds or removes classes in HTML elements.
|
|
6
|
+
**/
|
|
7
|
+
class ClasslistController extends TypedController<HTMLElement, { subject: HTMLDivElement }>() {
|
|
8
|
+
static targets = [ "subject" ]
|
|
9
|
+
|
|
10
|
+
classList!: string[]
|
|
11
|
+
|
|
12
|
+
connect() {
|
|
13
|
+
let dataset = this.element.dataset
|
|
14
|
+
this.classList = dataset.classList!.split(" ")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
add() {
|
|
18
|
+
for (let subject of this.subjectTargets) {
|
|
19
|
+
subject.classList.add(...this.classList)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
remove() {
|
|
24
|
+
for (let subject of this.subjectTargets) {
|
|
25
|
+
subject.classList.remove(...this.classList)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
application.register("classlist", ClasslistController)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { application } from "./application"
|
|
2
|
+
import { ChainableController } from "./chainable_controller"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Performs a `console.log` after connecting.
|
|
6
|
+
* Mainly used in development and for debugging.
|
|
7
|
+
**/
|
|
8
|
+
class LogController extends ChainableController<HTMLElement> {
|
|
9
|
+
message!: string
|
|
10
|
+
|
|
11
|
+
async init() {
|
|
12
|
+
let dataset = this.element.dataset
|
|
13
|
+
this.message = dataset.message!
|
|
14
|
+
let onEvent = dataset.onEvent
|
|
15
|
+
if (onEvent) return
|
|
16
|
+
|
|
17
|
+
this.log()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
log() {
|
|
21
|
+
console.log(this.message)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
application.register("log", LogController)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sleep } from "../utils/misc"
|
|
2
|
+
import { application } from "./application"
|
|
3
|
+
import { ChainableController } from "./chainable_controller"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sleeps for the given amount of milliseconds.
|
|
7
|
+
* Useful in controller sequences to delay the execution
|
|
8
|
+
* of `connect` logic of some other controller.
|
|
9
|
+
**/
|
|
10
|
+
class SleepController extends ChainableController<HTMLElement> {
|
|
11
|
+
async init() {
|
|
12
|
+
let dataset = this.element.dataset
|
|
13
|
+
let timeout = Number(dataset.timeout)
|
|
14
|
+
|
|
15
|
+
await sleep(timeout)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
application.register("sleep", SleepController)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
3
|
+
import { Controller } from "@hotwired/stimulus"
|
|
4
|
+
|
|
5
|
+
type TargetDefinitions = Record<string, HTMLElement>
|
|
6
|
+
|
|
7
|
+
type StimulusTargetProps<T extends TargetDefinitions> = {
|
|
8
|
+
[K in keyof T as `${K & string}Target`]: T[K]
|
|
9
|
+
} & {
|
|
10
|
+
[K in keyof T as `${K & string}Targets`]: T[K][]
|
|
11
|
+
} & {
|
|
12
|
+
[K in keyof T as `has${Capitalize<K & string>}Target`]: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type AbstractStimulusControllerConstructor = abstract new (...args: any[]) => Controller<any>
|
|
16
|
+
|
|
17
|
+
export function MakeControllerWithTargetsFactory<TBase extends AbstractStimulusControllerConstructor>(Base: TBase) {
|
|
18
|
+
return function <
|
|
19
|
+
TElement extends Element,
|
|
20
|
+
TTargets extends TargetDefinitions = {},
|
|
21
|
+
>() {
|
|
22
|
+
type Props = StimulusTargetProps<TTargets>
|
|
23
|
+
|
|
24
|
+
return Base as unknown as abstract new (
|
|
25
|
+
...args: ConstructorParameters<TBase>
|
|
26
|
+
) => Controller<TElement> & Props
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Main factory function for creating base classes
|
|
32
|
+
* for typed stimulus controllers.
|
|
33
|
+
*
|
|
34
|
+
* In order to use it specify the `Element` type and
|
|
35
|
+
* a record of target definitions.
|
|
36
|
+
*
|
|
37
|
+
* ```
|
|
38
|
+
* class MyController extends TypedController<
|
|
39
|
+
* HTMLFormElement,
|
|
40
|
+
* {
|
|
41
|
+
* input: HTMLInputElement,
|
|
42
|
+
* submitButton: HTMLElement,
|
|
43
|
+
* }
|
|
44
|
+
* >() {
|
|
45
|
+
* static targets = [ "input", "submitButton" ]
|
|
46
|
+
*
|
|
47
|
+
* connect() {
|
|
48
|
+
* console.log("Hello!")
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const TypedController = MakeControllerWithTargetsFactory(Controller)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// throws an error when the value is null or undefined
|
|
2
|
+
// otherwise returns the value
|
|
3
|
+
export function must<T>(value: T): NonNullable<T> {
|
|
4
|
+
if (value === undefined || value === null) {
|
|
5
|
+
throw new Error('value must be defined')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return value
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert a boolean HTML Element attribute to a JS boolean.
|
|
13
|
+
*/
|
|
14
|
+
export function attrbool(value: unknown): boolean {
|
|
15
|
+
return value === 'true'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function sleep(timeout: number | undefined): Promise<void> {
|
|
19
|
+
if (timeout === undefined) return Promise.resolve()
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
resolve()
|
|
24
|
+
}, timeout)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Registers an event listener on the given target
|
|
30
|
+
* and returns a promise that gets resolved when
|
|
31
|
+
* the event was caught by the listener.
|
|
32
|
+
*/
|
|
33
|
+
export function addEventPromise(target: EventTarget, type: string): Promise<Event> {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
target.addEventListener(type, (event) => resolve(event))
|
|
36
|
+
})
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function cloneTemplate(id: string): DocumentFragment {
|
|
2
|
+
let errTemplate = document.querySelector(`template#${id}`) as HTMLTemplateElement
|
|
3
|
+
return document.importNode(errTemplate.content, true)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function cloneTemplateInto(templateId: string, targetContainerId: string): DocumentFragment {
|
|
7
|
+
let container = document.getElementById(targetContainerId)!
|
|
8
|
+
let newNode = cloneTemplate(templateId)
|
|
9
|
+
container.appendChild(newNode)
|
|
10
|
+
return newNode
|
|
11
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Phlex::Stimulus
|
|
5
|
+
module Components
|
|
6
|
+
# Abstract class for Phlex components that render HTML with better sorbet and stimulus support.
|
|
7
|
+
#
|
|
8
|
+
# @abstract
|
|
9
|
+
class Base < Phlex::HTML
|
|
10
|
+
#: -> ApplicationController::HelperProxy
|
|
11
|
+
def h = view_context
|
|
12
|
+
|
|
13
|
+
#: -> String
|
|
14
|
+
def inspect
|
|
15
|
+
"#<#{self.class.name}>"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Constructs a Stimulus event hook string
|
|
19
|
+
# eg.
|
|
20
|
+
#
|
|
21
|
+
# event('click', 'summary#redirect') #=> "click->summary#redirect"
|
|
22
|
+
#
|
|
23
|
+
#: (String, String) -> String
|
|
24
|
+
def event(event_name, full_action_name)
|
|
25
|
+
"#{event_name}->#{full_action_name}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Converts a Ruby boolean to a boolean
|
|
29
|
+
# value in HTML attributes.
|
|
30
|
+
#
|
|
31
|
+
# Returns `"true"` for a truthy value, otherwise `nil`.
|
|
32
|
+
#
|
|
33
|
+
#: (bool) -> String?
|
|
34
|
+
def attrbool(val)
|
|
35
|
+
'true' if val
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Output a single newline character.
|
|
39
|
+
# If a block is given, a space will be output before and after the block.
|
|
40
|
+
#
|
|
41
|
+
#: ?{ -> void } -> void
|
|
42
|
+
def newline(&block)
|
|
43
|
+
plain "\n"
|
|
44
|
+
return unless block
|
|
45
|
+
|
|
46
|
+
block.call
|
|
47
|
+
plain "\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Merges the given list of class names (with optional nils)
|
|
51
|
+
# into a single class string separated by spaces.
|
|
52
|
+
#
|
|
53
|
+
#: (*String?) -> String
|
|
54
|
+
def class_list(*args)
|
|
55
|
+
buff = String.new
|
|
56
|
+
|
|
57
|
+
args.each do |arg|
|
|
58
|
+
next unless arg
|
|
59
|
+
|
|
60
|
+
buff << arg << ' '
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
buff.strip
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
#: (Array[String?]) -> String
|
|
67
|
+
def class_list!(args)
|
|
68
|
+
class_list(*T.unsafe(args))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
alias strlist class_list
|
|
72
|
+
alias strlist! class_list!
|
|
73
|
+
|
|
74
|
+
#: (String) -> bool
|
|
75
|
+
def image_exists?(path)
|
|
76
|
+
Boolean(h.resolve_asset_path(path))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|