opal-vite 0.2.17 → 0.3.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 +4 -4
- data/lib/opal/vite/version.rb +1 -1
- data/opal/opal_vite/concerns/v1/stimulus_helpers.rb +344 -6
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5274b86604ed917278cf1739cb4663c1d93246cd6bf82876876fea56c239c6c2
|
|
4
|
+
data.tar.gz: 72291ac54ac2770b38b7ade5a8907751c9845926efbc54ff479b6ac8623d485e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b134a748ab8322e8889dcd64e687632581ae5da768c5a5b26e0e2e52c032c309e0b39ebd9e0dd2a0645bdaf9ae53e60ed0450985611b220970a9a1ef802d682
|
|
7
|
+
data.tar.gz: a309c63ca72694695797d1d5ad0edef41427664a2fd5e4d827776b42bd4a4e02c940f772f6aab2afb5b857fa1b49f31ee749ddd674893ca7207566035ea7eeb1
|
data/lib/opal/vite/version.rb
CHANGED
|
@@ -450,12 +450,7 @@ module OpalVite
|
|
|
450
450
|
# @param name [String] Event name
|
|
451
451
|
# @yield Block to execute when event fires
|
|
452
452
|
def on_controller_event(name, &block)
|
|
453
|
-
`
|
|
454
|
-
const handler = #{block};
|
|
455
|
-
this.element.addEventListener(#{name}, function(e) {
|
|
456
|
-
handler(e);
|
|
457
|
-
});
|
|
458
|
-
`
|
|
453
|
+
`this.element.addEventListener(#{name}, #{block})`
|
|
459
454
|
end
|
|
460
455
|
|
|
461
456
|
# Get the current event's target element
|
|
@@ -1307,6 +1302,349 @@ module OpalVite
|
|
|
1307
1302
|
`#{promise}.finally(#{block})`
|
|
1308
1303
|
end
|
|
1309
1304
|
|
|
1305
|
+
# ===== Stimulus Values API =====
|
|
1306
|
+
# Access Stimulus values defined with `static values = { name: Type }`
|
|
1307
|
+
# Note: These methods use "stimulus_value" prefix to avoid conflict with
|
|
1308
|
+
# get_value(element) which gets an element's value attribute.
|
|
1309
|
+
|
|
1310
|
+
# Get a Stimulus value
|
|
1311
|
+
# @param name [Symbol, String] Value name (e.g., :count, :url)
|
|
1312
|
+
# @return [Object] The value (auto-converted by Stimulus based on type)
|
|
1313
|
+
# @example
|
|
1314
|
+
# # With self.values = { count: :number, url: :string }
|
|
1315
|
+
# stimulus_value(:count) # => 0
|
|
1316
|
+
# stimulus_value(:url) # => "/api/items"
|
|
1317
|
+
def stimulus_value(name)
|
|
1318
|
+
prop_name = "#{camelize(name, false)}Value"
|
|
1319
|
+
`this[#{prop_name}]`
|
|
1320
|
+
end
|
|
1321
|
+
|
|
1322
|
+
# Set a Stimulus value
|
|
1323
|
+
# @param name [Symbol, String] Value name
|
|
1324
|
+
# @param value [Object] The value to set
|
|
1325
|
+
# @example
|
|
1326
|
+
# set_stimulus_value(:count, 5)
|
|
1327
|
+
# set_stimulus_value(:items, [1, 2, 3])
|
|
1328
|
+
def set_stimulus_value(name, value)
|
|
1329
|
+
prop_name = "#{camelize(name, false)}Value"
|
|
1330
|
+
native_value = value.respond_to?(:to_n) ? value.to_n : value
|
|
1331
|
+
`this[#{prop_name}] = #{native_value}`
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
# Check if a Stimulus value exists (has data attribute)
|
|
1335
|
+
# @param name [Symbol, String] Value name
|
|
1336
|
+
# @return [Boolean] true if value's data attribute exists
|
|
1337
|
+
# @example
|
|
1338
|
+
# if has_stimulus_value?(:api_url)
|
|
1339
|
+
# fetch_json(stimulus_value(:api_url)) { |data| ... }
|
|
1340
|
+
# end
|
|
1341
|
+
def has_stimulus_value?(name)
|
|
1342
|
+
prop_name = "has#{camelize(name)}Value"
|
|
1343
|
+
`this[#{prop_name}]`
|
|
1344
|
+
end
|
|
1345
|
+
|
|
1346
|
+
# Increment a numeric Stimulus value
|
|
1347
|
+
# @param name [Symbol, String] Value name
|
|
1348
|
+
# @param amount [Number] Amount to increment (default: 1)
|
|
1349
|
+
def increment_stimulus_value(name, amount = 1)
|
|
1350
|
+
set_stimulus_value(name, stimulus_value(name) + amount)
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
# Decrement a numeric Stimulus value
|
|
1354
|
+
# @param name [Symbol, String] Value name
|
|
1355
|
+
# @param amount [Number] Amount to decrement (default: 1)
|
|
1356
|
+
def decrement_stimulus_value(name, amount = 1)
|
|
1357
|
+
set_stimulus_value(name, stimulus_value(name) - amount)
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1360
|
+
# Toggle a boolean Stimulus value
|
|
1361
|
+
# @param name [Symbol, String] Value name
|
|
1362
|
+
def toggle_stimulus_value(name)
|
|
1363
|
+
set_stimulus_value(name, !stimulus_value(name))
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1366
|
+
# ===== Stimulus CSS Classes API =====
|
|
1367
|
+
# Access CSS classes defined with `static classes = [ "loading", "active" ]`
|
|
1368
|
+
|
|
1369
|
+
# Get a Stimulus CSS class (singular)
|
|
1370
|
+
# @param name [Symbol, String] Class logical name
|
|
1371
|
+
# @return [String] The CSS class name
|
|
1372
|
+
# @example
|
|
1373
|
+
# # With static classes = [ "loading" ] and data-controller-loading-class="spinner"
|
|
1374
|
+
# get_class(:loading) # => "spinner"
|
|
1375
|
+
def get_class(name)
|
|
1376
|
+
prop_name = "#{camelize(name, false)}Class"
|
|
1377
|
+
`this[#{prop_name}]`
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1380
|
+
# Get all Stimulus CSS classes (plural, for space-separated values)
|
|
1381
|
+
# @param name [Symbol, String] Class logical name
|
|
1382
|
+
# @return [Array] Array of CSS class names
|
|
1383
|
+
# @example
|
|
1384
|
+
# # With data-controller-loading-class="spinner bg-gray-500"
|
|
1385
|
+
# get_classes(:loading) # => ["spinner", "bg-gray-500"]
|
|
1386
|
+
def get_classes(name)
|
|
1387
|
+
prop_name = "#{camelize(name, false)}Classes"
|
|
1388
|
+
`Array.from(this[#{prop_name}] || [])`
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
# Check if a Stimulus CSS class is defined
|
|
1392
|
+
# @param name [Symbol, String] Class logical name
|
|
1393
|
+
# @return [Boolean] true if class data attribute exists
|
|
1394
|
+
def has_class_definition?(name)
|
|
1395
|
+
prop_name = "has#{camelize(name)}Class"
|
|
1396
|
+
`this[#{prop_name}]`
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
# Apply a Stimulus CSS class to an element
|
|
1400
|
+
# @param element [Native] DOM element
|
|
1401
|
+
# @param name [Symbol, String] Class logical name
|
|
1402
|
+
def apply_class(element, name)
|
|
1403
|
+
el = to_native_element(element)
|
|
1404
|
+
class_name = get_class(name)
|
|
1405
|
+
`#{el}.classList.add(#{class_name})` if class_name
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
# Apply all Stimulus CSS classes to an element
|
|
1409
|
+
# @param element [Native] DOM element
|
|
1410
|
+
# @param name [Symbol, String] Class logical name
|
|
1411
|
+
def apply_classes(element, name)
|
|
1412
|
+
el = to_native_element(element)
|
|
1413
|
+
classes = get_classes(name)
|
|
1414
|
+
`#{el}.classList.add(...#{classes})` if classes
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1417
|
+
# Remove a Stimulus CSS class from an element
|
|
1418
|
+
# @param element [Native] DOM element
|
|
1419
|
+
# @param name [Symbol, String] Class logical name
|
|
1420
|
+
def remove_applied_class(element, name)
|
|
1421
|
+
el = to_native_element(element)
|
|
1422
|
+
class_name = get_class(name)
|
|
1423
|
+
`#{el}.classList.remove(#{class_name})` if class_name
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
# Remove all Stimulus CSS classes from an element
|
|
1427
|
+
# @param element [Native] DOM element
|
|
1428
|
+
# @param name [Symbol, String] Class logical name
|
|
1429
|
+
def remove_applied_classes(element, name)
|
|
1430
|
+
el = to_native_element(element)
|
|
1431
|
+
classes = get_classes(name)
|
|
1432
|
+
`#{el}.classList.remove(...#{classes})` if classes
|
|
1433
|
+
end
|
|
1434
|
+
|
|
1435
|
+
# ===== Stimulus Outlets API =====
|
|
1436
|
+
# Access outlets defined with `static outlets = [ "result" ]`
|
|
1437
|
+
|
|
1438
|
+
# Check if an outlet exists
|
|
1439
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1440
|
+
# @return [Boolean] true if outlet is connected
|
|
1441
|
+
# @example
|
|
1442
|
+
# if has_outlet?(:modal)
|
|
1443
|
+
# get_outlet(:modal).open
|
|
1444
|
+
# end
|
|
1445
|
+
def has_outlet?(name)
|
|
1446
|
+
prop_name = "has#{camelize(name)}Outlet"
|
|
1447
|
+
`this[#{prop_name}]`
|
|
1448
|
+
end
|
|
1449
|
+
|
|
1450
|
+
# Get a single outlet controller instance
|
|
1451
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1452
|
+
# @return [Native] The outlet controller instance
|
|
1453
|
+
# @note Throws error if outlet doesn't exist - use has_outlet? first
|
|
1454
|
+
def get_outlet(name)
|
|
1455
|
+
prop_name = "#{camelize(name, false)}Outlet"
|
|
1456
|
+
`this[#{prop_name}]`
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
# Get all outlet controller instances
|
|
1460
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1461
|
+
# @return [Array] Array of outlet controller instances
|
|
1462
|
+
def get_outlets(name)
|
|
1463
|
+
prop_name = "#{camelize(name, false)}Outlets"
|
|
1464
|
+
`Array.from(this[#{prop_name}] || [])`
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
# Get a single outlet's element
|
|
1468
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1469
|
+
# @return [Native] The outlet's controller element
|
|
1470
|
+
# @note Throws error if outlet doesn't exist - use has_outlet? first
|
|
1471
|
+
def get_outlet_element(name)
|
|
1472
|
+
prop_name = "#{camelize(name, false)}OutletElement"
|
|
1473
|
+
`this[#{prop_name}]`
|
|
1474
|
+
end
|
|
1475
|
+
|
|
1476
|
+
# Get all outlet elements
|
|
1477
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1478
|
+
# @return [Array] Array of outlet controller elements
|
|
1479
|
+
def get_outlet_elements(name)
|
|
1480
|
+
prop_name = "#{camelize(name, false)}OutletElements"
|
|
1481
|
+
`Array.from(this[#{prop_name}] || [])`
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1484
|
+
# Call a method on an outlet controller
|
|
1485
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1486
|
+
# @param method [Symbol, String] Method name to call
|
|
1487
|
+
# @param args [Array] Arguments to pass
|
|
1488
|
+
# @return [Object] Method return value
|
|
1489
|
+
def call_outlet(name, method, *args)
|
|
1490
|
+
return nil unless has_outlet?(name)
|
|
1491
|
+
outlet = get_outlet(name)
|
|
1492
|
+
js_call_on(outlet, method, *args)
|
|
1493
|
+
end
|
|
1494
|
+
|
|
1495
|
+
# Call a method on all outlet controllers
|
|
1496
|
+
# @param name [Symbol, String] Outlet identifier
|
|
1497
|
+
# @param method [Symbol, String] Method name to call
|
|
1498
|
+
# @param args [Array] Arguments to pass
|
|
1499
|
+
def call_all_outlets(name, method, *args)
|
|
1500
|
+
get_outlets(name).each do |outlet|
|
|
1501
|
+
js_call_on(outlet, method, *args)
|
|
1502
|
+
end
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
# ===== Stimulus dispatch() API =====
|
|
1506
|
+
# Emit custom events with controller identifier prefix
|
|
1507
|
+
|
|
1508
|
+
# Dispatch a Stimulus-style custom event
|
|
1509
|
+
# @param name [String] Event name (will be prefixed with controller identifier)
|
|
1510
|
+
# @param detail [Hash] Event detail data
|
|
1511
|
+
# @param target [Native, nil] Target element (default: controller element)
|
|
1512
|
+
# @param prefix [String, nil] Custom prefix (default: controller identifier)
|
|
1513
|
+
# @param bubbles [Boolean] Whether event bubbles (default: true)
|
|
1514
|
+
# @param cancelable [Boolean] Whether event is cancelable (default: true)
|
|
1515
|
+
# @return [Native] The dispatched event
|
|
1516
|
+
# @example
|
|
1517
|
+
# # In a "clipboard" controller:
|
|
1518
|
+
# stimulus_dispatch("copied", detail: { content: text })
|
|
1519
|
+
# # Dispatches "clipboard:copied" event
|
|
1520
|
+
def stimulus_dispatch(name, detail: {}, target: nil, prefix: nil, bubbles: true, cancelable: true)
|
|
1521
|
+
native_detail = detail.respond_to?(:to_n) ? detail.to_n : detail
|
|
1522
|
+
# Build options object, only including target/prefix if explicitly provided
|
|
1523
|
+
# Note: Ruby nil becomes Opal.nil in JS (truthy), so we must check explicitly
|
|
1524
|
+
options = `{ detail: #{native_detail}, bubbles: #{bubbles}, cancelable: #{cancelable} }`
|
|
1525
|
+
`#{options}.target = #{target}` unless target.nil?
|
|
1526
|
+
`#{options}.prefix = #{prefix}` unless prefix.nil?
|
|
1527
|
+
`this.dispatch(#{name}, #{options})`
|
|
1528
|
+
end
|
|
1529
|
+
|
|
1530
|
+
# Dispatch event and check if it was cancelled
|
|
1531
|
+
# @param name [String] Event name
|
|
1532
|
+
# @param detail [Hash] Event detail data
|
|
1533
|
+
# @return [Boolean] true if event was NOT cancelled
|
|
1534
|
+
def stimulus_dispatch_confirm(name, detail: {})
|
|
1535
|
+
event = stimulus_dispatch(name, detail: detail, cancelable: true)
|
|
1536
|
+
`!#{event}.defaultPrevented`
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1539
|
+
# ===== Stimulus Action Parameters API =====
|
|
1540
|
+
# Access action parameters from data-[identifier]-[name]-param attributes
|
|
1541
|
+
|
|
1542
|
+
# Get all action parameters from the current event
|
|
1543
|
+
# @return [Native] JavaScript object with all parameters
|
|
1544
|
+
# @example
|
|
1545
|
+
# # <button data-action="item#delete" data-item-id-param="123">
|
|
1546
|
+
# def delete(event)
|
|
1547
|
+
# params = action_params # => { id: 123 }
|
|
1548
|
+
# end
|
|
1549
|
+
def action_params
|
|
1550
|
+
`event.params || {}`
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
# Get a specific action parameter
|
|
1554
|
+
# @param name [Symbol, String] Parameter name
|
|
1555
|
+
# @return [Object] Parameter value (auto-typecast by Stimulus)
|
|
1556
|
+
# @example
|
|
1557
|
+
# action_param(:id) # => 123 (Number)
|
|
1558
|
+
# action_param(:url) # => "/api/item" (String)
|
|
1559
|
+
def action_param(name)
|
|
1560
|
+
`(event.params || {})[#{name.to_s}]`
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
# Get action parameter as integer with default
|
|
1564
|
+
# @param name [Symbol, String] Parameter name
|
|
1565
|
+
# @param default [Integer] Default value if missing or NaN
|
|
1566
|
+
# @return [Integer] Parameter value
|
|
1567
|
+
def action_param_int(name, default = 0)
|
|
1568
|
+
value = action_param(name)
|
|
1569
|
+
result = parse_int(value)
|
|
1570
|
+
is_nan?(result) ? default : result
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
# Get action parameter as boolean
|
|
1574
|
+
# @param name [Symbol, String] Parameter name
|
|
1575
|
+
# @return [Boolean] Parameter value
|
|
1576
|
+
def action_param_bool(name)
|
|
1577
|
+
value = action_param(name)
|
|
1578
|
+
`!!#{value} && #{value} !== "false" && #{value} !== "0"`
|
|
1579
|
+
end
|
|
1580
|
+
|
|
1581
|
+
# Check if an action parameter exists
|
|
1582
|
+
# @param name [Symbol, String] Parameter name
|
|
1583
|
+
# @return [Boolean] true if parameter exists
|
|
1584
|
+
def has_action_param?(name)
|
|
1585
|
+
`(event.params || {}).hasOwnProperty(#{name.to_s})`
|
|
1586
|
+
end
|
|
1587
|
+
|
|
1588
|
+
# ===== Stimulus Controller Access =====
|
|
1589
|
+
# Access controller properties and other controllers
|
|
1590
|
+
|
|
1591
|
+
# Get the controller's application instance
|
|
1592
|
+
# @return [Native] Stimulus Application
|
|
1593
|
+
def this_application
|
|
1594
|
+
`this.application`
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
# Get the controller's identifier
|
|
1598
|
+
# @return [String] Controller identifier (e.g., "modal", "tabs")
|
|
1599
|
+
def this_identifier
|
|
1600
|
+
`this.identifier`
|
|
1601
|
+
end
|
|
1602
|
+
|
|
1603
|
+
# Get the controller's element
|
|
1604
|
+
# @return [Native] Controller's root element
|
|
1605
|
+
def this_element
|
|
1606
|
+
`this.element`
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
# Get another controller instance by element and identifier
|
|
1610
|
+
# @param element [Native] DOM element with the controller
|
|
1611
|
+
# @param identifier [String] Controller identifier
|
|
1612
|
+
# @return [Native, nil] Controller instance or nil
|
|
1613
|
+
# @example
|
|
1614
|
+
# modal = get_controller(modal_element, "modal")
|
|
1615
|
+
# modal.open if modal
|
|
1616
|
+
def get_controller(element, identifier)
|
|
1617
|
+
el = to_native_element(element)
|
|
1618
|
+
`this.application.getControllerForElementAndIdentifier(#{el}, #{identifier})`
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1621
|
+
# Get all controllers of a type within an element's scope
|
|
1622
|
+
# @param element [Native] Container element
|
|
1623
|
+
# @param identifier [String] Controller identifier
|
|
1624
|
+
# @return [Array] Array of controller instances
|
|
1625
|
+
def get_controllers(element, identifier)
|
|
1626
|
+
el = to_native_element(element)
|
|
1627
|
+
`Array.from(#{el}.querySelectorAll('[data-controller~="' + #{identifier} + '"]'))
|
|
1628
|
+
.map(el => this.application.getControllerForElementAndIdentifier(el, #{identifier}))
|
|
1629
|
+
.filter(c => c !== null)`
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
# ===== Stimulus Scope Helpers =====
|
|
1633
|
+
|
|
1634
|
+
# Get the controller's scope (element and descendants, excluding nested controllers)
|
|
1635
|
+
# @return [Native] Controller's scope element
|
|
1636
|
+
def this_scope
|
|
1637
|
+
`this.scope.element`
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
# Check if an element is within this controller's scope
|
|
1641
|
+
# @param element [Native] Element to check
|
|
1642
|
+
# @return [Boolean] true if element is in scope
|
|
1643
|
+
def in_scope?(element)
|
|
1644
|
+
el = to_native_element(element)
|
|
1645
|
+
`this.element.contains(#{el})`
|
|
1646
|
+
end
|
|
1647
|
+
|
|
1310
1648
|
private
|
|
1311
1649
|
|
|
1312
1650
|
# Convert snake_case to camelCase, preserving existing camelCase
|