ruby_native 0.11.0 → 0.11.2

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: 354cc245f5601b5e39b3cbba720da1fe06edf675eb373e22eb1028b2c27056ce
4
- data.tar.gz: 38080a8029fbffa2c920cd734196a88377f6b20967b83f9f947eab37835f46c6
3
+ metadata.gz: 17263e207966bb624e15f4fc93399c2cfb28b15d9810cffa563cfa24b5a8b725
4
+ data.tar.gz: 6e7b9c3de16b19ef85ec7a614b207cf22e5d39f1517e5d9467950d315586660b
5
5
  SHA512:
6
- metadata.gz: 92f68b9731f743efd5aece5debb221c4963a93b373f9c76997dc688eba4f6da85b3eb3832efa457ebca0a0ed93d36a2b8417d0af5efa2fb23e8ce64734ac7002
7
- data.tar.gz: 3142eea082eab9acc7f279173ca731452cd3e21f58a1d491470b61433204bed26a35f81f71ca6fad60ac7271fb776931872215867d6fbc574682605ee7b8286f
6
+ metadata.gz: a9574b90d18f99866ee2d2c291475aeac0f4c7e57f8b15ea2c8cdc0aaa312818e2d7f5e1e5fa421e313e3f5214d36a314c975d56146b8d329660aed07aaab20c
7
+ data.tar.gz: 3c2da4697edaa56eb5194531352282c5c28aa1fa6c3c4edbd83d6a74da1f5d9a751e7175799244ad207741c73ecc4ba29a55a061cdcf563412bee7d9d21203fb
@@ -85,6 +85,89 @@ module RubyNative
85
85
  tag.div(data: data, hidden: true)
86
86
  end
87
87
 
88
+ # No :root. Landing a page as the tab root — dropping what is behind it —
89
+ # is a distinct operation from replacing the current entry, and no shell
90
+ # implements it: every one of them mapped :root onto replace, which unwinds
91
+ # nothing. Offering the word without the behavior is worse than not
92
+ # offering it, so it is out until a shell can honor it.
93
+ ACTIONS = %w[push replace].freeze
94
+
95
+ # Validates a push/replace landing value. Returns the value as a string,
96
+ # raises otherwise.
97
+ def self.validate_action(value, label: "action")
98
+ value = value.to_s
99
+ unless ACTIONS.include?(value)
100
+ raise ArgumentError,
101
+ "#{label} must be :push or :replace, got #{value.inspect}"
102
+ end
103
+ value
104
+ end
105
+
106
+ # The landing intents a page can declare about itself. Only :root so far.
107
+ # :modal is the obvious second member (both shells hardcode a /new + /edit
108
+ # rule today that no app can reach), which is why this is a vocabulary
109
+ # rather than a boolean tag.
110
+ PRESENTATION_INTENTS = %w[root].freeze
111
+
112
+ # Declares that this page is a root: it lands with nothing behind it, and no
113
+ # back affordance, wherever it lands.
114
+ #
115
+ # <%= native_presentation_tag :root %>
116
+ #
117
+ # Emits the fact on two channels, because the shells need it at two
118
+ # different moments and neither channel reaches both:
119
+ #
120
+ # 1. A `data-native-presentation` element, reported with every other signal
121
+ # once the page has rendered. This is what Normal Mode reads, and Normal
122
+ # Mode can act on it late: it has no push stack, so suppressing back is
123
+ # not a navigation and nothing refetches.
124
+ #
125
+ # 2. A `Native-Presentation` response header. Advanced Mode has to decide
126
+ # before the navigation commits, or it pushes and then visibly corrects
127
+ # itself. On a form submission Turbo has already fetched the destination
128
+ # by the time it proposes the visit, so the header is readable at
129
+ # `turbo:before-fetch-response` — before the proposal — and the header is
130
+ # the only part of that response readable synchronously, since the body
131
+ # arrives as a promise that consuming would take from Turbo.
132
+ #
133
+ # Nothing is declared at the origin. Both channels ride the response for the
134
+ # page itself, so a redirect chain carries the intent to wherever it actually
135
+ # lands rather than to wherever the link pointed.
136
+ #
137
+ # In Advanced Mode this takes effect before the navigation commits when the
138
+ # page arrives from a form submission, because that is the only case where
139
+ # Turbo has already fetched the destination by the time it proposes the
140
+ # visit. A link tap, a deep link and a cold boot are proposed before anything
141
+ # is fetched, so those fall back to the element and the shell corrects after
142
+ # the page renders. Normal Mode always uses the element.
143
+ #
144
+ # Do not call this inside a `cache` block. On a cache hit the element comes
145
+ # back from the cache and the header is never set, which silently leaves
146
+ # Advanced Mode with only the slower path.
147
+ def native_presentation_tag(presentation)
148
+ value = presentation.to_s
149
+ unless PRESENTATION_INTENTS.include?(value)
150
+ raise ArgumentError,
151
+ "native_presentation_tag must be #{PRESENTATION_INTENTS.map { |i| ":#{i}" }.join(" or ")}, " \
152
+ "got #{value.inspect}"
153
+ end
154
+
155
+ # `respond_to?` rather than a nil check alone: ActionView forwards it to
156
+ # the controller for the delegated methods, so a view rendered without one
157
+ # answers false here instead of raising a DelegationError.
158
+ if respond_to?(:response) && response
159
+ if Rails.env.development? && response.committed?
160
+ Rails.logger.warn(
161
+ "[ruby_native] native_presentation_tag rendered after the response was committed, " \
162
+ "so Advanced Mode will not see it before the navigation commits."
163
+ )
164
+ end
165
+ response.headers["Native-Presentation"] = value
166
+ end
167
+
168
+ tag.div(data: { native_presentation: value }, hidden: true)
169
+ end
170
+
88
171
  def native_navbar_tag(title = nil, pull_to_refresh: true, &block)
89
172
  builder = NavbarBuilder.new(self)
90
173
  capture(builder, &block) if block
@@ -131,14 +214,43 @@ module RubyNative
131
214
  # form (`{ ios: "...", android: "..." }`). When both are given, a matching
132
215
  # `icons[platform]` wins; otherwise falls back to `icon`. Returns nil when
133
216
  # nothing resolves.
217
+ #
218
+ # A non-Hash `icons:` raises rather than falling through. `icons: [ios:
219
+ # "...", android: "..."]` is an easy slip in ERB and yields an Array holding
220
+ # one Hash, which used to skip the lookup and fall back to `icon` — usually
221
+ # nil, so the button rendered with no icon on either platform and nothing
222
+ # said why. That reads as "per-platform icons are broken" rather than "wrong
223
+ # bracket," and it is invisible on whichever platform you aren't testing.
134
224
  def self.resolve_icon(icon: nil, icons: nil, platform: nil)
225
+ if !icons.nil? && !icons.is_a?(Hash)
226
+ raise ArgumentError,
227
+ "icons: must be a Hash like { ios: \"square.and.arrow.up\", android: \"share\" }, " \
228
+ "got #{icons.class}. Check for square brackets instead of curly braces."
229
+ end
230
+
135
231
  if icons.is_a?(Hash) && platform
136
232
  key = platform.to_sym
137
233
  per_platform = icons[key] || icons[key.to_s]
138
234
  return per_platform if per_platform
139
235
  end
140
- icon
236
+
237
+ # Nothing matched, which is every web render: `native_platform` is nil in
238
+ # a browser. Fall back to `icon:`, then to any name in `icons:`, the same
239
+ # order `RubyNative.backfill_tab_icons` uses for the YAML config.
240
+ #
241
+ # Returning nil here instead would make `icons:` alone unusable, because
242
+ # `native_fab_tag` raises on a nil icon and would 500 a page that renders
243
+ # fine inside the app. The signal element is hidden and only read by the
244
+ # native app, so which name survives on the web doesn't matter.
245
+ icon || fallback_icon(icons)
246
+ end
247
+
248
+ def self.fallback_icon(icons)
249
+ return nil unless icons.is_a?(Hash)
250
+
251
+ icons[:ios] || icons["ios"] || icons[:android] || icons["android"]
141
252
  end
253
+ private_class_method :fallback_icon
142
254
 
143
255
  class NavbarBuilder
144
256
  def initialize(context)
@@ -225,13 +337,14 @@ module RubyNative
225
337
  @items = []
226
338
  end
227
339
 
228
- def item(title, href: nil, click: nil, icon: nil, icons: nil, selected: false)
340
+ def item(title, href: nil, click: nil, icon: nil, icons: nil, selected: false, action: nil)
229
341
  resolved = RubyNative::Helper.resolve_icon(icon: icon, icons: icons, platform: @context.try(:native_platform))
230
342
  data = { native_menu_item: "", native_title: title }
231
343
  data[:native_href] = href if href
232
344
  data[:native_click] = click if click
233
345
  data[:native_icon] = resolved if resolved
234
346
  data[:native_selected] = "" if selected
347
+ data[:native_action] = RubyNative::Helper.validate_action(action) if action
235
348
  add(@context.tag.div(data: data))
236
349
  end
237
350
 
@@ -1,3 +1,3 @@
1
1
  module RubyNative
2
- VERSION = "0.11.0"
2
+ VERSION = "0.11.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Masilotti