markawesome 0.14.0 → 0.16.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: afd9d40552aa4f47c46ce12a72e4340075e9049a0d374dabbd894d6b4e9c0771
4
- data.tar.gz: bf63ff67375e59593d352c35b9e6852fd4f436a4005eba52bdf695b54d734f11
3
+ metadata.gz: eee224c1babae361baa15c8c0f0a65bebf839d9debc599bf8e4a43bb3752bdb4
4
+ data.tar.gz: 9d8d78ec8aaf682bf252c634a220fa7cb9283ec15dc70c280d69967ed9f2945b
5
5
  SHA512:
6
- metadata.gz: eeb2dffdb406f0eeb414cedfe834a616540956dddf5854c972b9743088e62b9d92054a4a1db2be1dc68817b5b81bec1ef7f0b30a0055a5508c6641710b8dc5f1
7
- data.tar.gz: c76afeb5d61c70cab00f10e8a3a0d9b2f825bf04b5c4e5f46915506092fb826c9f1e4cb49061da3c29437404c2c0d2188eaf38d634704a56f6398a3410438770
6
+ metadata.gz: edb36c15048371cf37c3d860d214faacc61158b9a497f09293ade0fb152b02bcb0c7a37ed1db0725939f1bea4d5159aad9bf3452456120ed5c5250c55dd61f4b
7
+ data.tar.gz: 112fbcbb947dc90348bc2c187b0ee3a8379fba4bff06e8da0ee3f8b550e9307933333795e245a524b41478b41fc75875f33a62f1b63263a13c5dabd16a37475f
data/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.16.0] - 2026-06-25
8
+
9
+ ### Added
10
+
11
+ - `CopyButtonTransformer` now accepts a `tooltip:full|copy|none` token controlling Web Awesome's `<wa-copy-button>` **`tooltip`** attribute (added in WA 3.6) — the *mode* of the built-in hover affordance, distinct from the existing `tooltip-placement`:
12
+ - **`full`** (WA default) — the tooltip shows on hover **and** focus, and is reused to display the brief copy-success/error feedback. Emitting `tooltip="full"` is explicit-but-redundant.
13
+ - **`copy`** — the tooltip stays **silent on hover/focus** and appears **only briefly to confirm a successful or failed copy** (feedback-only, no hover hint).
14
+ - **`none`** — no tooltip in any state.
15
+ - The token is a `key:value` form (like `distance:N`, `icon:name`), order-independent with the other copy-button tokens. The capture is **enum-anchored** (`full|copy|none`), so invalid values such as `tooltip:bogus` simply don't match and are dropped, falling back to WA's default — consistent with the "unknown tokens dropped" convention. Emit order stays deterministic: `tooltip` follows `tooltip-placement`.
16
+ - The plain-markdown degradation path (`render_as_markdown`) is unaffected — the wrapper and all params are discarded, leaving only the copied content.
17
+
18
+ ## [0.15.0] - 2026-06-24
19
+
20
+ ### Added
21
+
22
+ - New `TooltipTransformer` producing Web Awesome `<wa-tooltip>` — inline contextual help on hover/focus for glossary terms and inline definitions. Declarative, zero-JS, and static-site-safe: the tooltip is attached to a focusable anchor `<span>` via an auto-generated `for`/`id` pair, so authors write only the trigger term and the tip text.
23
+ - **Inline syntax** (primary): `(((anchor term >>> tip text)))`. The `(((` delimiter is unused by any other transformer, is not Markdown- or Liquid-special, and never appears in normal prose; the `>>>` separator matches the popover/dialog/details convention (anchor first, tip after `>>>`).
24
+ - **Block alternative**: `:::wa-tooltip placement? distance:N?` / anchor / `>>>` / tip / `:::`, for consistency with every other component's `:::wa-*` form.
25
+ - **Attributes** (order-independent leading tokens): `placement` (`top` default, `bottom`, `left`, `right`) and `distance:N`. Mirrors `PopoverTransformer`'s surface minus `link`/`without-arrow` (WA `<wa-tooltip>` has no `without-arrow` boolean — arrow size is CSS-only).
26
+ - **Tip content** is plain text (HTML-escaped), with literal `\n` rendered as `<br>` — the same surface as the popover's *inline* form. Tooltips hold brief text, so there is no Markdown body.
27
+ - **Emitted markup**: a focusable `<span id="tooltip-…" tabindex="0" class="ma-tooltip-anchor" style="text-decoration: underline dotted; cursor: help;">` anchor followed by `<wa-tooltip for="tooltip-…" placement="…" [distance="…"]>`. The anchor is focusable so keyboard/AT users get the tip (tooltips fire on focus too); `ma-tooltip-anchor` is a styling hook (mirrors popover's `ma-popover-trigger`).
28
+ - **IDs** are auto-wired via `tooltip-<first 8 of MD5(anchor+tip)>`, with `-2`/`-3` dedup suffixes for repeated identical tooltips — the same scheme as `PopoverTransformer`. Runs immediately after `PopoverTransformer` (early, after `LayoutTransformer`) so inline tooltips inside cards/callouts/details are transformed before Kramdown escapes `(((`/`>>>`.
29
+ - **Plain-markdown degradation** (`render_as_markdown`, used for `.md` endpoints / llms.txt): both forms degrade to `**anchor** (tip)`, mirroring the popover inline degradation.
30
+ - Aligned placements (`top-start`, …), `show-delay`/`hide-delay`, and a rich-content block form are explicit future follow-ups, not part of v1.
31
+
7
32
  ## [0.14.0] - 2026-06-24
8
33
 
9
34
  ### Added
data/README.md CHANGED
@@ -20,7 +20,7 @@ Used as the transformation engine for the [jekyll-webawesome](https://github.com
20
20
  | **Card** | `===` | `:::wa-card` | `<wa-card>content</wa-card>` |
21
21
  | **Carousel** | `~~~~~~` | `:::wa-carousel` | `<wa-carousel>` with carousel items |
22
22
  | **Comparison** | `\|\|\|` or `\|\|\|25` | `:::wa-comparison` or `:::wa-comparison 25` | `<wa-comparison>` with before/after slots |
23
- | **Copy Button** | `<<<` | `:::wa-copy-button` | `<wa-copy-button value="content">content</wa-copy-button>` |
23
+ | **Copy Button** | `<<<placement? tooltip:full\|copy\|none?` | `:::wa-copy-button placement? tooltip:full\|copy\|none?` | `<wa-copy-button value="content" tooltip-placement="top" tooltip="copy"></wa-copy-button>` |
24
24
  | **Details** | `^^^appearance? icon-placement?` | `:::wa-details appearance? icon-placement?` | `<wa-details>content</wa-details>` |
25
25
  | **Dialog** | `???params?` | `:::wa-dialog params?` | `<wa-dialog>` with trigger button and content |
26
26
  | **Icon** | `$$$icon-name` | `:::wa-icon icon-name` | `<wa-icon name="icon-name"></wa-icon>` |
@@ -26,7 +26,7 @@ module Markawesome
26
26
  #
27
27
  # @param component [Symbol] one of :accordion, :callout, :badge, :button,
28
28
  # :card, :carousel, :comparison, :copy_button, :details, :dialog, :icon,
29
- # :image_dialog, :layout, :popover, :tabs, :tag.
29
+ # :image_dialog, :layout, :popover, :tabs, :tag, :tooltip.
30
30
  # @yield [content, options] Proc that receives the full source content
31
31
  # and the renderer options; returns the content with that component
32
32
  # syntax replaced.
@@ -45,6 +45,7 @@ module Markawesome
45
45
  PIPELINE = %i[
46
46
  layout
47
47
  popover
48
+ tooltip
48
49
  badge
49
50
  button
50
51
  callout
@@ -64,6 +65,7 @@ module Markawesome
64
65
  TRANSFORMER_MAP = {
65
66
  layout: LayoutTransformer,
66
67
  popover: PopoverTransformer,
68
+ tooltip: TooltipTransformer,
67
69
  badge: BadgeTransformer,
68
70
  button: ButtonTransformer,
69
71
  callout: CalloutTransformer,
@@ -12,6 +12,7 @@ module Markawesome
12
12
 
13
13
  content = LayoutTransformer.transform(content)
14
14
  content = PopoverTransformer.transform(content)
15
+ content = TooltipTransformer.transform(content)
15
16
  content = BadgeTransformer.transform(content)
16
17
  content = ButtonTransformer.transform(content)
17
18
  content = CalloutTransformer.transform(content)
@@ -10,6 +10,7 @@ module Markawesome
10
10
  #
11
11
  # Params: space-separated tokens, any order (rightmost-wins for conflicts)
12
12
  # Placement: top, right, bottom, left
13
+ # Tooltip mode: tooltip:full|copy|none (when the built-in tooltip appears)
13
14
  # Duration: numeric value (feedback-duration in milliseconds)
14
15
  # Flags: disabled
15
16
  # Labels: copy-label="text", success-label="text", error-label="text"
@@ -37,6 +38,8 @@ module Markawesome
37
38
  disabled: %w[disabled]
38
39
  }.freeze
39
40
 
41
+ TOOLTIP_MODES = %w[full copy none].freeze
42
+
40
43
  def self.transform(content)
41
44
  # Define both regex patterns - capture params as a single string
42
45
  primary_regex = /^<<<(.*?)\n(.*?)\n<<</m
@@ -58,6 +61,10 @@ module Markawesome
58
61
  # Extract numeric feedback-duration
59
62
  attributes[:feedback_duration] = ::Regexp.last_match(1) if params_string =~ /\b(\d+)\b/
60
63
 
64
+ # Extract tooltip mode (enum-anchored: invalid values simply don't match and are dropped)
65
+ tooltip_mode_regex = /\btooltip:(#{TOOLTIP_MODES.join('|')})\b/
66
+ attributes[:tooltip] = ::Regexp.last_match(1) if params_string =~ tooltip_mode_regex
67
+
61
68
  build_copy_button_html(copy_content, attributes)
62
69
  end
63
70
 
@@ -98,6 +105,9 @@ module Markawesome
98
105
  # Add tooltip placement
99
106
  attr_parts << "tooltip-placement=\"#{attributes[:placement]}\"" if attributes[:placement]
100
107
 
108
+ # Add tooltip mode
109
+ attr_parts << "tooltip=\"#{attributes[:tooltip]}\"" if attributes[:tooltip]
110
+
101
111
  # Add custom labels
102
112
  attr_parts << "copy-label=\"#{attributes[:copy_label]}\"" if attributes[:copy_label]
103
113
  attr_parts << "success-label=\"#{attributes[:success_label]}\"" if attributes[:success_label]
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require_relative 'base_transformer'
5
+ require_relative '../attribute_parser'
6
+
7
+ module Markawesome
8
+ # Transforms tooltip syntax into wa-tooltip elements attached to a focusable
9
+ # anchor span via an auto-generated for/id pair. Declarative, zero-JS, and
10
+ # static-site-safe — great for glossary terms and inline definitions.
11
+ #
12
+ # Inline syntax (primary): (((anchor term >>> tip text)))
13
+ # Alternative block syntax: :::wa-tooltip params\nanchor\n>>>\ntip\n:::
14
+ #
15
+ # Params: space-separated tokens (order doesn't matter)
16
+ # Placement: top (default), bottom, left, right
17
+ # Distance: distance:N (e.g., distance:10)
18
+ #
19
+ # Tip content is plain text (HTML-escaped), with literal `\n` rendered as
20
+ # <br> — the same surface as the popover's inline form. Tooltips hold brief
21
+ # text, so there is no markdown body.
22
+ class TooltipTransformer < BaseTransformer
23
+ TOOLTIP_ATTRIBUTES = {
24
+ placement: %w[top bottom left right]
25
+ }.freeze
26
+
27
+ # Inline regex (single-line, no newlines allowed): capture 1 = params+anchor,
28
+ # capture 2 = tip text.
29
+ INLINE_REGEX = /\(\(\([ \t]*([^\r\n]*?)[ \t]*>>>[ \t]*([^\r\n]+?)[ \t]*\)\)\)/
30
+ # Block alternative regex (multiline): capture 1 = params, 2 = anchor, 3 = tip.
31
+ ALTERNATIVE_REGEX = /^:::wa-tooltip([^\n]*)$\n(.*?)\n^>>>$\n(.*?)\n^:::$/m
32
+
33
+ def self.transform(content)
34
+ # Tracks ID base usage within this transform call so repeated tooltips
35
+ # (same anchor + tip) get disambiguated suffixes instead of colliding on
36
+ # the page.
37
+ seen_ids = Hash.new(0)
38
+
39
+ inline_transform = {
40
+ regex: INLINE_REGEX,
41
+ block: proc do |_match, matchdata|
42
+ combined = matchdata[1]
43
+ tip_text = matchdata[2].strip
44
+
45
+ params_string, anchor_text = parse_inline_anchor_and_params(combined)
46
+ placement, distance = parse_parameters(params_string)
47
+
48
+ tooltip_id = generate_tooltip_id(anchor_text, tip_text, seen_ids)
49
+
50
+ build_tooltip_html(tooltip_id, anchor_text, tip_text,
51
+ { placement: placement, distance: distance })
52
+ end
53
+ }
54
+
55
+ alternative_transform = {
56
+ regex: ALTERNATIVE_REGEX,
57
+ block: proc do |_match, matchdata|
58
+ params_string = matchdata[1]
59
+ anchor_text = matchdata[2].strip
60
+ tip_text = matchdata[3].strip
61
+
62
+ placement, distance = parse_parameters(params_string)
63
+
64
+ tooltip_id = generate_tooltip_id(anchor_text, tip_text, seen_ids)
65
+
66
+ build_tooltip_html(tooltip_id, anchor_text, tip_text,
67
+ { placement: placement, distance: distance })
68
+ end
69
+ }
70
+
71
+ # Inline pattern first to avoid conflicts with the block pattern.
72
+ apply_multiple_patterns(content, [inline_transform, alternative_transform])
73
+ end
74
+
75
+ def self.render_as_markdown(content, _options = {})
76
+ inline_transform = {
77
+ regex: INLINE_REGEX,
78
+ block: proc do |_match, matchdata|
79
+ combined = matchdata[1]
80
+ tip_text = matchdata[2].strip
81
+ _params, anchor_text = parse_inline_anchor_and_params(combined)
82
+ "**#{anchor_text}** (#{tip_text})"
83
+ end
84
+ }
85
+
86
+ alternative_transform = {
87
+ regex: ALTERNATIVE_REGEX,
88
+ block: proc do |_match, matchdata|
89
+ anchor_text = matchdata[2].strip
90
+ tip_text = matchdata[3].strip
91
+ "**#{anchor_text}** (#{tip_text})"
92
+ end
93
+ }
94
+
95
+ apply_multiple_patterns(content, [inline_transform, alternative_transform])
96
+ end
97
+
98
+ class << self
99
+ private
100
+
101
+ def parse_parameters(params_string)
102
+ return ['top', nil] if params_string.nil? || params_string.strip.empty?
103
+
104
+ attributes = AttributeParser.parse(params_string, TOOLTIP_ATTRIBUTES)
105
+ placement = attributes[:placement] || 'top'
106
+
107
+ # Look for distance:N parameter (rightmost-wins)
108
+ tokens = params_string.strip.split(/\s+/)
109
+ distance_token = tokens.reverse.find { |token| token.match?(/^distance:\d+$/) }
110
+ distance = distance_token&.sub('distance:', '')
111
+
112
+ [placement, distance]
113
+ end
114
+
115
+ def generate_tooltip_id(anchor_text, tip_text, seen_ids)
116
+ hash_input = "#{anchor_text}#{tip_text}"
117
+ hash = Digest::MD5.hexdigest(hash_input)
118
+ base = "tooltip-#{hash[0..7]}"
119
+ occurrence = seen_ids[base] += 1
120
+ occurrence == 1 ? base : "#{base}-#{occurrence}"
121
+ end
122
+
123
+ def parse_inline_anchor_and_params(combined_string)
124
+ tokens = combined_string.strip.split(/\s+/)
125
+ param_tokens = []
126
+ anchor_tokens = []
127
+ found_anchor = false
128
+
129
+ tokens.each do |token|
130
+ if !found_anchor && tooltip_param?(token)
131
+ param_tokens << token
132
+ else
133
+ found_anchor = true
134
+ anchor_tokens << token
135
+ end
136
+ end
137
+
138
+ anchor_text = anchor_tokens.join(' ')
139
+
140
+ # If no anchor text remains, treat entire string as the anchor (no params)
141
+ if anchor_text.empty?
142
+ ['', combined_string.strip]
143
+ else
144
+ [param_tokens.join(' '), anchor_text]
145
+ end
146
+ end
147
+
148
+ def tooltip_param?(token)
149
+ TOOLTIP_ATTRIBUTES.any? { |_attr, values| values.include?(token) } ||
150
+ token.match?(/^distance:\d+$/)
151
+ end
152
+
153
+ def build_tooltip_html(tooltip_id, anchor_text, tip_text, options)
154
+ anchor_content = escape_html(anchor_text)
155
+ tip_escaped = escape_html(tip_text).gsub('\n', '<br>')
156
+
157
+ tooltip_attrs = ["for=\"#{tooltip_id}\""]
158
+ tooltip_attrs << "placement=\"#{options[:placement]}\""
159
+ tooltip_attrs << "distance=\"#{options[:distance]}\"" if options[:distance]
160
+
161
+ anchor = build_anchor(tooltip_id, anchor_content)
162
+
163
+ "#{anchor}<wa-tooltip #{tooltip_attrs.join(' ')}>#{tip_escaped}</wa-tooltip>"
164
+ end
165
+
166
+ # Focusable span so keyboard/AT users get the tip (tooltips fire on focus
167
+ # too). The dotted underline + help cursor mirror the link-style popover
168
+ # trigger; the ma-tooltip-anchor class is a styling hook for consumers.
169
+ def build_anchor(tooltip_id, anchor_content)
170
+ style = 'text-decoration: underline dotted; cursor: help;'
171
+ "<span id=\"#{tooltip_id}\" tabindex=\"0\" class=\"ma-tooltip-anchor\" " \
172
+ "style=\"#{style}\">#{anchor_content}</span>"
173
+ end
174
+
175
+ def escape_html(text)
176
+ text.gsub('&', '&amp;')
177
+ .gsub('<', '&lt;')
178
+ .gsub('>', '&gt;')
179
+ .gsub('"', '&quot;')
180
+ .gsub("'", '&#39;')
181
+ end
182
+ end
183
+ end
184
+ end
@@ -20,3 +20,4 @@ require_relative 'transformers/layout_transformer'
20
20
  require_relative 'transformers/popover_transformer'
21
21
  require_relative 'transformers/tabs_transformer'
22
22
  require_relative 'transformers/tag_transformer'
23
+ require_relative 'transformers/tooltip_transformer'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Markawesome
4
- VERSION = '0.14.0'
4
+ VERSION = '0.16.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markawesome
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janne Waren
@@ -101,6 +101,7 @@ files:
101
101
  - lib/markawesome/transformers/popover_transformer.rb
102
102
  - lib/markawesome/transformers/tabs_transformer.rb
103
103
  - lib/markawesome/transformers/tag_transformer.rb
104
+ - lib/markawesome/transformers/tooltip_transformer.rb
104
105
  - lib/markawesome/version.rb
105
106
  - markawesome.gemspec
106
107
  homepage: https://github.com/jannewaren/markawesome