@djangocfg/widget-chat 0.1.12 → 0.1.14

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/widget-chat",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "A transport-agnostic chat surface: a pure core, a store, primitives, a composer, and the assembled components a host mounts",
5
5
  "keywords": [
6
6
  "chat",
@@ -55,7 +55,7 @@
55
55
  "test:watch": "vitest"
56
56
  },
57
57
  "dependencies": {
58
- "@djangocfg/widget-kit": "^0.1.13",
58
+ "@djangocfg/widget-kit": "^0.1.15",
59
59
  "@djangocfg/widget-media": "^0.1.9",
60
60
  "@streamdown/code": "^1.1.1",
61
61
  "@streamdown/math": "^1.0.2",
@@ -66,19 +66,19 @@
66
66
  "use-stick-to-bottom": "^1.1.6"
67
67
  },
68
68
  "peerDependencies": {
69
- "@djangocfg/api": "^2.1.583",
70
- "@djangocfg/i18n": "^2.1.583",
71
- "@djangocfg/ui-core": "^2.1.583",
69
+ "@djangocfg/api": "^2.1.585",
70
+ "@djangocfg/i18n": "^2.1.585",
71
+ "@djangocfg/ui-core": "^2.1.585",
72
72
  "lucide-react": "^0.545.0",
73
73
  "react": "^19.0.0",
74
74
  "remark-gfm": "^4.0.0",
75
75
  "streamdown": "^2.6.0"
76
76
  },
77
77
  "devDependencies": {
78
- "@djangocfg/api": "^2.1.583",
79
- "@djangocfg/i18n": "^2.1.583",
80
- "@djangocfg/typescript-config": "^2.1.583",
81
- "@djangocfg/ui-core": "^2.1.583",
78
+ "@djangocfg/api": "^2.1.585",
79
+ "@djangocfg/i18n": "^2.1.585",
80
+ "@djangocfg/typescript-config": "^2.1.585",
81
+ "@djangocfg/ui-core": "^2.1.585",
82
82
  "@storybook/react-vite": "^10.5.0",
83
83
  "@types/node": "^25.9.5",
84
84
  "@types/react": "19.2.15",
@@ -8,6 +8,7 @@ import {
8
8
  type Ref,
9
9
  } from "react";
10
10
 
11
+ import { useTouchSafeStyle } from "@djangocfg/ui-core/hooks";
11
12
  import { FIELD_SIZING, useAutosize } from "./use-autosize";
12
13
  import { useComposer } from "../context";
13
14
 
@@ -89,6 +90,16 @@ export function ComposerInput({
89
90
  const { draft, setText, handlePaste, send, canSend } = useComposer();
90
91
  const ref = useRef<HTMLTextAreaElement | null>(null);
91
92
 
93
+ /**
94
+ * iOS WebKit auto-zoom prevention (iOS-only targeting).
95
+ *
96
+ * Returns max(16px, 1em) for iOS devices to prevent viewport zoom on focus.
97
+ * Android and desktop return undefined (keep optimal font-size).
98
+ *
99
+ * @see {@link @researches/2026-10-25__ios-webkit-auto-zoom}
100
+ */
101
+ const touchSafeFontSize = useTouchSafeStyle();
102
+
92
103
  /**
93
104
  * Fill the internal ref (autosize measures through it) and the host's, if
94
105
  * one was passed. A callback rather than handing the host `ref` directly:
@@ -195,14 +206,23 @@ export function ComposerInput({
195
206
  *
196
207
  * Empty on browsers without support, where the effect above sizes the box
197
208
  * instead. Never both — a written `height` would override this.
209
+ *
210
+ * iOS auto-zoom prevention: merge touchSafeFontSize if present (iOS-only).
211
+ * On Android/desktop, touchSafeFontSize is undefined and style remains unchanged.
198
212
  */
199
213
  // `fieldSizing` is newer than the pinned csstype, so the cast is the
200
214
  // property being real and the types being behind — not a shape being
201
215
  // forced. Remove it once csstype carries the property.
202
216
  style={
203
217
  FIELD_SIZING
204
- ? ({ fieldSizing: "content", maxHeight: `${maxRows}lh` } as CSSProperties)
205
- : undefined
218
+ ? ({
219
+ fieldSizing: "content",
220
+ maxHeight: `${maxRows}lh`,
221
+ ...(touchSafeFontSize && { fontSize: touchSafeFontSize }),
222
+ } as CSSProperties)
223
+ : touchSafeFontSize
224
+ ? ({ fontSize: touchSafeFontSize } as CSSProperties)
225
+ : undefined
206
226
  }
207
227
  onChange={onChange}
208
228
  onPaste={onPaste}
@@ -447,3 +447,44 @@
447
447
  transition: none;
448
448
  }
449
449
  }
450
+
451
+ /* ============================================================================
452
+ iOS WebKit Auto-Zoom Prevention (CSS Baseline)
453
+ ========================================================================= */
454
+
455
+ /**
456
+ * Prevents iOS Safari/Chrome from zooming viewport when textarea gets focus.
457
+ *
458
+ * **Problem**: iOS WebKit zooms when font-size < 16px, causing conversation
459
+ * to "fly away" and creating critical UX failure in chat interfaces.
460
+ *
461
+ * **Solution**: Multi-layer defense:
462
+ * 1. CSS baseline (works without JS) — @supports iOS detection + mobile media
463
+ * 2. React hook (useTouchSafeStyle) — precise iOS-only targeting via device detect
464
+ *
465
+ * **WCAG Compliance**: ✅ WCAG 2.1 AA (SC 1.4.4, 1.4.10)
466
+ * - No viewport restrictions (user-scalable=no would violate WCAG)
467
+ * - Text zoom still works via browser accessibility settings
468
+ * - Maintains 200% text zoom capability
469
+ *
470
+ * @see {@link @researches/2026-10-25__ios-webkit-auto-zoom} Research
471
+ * @see {@link @djangocfg/ui-core/hooks/device/useTouchSafeStyle} React hook
472
+ */
473
+
474
+ /* iOS-specific via @supports (CSS feature detection) */
475
+ @supports (-webkit-touch-callout: none) {
476
+ .composer-box textarea,
477
+ .composer-box input,
478
+ .composer-box select {
479
+ font-size: max(1rem, 16px);
480
+ }
481
+ }
482
+
483
+ /* Mobile baseline (all devices, fallback for browsers without @supports) */
484
+ @media (max-width: 768px) {
485
+ .composer-box textarea,
486
+ .composer-box input,
487
+ .composer-box select {
488
+ font-size: max(1rem, 16px);
489
+ }
490
+ }