fix: (regression 0.49.0) losing focus after selecting an item

Massimo Melina committed Apr 2, 2024 at 20:11 UTC a219294e0bd10d1e91541b7af95d1fabcf02321a
2 files changed +45 -53
frontend/src/BrowseFiles.ts
+37 -47
@@ -32,9 +32,7 @@ export function BrowseFiles() {
32 ),
33 }), [props])
34 if (!useAuthorized())
35 - return h(CustomCode, { name: 'unauthorized',
36 - ifEmpty: () => h('h1', { className: 'unauthorized' }, t`Unauthorized`)
37 - })
35 + return h(CustomCode, { name: 'unauthorized' }, h('h1', { className: 'unauthorized' }, t`Unauthorized`) )
36 return h('div', propsDropFiles, // element dedicated to drop-files to cover full screen
37 h('div', {
38 className: 'list-wrapper ' + (tile_size ? 'tiles-mode' : 'list-mode'),
@@ -209,44 +207,40 @@ const Entry = ({ entry, midnight, separator }: EntryProps) => {
207 const ariaId = useId()
208 const ariaProps = { id: ariaId, 'aria-label': prefix(name + ', ', isFolder ? t`Folder` : entry.web ? t`Web page` : isLink ? t`Link` : '') }
209 return h('li', { className, label: separator },
212 - h(CustomCode, {
213 - name: 'entry',
214 - props: { entry },
215 - ifEmpty: () => h(Fragment, {},
216 - showFilter && h(Checkbox, {
217 - disabled: isLink,
218 - 'aria-labelledby': ariaId,
219 - value: selected[uri],
220 - onChange(v) {
221 - if (v)
222 - return state.selected[uri] = true
223 - delete state.selected[uri]
224 - },
225 - }),
226 - h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
227 - ...isFolder || entry.web ? [ // internal navigation, use Link component
228 - h(Link, { to: uri, reloadDocument: entry.web, ...ariaProps }, // without reloadDocument, once you enter the web page, the back button won't bring you back to the frontend
229 - ico, entry.n.slice(0, -1)), // don't use name, as we want to include whole path in case of search
230 - // popup button is here to be able to detect link-wrapper:hover
231 - file_menu_on_link && !showingButton && h('button', {
232 - className: 'popup-menu-button',
233 - onClick: fileMenu
234 - }, hIcon('menu'), t`Menu`)
235 - ] : containerName ? [
236 - h('a', { href: uri, onClick, tabIndex: -1, 'aria-hidden': true }, ico),
237 - h(Link, { to: containerDir, className: 'container-folder', tabIndex: -1 }, containerName),
238 - h('a', { href: uri, onClick, ...ariaProps }, name)
239 - ] : [h('a', { href: uri, onClick, ...ariaProps }, ico, name)],
240 - ),
241 - h(CustomCode, { name: 'afterEntryName', props: { entry } }),
242 - entry.comment && h('div', { className: 'entry-comment' }, entry.comment),
243 - h('div', { className: 'entry-panel' },
244 - h(EntryDetails, { entry, midnight }),
245 - showingButton && iconBtn('menu', fileMenu, { className: 'file-menu-button' }),
246 - ),
247 - h('div'),
248 - )
249 - }),
210 + h(CustomCode, { name: 'entry', entry },
211 + showFilter && h(Checkbox, {
212 + disabled: isLink,
213 + 'aria-labelledby': ariaId,
214 + value: selected[uri],
215 + onChange(v) {
216 + if (v)
217 + return state.selected[uri] = true
218 + delete state.selected[uri]
219 + },
220 + }),
221 + h('span', { className: 'link-wrapper' }, // container to handle mouse over for both children
222 + ...isFolder || entry.web ? [ // internal navigation, use Link component
223 + h(Link, { to: uri, reloadDocument: entry.web, ...ariaProps }, // without reloadDocument, once you enter the web page, the back button won't bring you back to the frontend
224 + ico, entry.n.slice(0, -1)), // don't use name, as we want to include whole path in case of search
225 + // popup button is here to be able to detect link-wrapper:hover
226 + file_menu_on_link && !showingButton && h('button', {
227 + className: 'popup-menu-button',
228 + onClick: fileMenu
229 + }, hIcon('menu'), t`Menu`)
230 + ] : containerName ? [
231 + h('a', { href: uri, onClick, tabIndex: -1, 'aria-hidden': true }, ico),
232 + h(Link, { to: containerDir, className: 'container-folder', tabIndex: -1 }, containerName),
233 + h('a', { href: uri, onClick, ...ariaProps }, name)
234 + ] : [h('a', { href: uri, onClick, ...ariaProps }, ico, name)],
235 + ),
236 + h(CustomCode, { name: 'afterEntryName', entry }),
237 + entry.comment && h('div', { className: 'entry-comment' }, entry.comment),
238 + h('div', { className: 'entry-panel' },
239 + h(EntryDetails, { entry, midnight }),
240 + showingButton && iconBtn('menu', fileMenu, { className: 'file-menu-button' }),
241 + ),
242 + h('div'),
243 + ),
244 )
245
246 function fileMenu(ev: MouseEvent) {
@@ -266,11 +260,7 @@ const Entry = ({ entry, midnight, separator }: EntryProps) => {
260 }
261
262 export function getEntryIcon(entry: DirEntry) {
269 - return h(CustomCode, {
270 - name: 'entryIcon',
271 - props: { entry },
272 - ifEmpty: () => entry.getDefaultIcon()
273 - })
263 + return h(CustomCode, { name: 'entryIcon', entry }, entry.getDefaultIcon())
264 }
265
266 export const EntryDetails = memo(({ entry, midnight }: { entry: DirEntry, midnight: Date }) => {
@@ -280,7 +270,7 @@ export const EntryDetails = memo(({ entry, midnight }: { entry: DirEntry, midnig
270 const {t} = useI18N()
271 const dd = '2-digit'
272 return h('div', { className: 'entry-details' },
283 - h(CustomCode, { name: 'additionalEntryDetails', props: { entry } }),
273 + h(CustomCode, { name: 'additionalEntryDetails', entry }),
274 entry.p?.match(entry.isFolder ? /l/i : /r/i) && hIcon('password', { className: 'miss-perm', title: t(MISSING_PERM) }),
275 h(EntrySize, { s }),
276 time && h('span', {
frontend/src/components.ts
+8 -6
@@ -1,7 +1,7 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { getHFS, hfsEvent, hIcon, Html, isPrimitive, onlyTruthy, prefix } from './misc'
4 -import { ButtonHTMLAttributes, ChangeEvent, createElement as h, CSSProperties, FC, forwardRef, Fragment,
4 +import { ButtonHTMLAttributes, ChangeEvent, createElement as h, CSSProperties, forwardRef, Fragment,
5 HTMLAttributes, InputHTMLAttributes, isValidElement, MouseEventHandler, ReactNode, SelectHTMLAttributes,
6 useMemo, useState, ComponentPropsWithoutRef } from 'react'
7 import _ from 'lodash'
@@ -66,18 +66,20 @@ export function Select<T extends string>({ onChange, value, options, ...props }:
66 }, options.map(({ value, label }) => h('option', { key: value, value }, label)))
67 }
68
69 -export function CustomCode({ name, props, ifEmpty }: { name: string, props?: any, ifEmpty?: FC }) {
70 - const children = useMemo(() => {
69 +export function CustomCode({ name, children, ...props }: { name: string, children?: ReactNode } & any) {
70 + const result = useMemo(() => {
71 + props.def = children // not using 'default' because user can have unexpected error destructuring object
72 const ret = onlyTruthy(hfsEvent(name, props)
73 .map((x, key) => isValidElement(x) ? h(Fragment, { key }, x)
74 : x === 0 || x && isPrimitive(x) ? h(Html, { key, code: String(x) })
74 - : null))
75 + : _.isArray(x) ? h(Fragment, { key }, ...x)
76 + : null))
77 const html = getHFS().customHtml?.[name]
78 if (html?.trim?.())
79 ret.push(h(Html, { key: 'x', code: html }))
80 return ret
79 - }, [name, ...props ? Object.values(props) : []])
80 - return children.length || !ifEmpty ? h(Fragment, {}, children) : h(ifEmpty)
81 + }, [name, children, ...props ? Object.values(props) : []])
82 + return result.length || !children ? h(Fragment, {}, result) : children
83 }
84
85 interface IconBtnOptions extends ButtonHTMLAttributes<any> { style?: any, title?: string }