klods-ruby 0.2.1 → 1.0.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: 4e3ab8d782489ea35bb1c52b06491530b44f3c3679feb3523fa7ad95e715b4b3
4
- data.tar.gz: cc6334fa4a3c4376b721eea8bf4a616866786bbab49c2b20a2fa6c7666af0af2
3
+ metadata.gz: 844cc92165c4952be948f23654939d6781ca0feda123c8ea10e8296e22b806a0
4
+ data.tar.gz: c4952d7416e7f9dbf522649725e1d28deaad0722d3ceea78899961e18186a4e6
5
5
  SHA512:
6
- metadata.gz: 2300e7387f53982d140dc85858e06065d1b956320f059aa4ee90eca25d19816421d1966e9618a84c3a4034f9ae67d8d0ba426f2e3f4367f6ba9d1646f9be1035
7
- data.tar.gz: 8a1dc30aa6f622e80528d8cc78ab9a7c4e10e6f50a0a6b6e13ed4e6966c550cf73ca00d01169d0c6225703857046fafc48b6f60368ea97c8e911062998076687
6
+ metadata.gz: df8ed9b618d7d6f9b3b9400a323e3128d70ef8ee1749ed687362c5640a5a8a3015127b6449b4d240b009496147179ac8ab15639d2dd01dbbc598ac565e20066d
7
+ data.tar.gz: b0ee9c030e8d944baa807a2c2971142f6b9a39889624233daba59c1caa34ec02f09f8dbec5ba7070a7b01a30b29799c6ef4a3d33d6078a5bab097fd09741c5a4
@@ -37,16 +37,56 @@ module Klods
37
37
  Core.build(tag: "div", base: "klods-modal__actions", props: props, children: children)
38
38
  end
39
39
 
40
+ # Close button — the × icon is provided by CSS via a ::before mask-image.
41
+ # Closes the containing dialog automatically via inline JS.
40
42
  def modal_close(attrs = nil)
41
43
  attrs = (attrs || {}).transform_keys(&:to_s)
42
44
  extra_class = attrs.delete("class")
43
45
  cls = Core.class_names("klods-modal__close", Core.resolve_class(extra_class))
44
- final_attrs = {"type" => "button", "aria-label" => "Close"}
46
+ final_attrs = {
47
+ "type" => "button",
48
+ "aria-label" => "Close",
49
+ "onclick" => "this.closest('dialog').close()"
50
+ }
45
51
  .merge(attrs)
46
52
  .merge("class" => cls.empty? ? nil : cls)
47
53
  .compact
48
54
  Core.el("button", final_attrs)
49
55
  end
56
+
57
+ # Button that closes the containing <dialog> when clicked.
58
+ # Accepts the same props as button (e.g. variant:).
59
+ def modal_dismiss(a = nil, b = nil)
60
+ props, children = Core.normalize_args(a, b)
61
+ merged = {
62
+ "type" => "button",
63
+ "onclick" => "this.closest('dialog').close()"
64
+ }.merge(props.transform_keys(&:to_s))
65
+ Core.build(
66
+ tag: "button", base: "klods-button",
67
+ modifiers: {
68
+ variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }
69
+ },
70
+ props: merged, children: children
71
+ )
72
+ end
73
+
74
+ # Button that opens the next sibling <dialog> as a modal when clicked.
75
+ # Accepts the same props as button (e.g. variant:).
76
+ def modal_trigger(a = nil, b = nil)
77
+ props, children = Core.normalize_args(a, b)
78
+ merged = {
79
+ "type" => "button",
80
+ "onclick" => "this.nextElementSibling.showModal()"
81
+ }.merge(props.transform_keys(&:to_s))
82
+ Core.build(
83
+ tag: "button", base: "klods-button",
84
+ modifiers: {
85
+ variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }
86
+ },
87
+ props: merged, children: children
88
+ )
89
+ end
50
90
  end
51
91
  end
52
92
  end
@@ -22,6 +22,18 @@ module Klods
22
22
  {panel: panel, label: label, tab_id: tab_id, panel_id: panel_id, active: i == 0}
23
23
  end
24
24
 
25
+ activate_tab_js =
26
+ "var c=this.closest('.klods-tabs');" \
27
+ "c.querySelectorAll('[role=tab]').forEach(t=>{" \
28
+ "var a=t===this;" \
29
+ "t.setAttribute('aria-selected',a);" \
30
+ "t.classList.toggle('klods-tabs__tab--active',a);" \
31
+ "a?t.removeAttribute('tabindex'):t.setAttribute('tabindex','-1')" \
32
+ "});" \
33
+ "c.querySelectorAll(':scope>.klods-tabs__panel').forEach(p=>{" \
34
+ "p.hidden=p.id!==this.getAttribute('aria-controls')" \
35
+ "})"
36
+
25
37
  tab_list = Core.el(
26
38
  "div",
27
39
  {"class" => "klods-tabs__list", "role" => "tablist"},
@@ -32,7 +44,8 @@ module Klods
32
44
  "id" => item[:tab_id],
33
45
  "aria-selected" => item[:active].to_s,
34
46
  "aria-controls" => item[:panel_id],
35
- "class" => item[:active] ? "klods-tabs__tab klods-tabs__tab--active" : "klods-tabs__tab"
47
+ "class" => item[:active] ? "klods-tabs__tab klods-tabs__tab--active" : "klods-tabs__tab",
48
+ "onclick" => activate_tab_js
36
49
  }
37
50
  tab_attrs["tabindex"] = "-1" unless item[:active]
38
51
  Core.el("button", tab_attrs, item[:label])
@@ -28,9 +28,52 @@ module Klods
28
28
  )
29
29
  attrs = props.merge("role" => "status", "class" => cls.empty? ? nil : cls).compact
30
30
  body = Core.el("span", {"class" => "klods-toast__body"}, children)
31
- close_btn = Core.el("button", {"type" => "button", "class" => "klods-toast__close", "aria-label" => "Dismiss"})
31
+ dismiss_js = "var e=this.closest('.klods-toast');if(e){e.setAttribute('data-dismissing','');var f=setTimeout(function(){e.remove()},200);e.addEventListener('animationend',function(){clearTimeout(f);e.remove()},{once:true})}"
32
+ close_btn = Core.el("button", {"type" => "button", "class" => "klods-toast__close", "aria-label" => "Dismiss", "onclick" => dismiss_js})
32
33
  Core.el("div", attrs, [body, close_btn])
33
34
  end
35
+
36
+ # Renders a button that dynamically creates and shows a toast on click.
37
+ # message: the toast body text (string). toast_variant: toast style. duration: ms (0 = persistent).
38
+ def toast_trigger(a = nil, b = nil)
39
+ props, children = Core.normalize_args(a, b)
40
+ props = props.transform_keys(&:to_s)
41
+ message = props.delete("message")
42
+ message = children if message.nil?
43
+ variant = props.delete("toast_variant") || "default"
44
+ duration = props.key?("duration") ? props.delete("duration").to_i : 5000
45
+
46
+ escaped = message.to_s.gsub(/[\\']/) { |c| "\\#{c}" }
47
+ toast_class = (variant.to_s == "default") ? "klods-toast" : "klods-toast klods-toast--#{variant}"
48
+
49
+ dismiss_fn = "function _kd(e){e.setAttribute('data-dismissing','');var f=setTimeout(function(){e.remove()},200);e.addEventListener('animationend',function(){clearTimeout(f);e.remove()},{once:true})}"
50
+ get_region = "var r=document.querySelector('.klods-toast-region');" \
51
+ "if(!r){r=document.createElement('div');r.className='klods-toast-region';" \
52
+ "r.setAttribute('aria-live','polite');r.setAttribute('aria-atomic','false');" \
53
+ "r.setAttribute('role','region');r.setAttribute('aria-label','Notifications');" \
54
+ "document.body.appendChild(r)}"
55
+ make_toast = "var t=document.createElement('div');t.className='#{toast_class}';t.setAttribute('role','status');" \
56
+ "var b=document.createElement('span');b.className='klods-toast__body';b.textContent='#{escaped}';t.appendChild(b);" \
57
+ "var c=document.createElement('button');c.type='button';c.className='klods-toast__close';" \
58
+ "c.setAttribute('aria-label','Dismiss');c.onclick=function(){_kd(t)};t.appendChild(c);r.appendChild(t)"
59
+ auto_dismiss = (duration > 0) ? ";setTimeout(function(){_kd(t)},#{duration})" : ""
60
+ onclick = "(function(){#{dismiss_fn};#{get_region};#{make_toast}#{auto_dismiss}})()"
61
+
62
+ Core.build(tag: "button", base: "klods-button",
63
+ modifiers: {variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }},
64
+ props: props.merge("type" => "button", "onclick" => onclick),
65
+ children: children)
66
+ end
67
+
68
+ # Renders a button that removes all visible toasts on click.
69
+ def clear_toasts_trigger(a = nil, b = nil)
70
+ props, children = Core.normalize_args(a, b)
71
+ onclick = "document.querySelectorAll('.klods-toast-region').forEach(function(r){r.remove()})"
72
+ Core.build(tag: "button", base: "klods-button",
73
+ modifiers: {variant: ->(v) { (v && v.to_s != "default") ? "klods-button--#{v}" : nil }},
74
+ props: props.transform_keys(&:to_s).merge("type" => "button", "onclick" => onclick),
75
+ children: children)
76
+ end
34
77
  end
35
78
  end
36
79
  end
@@ -1,8 +1,6 @@
1
1
  module Klods
2
2
  module Components
3
3
  module Tooltip
4
- # tooltip(props, children) — renders the static HTML structure.
5
- # Visibility is toggled by data-open via client JS; event wiring is omitted here.
6
4
  def tooltip(props, children)
7
5
  props = props.transform_keys(&:to_s)
8
6
  tip = props.delete("tip")
@@ -16,10 +14,19 @@ module Klods
16
14
  tip
17
15
  )
18
16
 
17
+ # Inline JS mirrors showTooltip/hideTooltip from klods-js.
18
+ # _kh (klods hide) is stored on the tip element to cancel pending hides.
19
+ show_js = "var t=this.querySelector('[role=tooltip]');if(t){clearTimeout(t._kh);t.setAttribute('data-open','')}"
20
+ hide_js = "var t=this.querySelector('[role=tooltip]');if(t){t._kh=setTimeout(()=>t.removeAttribute('data-open'),80)}"
21
+
19
22
  cls = Core.class_names("klods-tooltip", Core.resolve_class(extra_class))
20
23
  attrs = props.merge(
21
24
  "class" => cls.empty? ? nil : cls,
22
- "aria-describedby" => id
25
+ "aria-describedby" => id,
26
+ "onmouseenter" => show_js,
27
+ "onmouseleave" => hide_js,
28
+ "onfocusin" => show_js,
29
+ "onfocusout" => "if(!this.contains(event.relatedTarget)){var t=this.querySelector('[role=tooltip]');if(t)t.removeAttribute('data-open')}"
23
30
  ).compact
24
31
 
25
32
  Core.el("span", attrs, [children, tip_node])
data/lib/klods/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Klods
2
- VERSION = "0.2.1"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klods-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Drue Wilding