livechat 0.3.2 → 0.3.4

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: 5c30e19e783526543e297b77114a6e1d823799c22aca38a2048fc4bb476feb3f
4
- data.tar.gz: 6746d78ed02867bd663d2d34c636f68530cc3b818678c1b4b5cd79cb70781eb6
3
+ metadata.gz: aa83a99e1d1ebe0259347b286862ec7cf7cfffbfe47ea4a624baff36b3ca066e
4
+ data.tar.gz: 0d017808858c6dfbce423e283b061476a74b3075b42bb70c3f10c54fb74b5622
5
5
  SHA512:
6
- metadata.gz: 651eedbae0aabd2570b1690cb3f06e961abadbe3922670ed254b4c108f2553341e6a27e43e35b565c61004dd7ee6df751b38d62b6bd996aa1c97f50f377b71ef
7
- data.tar.gz: ec6e305609121daf2182095c44bc2bccc098bee0a6d199ba54c46d3b8665c8de4c749eb6adfa34c5884992318701d157a8146a7155c7d9039a782456b7afd446
6
+ metadata.gz: 97dc8a43bfb5ab7fecd0dabdfaf5f126e3346b97e73f56aa96129331046e74355bbc201a7fc0ab010c1ef0e48a73d15ec7aaadac0dd185c4310480271486984c
7
+ data.tar.gz: e1469a54e206b39b539d6dacc767568caad7a44da3540d5ff3b746fb0affb74e83224f3ca9f0ea87163f2796656d66b274aaa58332e1cd40ddfda9b441b1028d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.4
4
+
5
+ - Docs: show how to wire `current_user` with Rails 8's built-in authentication
6
+ (`bin/rails generate authentication`), alongside the existing Devise/Warden
7
+ example — in the README and the generated initializer.
8
+
9
+ ## 0.3.3
10
+
11
+ - Accessibility: on phones (where the panel is a full-screen modal) focus is
12
+ trapped inside it and page scroll is locked behind it; closing restores
13
+ focus to whatever opened the chat. Errors and the email-saved note are now
14
+ announced to screen readers. The desktop popover deliberately stays
15
+ non-modal — you can read the page while chatting.
16
+
3
17
  ## 0.3.2
4
18
 
5
19
  - The chat panel is full-screen on phones (≤480px) — a conversation is an
data/README.md CHANGED
@@ -91,6 +91,20 @@ Livechat.configure do |config|
91
91
  end
92
92
  ```
93
93
 
94
+ `current_user` (and any admin gate) receives the raw request, so it works
95
+ with whatever auth you have:
96
+
97
+ ```ruby
98
+ # Devise / Warden:
99
+ config.current_user = ->(request) { request.env["warden"]&.user }
100
+
101
+ # Rails 8 built-in auth (bin/rails generate authentication):
102
+ config.current_user = lambda do |request|
103
+ token = request.cookies["session_token"]
104
+ Session.find_signed(token)&.user if token
105
+ end
106
+ ```
107
+
94
108
  ### Brand color
95
109
 
96
110
  ```ruby
@@ -16,7 +16,15 @@ Livechat.configure do |config|
16
16
  # Resolve the current user (optional). Return an object responding to #id,
17
17
  # or nil. Signed-in visitors keep one conversation across devices; guests
18
18
  # are tracked with a cookie. The same user is the agent when replying.
19
+ #
20
+ # Devise / Warden:
19
21
  # config.current_user = ->(request) { request.env["warden"]&.user }
22
+ #
23
+ # Rails 8 built-in auth (bin/rails generate authentication):
24
+ # config.current_user = lambda do |request|
25
+ # token = request.cookies["session_token"]
26
+ # Session.find_signed(token)&.user if token
27
+ # end
20
28
 
21
29
  # How visitors appear in the inbox.
22
30
  # config.visitor_label = ->(user) { user.name.presence || user.email }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Livechat
4
- VERSION = '0.3.2'
4
+ VERSION = '0.3.4'
5
5
  end
@@ -33,6 +33,11 @@
33
33
  var emailRowEl = null;
34
34
  var errorEl = null;
35
35
  var isOpen = false;
36
+ var lastFocused = null;
37
+ // On phones the panel is a full-screen modal (focus trapped, page scroll
38
+ // locked); on desktop it stays a non-modal popover you can chat alongside.
39
+ var mobileModal = window.matchMedia ? window.matchMedia("(max-width: 480px)") : null;
40
+ var savedOverflow = null;
36
41
  // The id of the newest message actually RENDERED into the list — never
37
42
  // advanced by background polls, so reopening the panel refetches exactly
38
43
  // the messages it hasn't shown yet.
@@ -112,6 +117,9 @@
112
117
  isOpen = false;
113
118
  lastRenderedId = 0;
114
119
  lastAuthorKey = null;
120
+ // A Turbo body swap removes the panel without closePanel() running; the
121
+ // documentElement (and any scroll lock on it) survives the swap.
122
+ unlockScroll();
115
123
 
116
124
  root = document.createElement("div");
117
125
  root.id = "lvc-root";
@@ -173,6 +181,7 @@
173
181
  panel.id = "lvc-panel";
174
182
  panel.setAttribute("role", "dialog");
175
183
  panel.setAttribute("aria-label", config.appName);
184
+ panel.addEventListener("keydown", trapFocus);
176
185
 
177
186
  var header = document.createElement("div");
178
187
  header.id = "lvc-header";
@@ -206,6 +215,9 @@
206
215
 
207
216
  errorEl = document.createElement("div");
208
217
  errorEl.id = "lvc-error";
218
+ // The alert region exists before any text lands in it, so screen
219
+ // readers announce the message instead of a silent visual update.
220
+ errorEl.setAttribute("role", "alert");
209
221
  errorEl.hidden = true;
210
222
  panel.appendChild(errorEl);
211
223
 
@@ -252,6 +264,7 @@
252
264
  function buildEmailRow() {
253
265
  var row = document.createElement("form");
254
266
  row.id = "lvc-email";
267
+ row.setAttribute("aria-live", "polite"); // "we'll also reply by email" is a status
255
268
  row.hidden = true;
256
269
 
257
270
  var label = document.createElement("span");
@@ -297,6 +310,7 @@
297
310
  mount();
298
311
  if (!isOpen) {
299
312
  isOpen = true;
313
+ lastFocused = document.activeElement;
300
314
  sessionSet("livechat_open", "1");
301
315
 
302
316
  // The panel is built once and then hidden/shown — closing must never
@@ -307,6 +321,8 @@
307
321
  root.appendChild(panel);
308
322
  }
309
323
  panel.hidden = false;
324
+ panel.setAttribute("aria-modal", isMobileModal() ? "true" : "false");
325
+ if (isMobileModal()) lockScroll();
310
326
  setUnread(0);
311
327
  poll();
312
328
  schedulePoll();
@@ -325,9 +341,45 @@
325
341
  sessionSet("livechat_open", "");
326
342
  var panel = document.getElementById("lvc-panel");
327
343
  if (panel) panel.hidden = true;
344
+ unlockScroll();
345
+ if (lastFocused && document.contains(lastFocused)) lastFocused.focus();
328
346
  schedulePoll();
329
347
  }
330
348
 
349
+ function isMobileModal() {
350
+ return !!(mobileModal && mobileModal.matches);
351
+ }
352
+
353
+ // Tab cycles inside the panel only while it is the full-screen mobile
354
+ // modal; the desktop popover deliberately lets focus reach the page.
355
+ function trapFocus(event) {
356
+ if (event.key !== "Tab" || !isMobileModal()) return;
357
+ var panel = document.getElementById("lvc-panel");
358
+ var focusable = panel.querySelectorAll("button, [href], input, textarea, select");
359
+ if (!focusable.length) return;
360
+ var first = focusable[0];
361
+ var last = focusable[focusable.length - 1];
362
+ if (event.shiftKey && document.activeElement === first) {
363
+ event.preventDefault();
364
+ last.focus();
365
+ } else if (!event.shiftKey && document.activeElement === last) {
366
+ event.preventDefault();
367
+ first.focus();
368
+ }
369
+ }
370
+
371
+ function lockScroll() {
372
+ if (savedOverflow !== null) return;
373
+ savedOverflow = document.documentElement.style.overflow;
374
+ document.documentElement.style.overflow = "hidden";
375
+ }
376
+
377
+ function unlockScroll() {
378
+ if (savedOverflow === null) return;
379
+ document.documentElement.style.overflow = savedOverflow;
380
+ savedOverflow = null;
381
+ }
382
+
331
383
  // --- polling ----------------------------------------------------------------
332
384
 
333
385
  function schedulePoll() {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: livechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov