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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 414432537b131f84b1f5d5aa2784d2b9206ac2ae9c75ce8f6da47a2eea266ca6
4
- data.tar.gz: 77b061084bf99cc1817ec7385570298fb2b469932f973127817cb4602c4b59b8
3
+ metadata.gz: 5274b86604ed917278cf1739cb4663c1d93246cd6bf82876876fea56c239c6c2
4
+ data.tar.gz: 72291ac54ac2770b38b7ade5a8907751c9845926efbc54ff479b6ac8623d485e
5
5
  SHA512:
6
- metadata.gz: c7e996efed354341ece21cb6d3ee78b60a74794954ac540363ec4aa73e8c97cb6dd2756b191f7871a38a187e76c46c112f3980c5dd45e1b250bf5dd8ccd76d4c
7
- data.tar.gz: 778c0d2d9b3a73e97107b3d86e96aed8d20f975d56fe44ebb1356032829af044b63aaf2b9d53aaa449f18e32b9281be52357279d924211224507b015ea6e89d5
6
+ metadata.gz: 1b134a748ab8322e8889dcd64e687632581ae5da768c5a5b26e0e2e52c032c309e0b39ebd9e0dd2a0645bdaf9ae53e60ed0450985611b220970a9a1ef802d682
7
+ data.tar.gz: a309c63ca72694695797d1d5ad0edef41427664a2fd5e4d827776b42bd4a4e02c940f772f6aab2afb5b857fa1b49f31ee749ddd674893ca7207566035ea7eeb1
@@ -1,5 +1,5 @@
1
1
  module Opal
2
2
  module Vite
3
- VERSION = "0.2.17"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opal-vite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.17
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stofu1234