@joewinke/jatui 0.2.31 → 0.2.33

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.
@@ -84,6 +84,16 @@ export function cycle(list, current, dir) {
84
84
  const BUMP_DURATION_MS = 280;
85
85
  const RAIL_FOCUS_CLASS = 'rail-focus';
86
86
  const RAIL_CYCLED_CLASS = 'rail-cycled';
87
+ /** `<input>` types whose vertical arrows natively step the value (spinner / picker). */
88
+ const STEPPER_INPUT_TYPES = new Set([
89
+ 'number',
90
+ 'range',
91
+ 'date',
92
+ 'time',
93
+ 'month',
94
+ 'week',
95
+ 'datetime-local',
96
+ ]);
87
97
  function resolveOrder(opts) {
88
98
  return typeof opts.order === 'function' ? opts.order() : opts.order;
89
99
  }
@@ -213,6 +223,13 @@ function makeController(optsRef) {
213
223
  const isTextField = !!el &&
214
224
  (tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable === true);
215
225
  const isMultiline = !!el && (tag === 'TEXTAREA' || el.isContentEditable === true);
226
+ const inputType = tag === 'INPUT' ? (el.type || '').toLowerCase() : '';
227
+ // A <select> cycles its value with arrows on BOTH axes, and a radio moves
228
+ // within its `name` group on both axes — the rail must yield entirely or it
229
+ // silently drives an unrelated rung while the user thinks they're picking a value.
230
+ const ownsBothArrowAxes = tag === 'SELECT' || inputType === 'radio';
231
+ const blocksVerticalArrows = isMultiline || ownsBothArrowAxes || STEPPER_INPUT_TYPES.has(inputType);
232
+ const blocksHorizontalArrows = isTextField || ownsBothArrowAxes;
216
233
  const hasMod = event.metaKey || event.ctrlKey || event.altKey;
217
234
  // Modal trap — stop non-text keystrokes from bubbling to window-level page
218
235
  // listeners (e.g., a listNav attached to `window`). Text-field keystrokes
@@ -245,13 +262,13 @@ function makeController(optsRef) {
245
262
  const popoverOpen = opts.isPopoverOpen?.() ?? false;
246
263
  if (!hasMod && !popoverOpen) {
247
264
  // Vertical: ArrowDown / j step down; ArrowUp / k step up.
248
- // j / k are blocked in ANY text field; arrows are blocked only inside
249
- // multiline fields so the caret can still move line-by-line.
265
+ // j / k are blocked in ANY text field (they'd type a letter); arrows are
266
+ // blocked wherever the control owns them natively.
250
267
  const goDown = event.key === 'ArrowDown' || event.key === 'j';
251
268
  const goUp = event.key === 'ArrowUp' || event.key === 'k';
252
269
  if (goDown || goUp) {
253
270
  const isVimKey = event.key === 'j' || event.key === 'k';
254
- const blocked = isVimKey ? isTextField : isMultiline;
271
+ const blocked = isVimKey ? isTextField : blocksVerticalArrows;
255
272
  if (!blocked) {
256
273
  event.preventDefault();
257
274
  moveRung(goDown ? 1 : -1);
@@ -261,7 +278,7 @@ function makeController(optsRef) {
261
278
  // Horizontal: ArrowLeft / ArrowRight dispatch to `onAction`.
262
279
  // Skipped inside text fields so caret-left / caret-right keep working.
263
280
  if ((event.key === 'ArrowLeft' || event.key === 'ArrowRight') &&
264
- !isTextField) {
281
+ !blocksHorizontalArrows) {
265
282
  event.preventDefault();
266
283
  const attr = resolveAttr(opts);
267
284
  // Prefer the element that actually has DOM focus, then fall back to
@@ -263,6 +263,12 @@
263
263
  padding: 0.4rem 0.25rem;
264
264
  }
265
265
 
266
+ /* Unstyled otherwise, this inherits the host page's text color instead of
267
+ one legible against the panel's own hardcoded dark background. */
268
+ .kso-description {
269
+ color: oklch(0.85 0.02 250);
270
+ }
271
+
266
272
  .kso-section-title {
267
273
  font-size: 0.7rem;
268
274
  text-transform: uppercase;
@@ -122,6 +122,7 @@
122
122
  captionsSrc = null,
123
123
  captionsLabel = 'Captions',
124
124
  captionsLang = 'en',
125
+ captionsDefault = true,
125
126
  chapters = [],
126
127
  speakers = [],
127
128
  transcript = [],
@@ -159,6 +160,21 @@
159
160
  captionsSrc?: string | null;
160
161
  captionsLabel?: string;
161
162
  captionsLang?: string;
163
+ /**
164
+ * Should the captions <track> start ENABLED (`default` attribute)?
165
+ *
166
+ * `true` (default) preserves the historical behaviour: a track with a src
167
+ * shows on load. Pass `false` when the video already has captions burned
168
+ * into its frames (e.g. an academy episode render) — the track is still
169
+ * present and toggleable from the native menu, but not painted on top of
170
+ * the burned-in text, which would double every caption.
171
+ *
172
+ * Long-term direction (see the double-caption bug, jat-abwim): renders
173
+ * should STOP burning captions into the frames and rely on this
174
+ * toggleable VTT track alone. Until episodes are re-rendered without
175
+ * burned-in text, a burned-in episode passes `captionsDefault={false}`.
176
+ */
177
+ captionsDefault?: boolean;
162
178
  chapters?: VideoChapter[];
163
179
  speakers?: VideoSpeakerTurn[];
164
180
  transcript?: VideoTranscriptSegment[];
@@ -1052,8 +1068,12 @@
1052
1068
  class="vp-video"
1053
1069
  >
1054
1070
  <!-- A captions track is always present for a11y; without a src it is
1055
- an empty, inert track rather than a missing affordance. -->
1056
- <track kind="captions" src={captionsSrc ?? undefined} srclang={captionsLang} label={captionsLabel} default={!!captionsSrc} />
1071
+ an empty, inert track rather than a missing affordance. It starts
1072
+ ENABLED only when there's a src AND the host opts in via
1073
+ captionsDefault (true by default). A burned-in-captions video
1074
+ passes captionsDefault={false} so the track stays available in the
1075
+ menu but isn't painted on top of the frames' own text (jat-abwim). -->
1076
+ <track kind="captions" src={captionsSrc ?? undefined} srclang={captionsLang} label={captionsLabel} default={!!captionsSrc && captionsDefault} />
1057
1077
  </video>
1058
1078
  <div class="vp-osd" class:on={osdOn} aria-hidden="true">{osd}</div>
1059
1079
  </div>
@@ -68,6 +68,21 @@ type $$ComponentProps = {
68
68
  captionsSrc?: string | null;
69
69
  captionsLabel?: string;
70
70
  captionsLang?: string;
71
+ /**
72
+ * Should the captions <track> start ENABLED (`default` attribute)?
73
+ *
74
+ * `true` (default) preserves the historical behaviour: a track with a src
75
+ * shows on load. Pass `false` when the video already has captions burned
76
+ * into its frames (e.g. an academy episode render) — the track is still
77
+ * present and toggleable from the native menu, but not painted on top of
78
+ * the burned-in text, which would double every caption.
79
+ *
80
+ * Long-term direction (see the double-caption bug, jat-abwim): renders
81
+ * should STOP burning captions into the frames and rely on this
82
+ * toggleable VTT track alone. Until episodes are re-rendered without
83
+ * burned-in text, a burned-in episode passes `captionsDefault={false}`.
84
+ */
85
+ captionsDefault?: boolean;
71
86
  chapters?: VideoChapter[];
72
87
  speakers?: VideoSpeakerTurn[];
73
88
  transcript?: VideoTranscriptSegment[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",