@kolkrabbi/kol-component 0.97.1 → 0.97.3

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": "@kolkrabbi/kol-component",
3
- "version": "0.97.1",
3
+ "version": "0.97.3",
4
4
  "description": "KOL design-system components — atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,6 +35,7 @@ import { Icon } from '@kolkrabbi/kol-icons'
35
35
  * @param {Function} onPrefix (prefix) => void
36
36
  * @param {{files: object[], index: number}|null} quickLook the open Quick Look, or null
37
37
  * @param {Function} onQuickLook (quickLook|null) => void — space opens / closes, ↑/↓ step while open
38
+ * @param {Function} onPick (file|null) => void — the picked file whenever it changes (a folder pick or an outside prefix change → null)
38
39
  * @param {Function} urlOf (o) => string — the object's public URL for the preview image
39
40
  * @param {Function} kindOf (o) => 'image' | 'video' | 'audio' | string
40
41
  * @param {Object} kindLabel kind → label shown when there is no visual preview
@@ -141,6 +142,7 @@ export default function ColumnBrowser({
141
142
  onPrefix = () => {},
142
143
  quickLook = null,
143
144
  onQuickLook,
145
+ onPick,
144
146
  urlOf,
145
147
  kindOf = defaultKindOf,
146
148
  kindLabel = DEFAULT_KIND_LABEL,
@@ -149,14 +151,11 @@ export default function ColumnBrowser({
149
151
  className = '',
150
152
  }) {
151
153
  const [picked, setPicked] = useState(null)
152
- // Keyboard cursor: which column, which row. Clicks keep it in sync.
153
- const [cursor, setCursor] = useState({ col: 0, idx: 0 })
154
- /* the cursor is NOT drawn until the keyboard is used (ColumnBrowserCursorStart,
155
- * kol-r2b2 2026-08-27): at rest it sat on row 0 beside the open folder and
156
- * read as a second selection. Arrows arm it; a click seeds it. */
157
- const [cursorActive, setCursorActive] = useState(false)
158
- const rootRef = useRef(null)
159
-
154
+ /* `onPick` (ColumnBrowserOnPick, kol-r2b2 2026-08-27): the picked file, for
155
+ * a Finder-style breadcrumb fired whenever it changes; a folder pick or an
156
+ * outside prefix change fires null */
157
+ const pick = (o) => { setPicked(o); onPick?.(o) }
158
+ useEffect(() => { if (picked) pick(null) }, [prefix]) // eslint-disable-line react-hooks/exhaustive-deps
160
159
  const levelsOf = (pfx) => {
161
160
  const out = ['']
162
161
  if (pfx) {
@@ -169,10 +168,31 @@ export default function ColumnBrowser({
169
168
  const { folders, files } = partition(objects.filter((o) => o.key.startsWith(level)), level)
170
169
  return [...folders.map((f) => ({ type: 'folder', name: f })), ...files.map((o) => ({ type: 'file', o }))]
171
170
  }
171
+ // Keyboard cursor: which column, which row. Clicks keep it in sync.
172
+ /* SEEDED on the open folder of the deepest column that has one (Finder's
173
+ * start — ColumnBrowserCursorSeed, kol-r2b2 2026-08-27): with a deep `prefix`
174
+ * the first ↓ used to act in column 0. Re-seeded when `prefix` changes; the
175
+ * internal moves land on the same spot, so nothing jumps. */
176
+ const seedCursor = (pfx) => {
177
+ const lv = levelsOf(pfx)
178
+ if (lv.length < 2) return { col: 0, idx: 0 }
179
+ const col = lv.length - 2
180
+ const opened = lv[col + 1].slice(lv[col].length)
181
+ const idx = itemsAt(lv[col]).findIndex((it) => it.type === 'folder' && it.name === opened)
182
+ return { col, idx: Math.max(0, idx) }
183
+ }
184
+ const [cursor, setCursor] = useState(() => seedCursor(prefix))
185
+ useEffect(() => { setCursor(seedCursor(prefix)) }, [prefix, objects.length]) // eslint-disable-line react-hooks/exhaustive-deps
186
+ /* the cursor is NOT drawn until the keyboard is used (ColumnBrowserCursorStart,
187
+ * kol-r2b2 2026-08-27): at rest it sat on row 0 beside the open folder and
188
+ * read as a second selection. Arrows arm it; a click seeds it. */
189
+ const [cursorActive, setCursorActive] = useState(false)
190
+ const rootRef = useRef(null)
191
+
172
192
  const land = (level, item) => {
173
193
  if (!item) return
174
- if (item.type === 'folder') { setPicked(null); onPrefix(level + item.name) }
175
- else setPicked(item.o)
194
+ if (item.type === 'folder') { pick(null); onPrefix(level + item.name) }
195
+ else pick(item.o)
176
196
  }
177
197
 
178
198
  const onKeyDown = (e) => {
@@ -191,6 +211,9 @@ export default function ColumnBrowser({
191
211
  const items = itemsAt(lv[col])
192
212
  if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
193
213
  const idx = Math.max(0, Math.min(items.length - 1, cursor.idx + (e.key === 'ArrowDown' ? 1 : -1)))
214
+ /* an arrow that did not move the cursor does nothing — re-landing on an
215
+ * open folder re-fired onPrefix (ColumnBrowserCursorSeed) */
216
+ if (idx === cursor.idx && col === cursor.col) return
194
217
  setCursor({ col, idx })
195
218
  land(lv[col], items[idx])
196
219
  } else if (e.key === 'ArrowRight') {
@@ -206,7 +229,7 @@ export default function ColumnBrowser({
206
229
  const opened = lv[col].slice(lv[col - 1].length)
207
230
  const idx = Math.max(0, parentItems.findIndex((it) => it.type === 'folder' && it.name === opened))
208
231
  setCursor({ col: col - 1, idx })
209
- setPicked(null)
232
+ pick(null)
210
233
  onPrefix(lv[col])
211
234
  }
212
235
  }
@@ -228,7 +251,7 @@ export default function ColumnBrowser({
228
251
  do { idx += step } while (items[idx] && items[idx].type !== 'file')
229
252
  if (!items[idx]) return
230
253
  setCursor({ col, idx })
231
- setPicked(items[idx].o)
254
+ pick(items[idx].o)
232
255
  const siblings = items.filter((it) => it.type === 'file').map((it) => it.o)
233
256
  onQuickLook?.({ files: siblings, index: siblings.findIndex((o) => o.key === items[idx].o.key) })
234
257
  }
@@ -267,7 +290,7 @@ export default function ColumnBrowser({
267
290
  active={f === activeFolder}
268
291
  cursor={cursorActive && cursor.col === k && cursor.idx === i}
269
292
  trailing={<Icon name="chevron-right" size={12} className="text-fg-32" />}
270
- onClick={() => { setCursor({ col: k, idx: i }); setCursorActive(true); rootRef.current?.focus(); setPicked(null); onPrefix(level + f) }}
293
+ onClick={() => { setCursor({ col: k, idx: i }); setCursorActive(true); rootRef.current?.focus(); pick(null); onPrefix(level + f) }}
271
294
  />
272
295
  ))}
273
296
  {files.map((o, i) => (
@@ -278,7 +301,7 @@ export default function ColumnBrowser({
278
301
  muted={shown?.key !== o.key}
279
302
  active={shown?.key === o.key}
280
303
  cursor={cursorActive && cursor.col === k && cursor.idx === folders.length + i}
281
- onClick={() => { setCursor({ col: k, idx: folders.length + i }); setCursorActive(true); rootRef.current?.focus(); setPicked(o) }}
304
+ onClick={() => { setCursor({ col: k, idx: folders.length + i }); setCursorActive(true); rootRef.current?.focus(); pick(o) }}
282
305
  />
283
306
  ))}
284
307
  {folders.length === 0 && files.length === 0 && (