svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Binding focus state to elements

Open Maximinodotpy opened this issue 1 year ago • 15 comments

Describe the problem

I hope i'm not missing something but it would be cool if we were able to bin variables to focusable elements abd find out this way if there focused or not, furthermore if we set this bound variable to true it should focus this element and blur in case its turned to false.

Describe the proposed solution

A property like this bind:focused

Alternatives considered

Using the focus and blur functions for focusable elements and the activeElement property of the document.

Importance

nice to have

Maximinodotpy avatar Jul 10 '23 08:07 Maximinodotpy

It would be convenient, but you already can get pretty close using actions.

Something along the lines of:

export function focused(node, store) {
    const onChange = () => store.set(document.activeElement == node);
    node.addEventListener('focus', onChange);
    node.addEventListener('blur', onChange);

    const cancel = store.subscribe(shouldFocus => {
        const focused = document.activeElement == node;
        if (focused && shouldFocus == false)
            node.blur();
        else if (focused == false && shouldFocus)
            node.focus();
    });

    return {
        destroy() {
            node.removeEventListener('focus', onChange);
            node.removeEventListener('blur', onChange);
            cancel();
        }
    }
}

Usage:

<script>
  import { writable } from 'svelte/store';
  import { focused } from './actions.js';

  const inputFocused = writable(false);
</script>

<input use:focused={inputFocused} />

REPL

Of course that is more verbose as it requires imports and stores.


For convenience the action and store could be combined into one object, e.g.

import { writable } from 'svelte/store';

export function createFocused(initialValue) {
  const store = writable(initialValue);

  function action(node) {
    // [action as above]
  }

  Object.assign(action, store);
  return action;
}
<script>
  import { createFocused } from './actions.js';
  const focused = createFocused(false);
</script>

<input use:focused />
Focused: {$focused}

REPL

brunnerh avatar Jul 10 '23 16:07 brunnerh

Ah youre right it could also be done this way but would it also be possible without a store?

Maximinodotpy avatar Jul 11 '23 06:07 Maximinodotpy

I do not think so. You need some way to bridge reactivity between component and non-component code.

brunnerh avatar Jul 11 '23 07:07 brunnerh

I'm not sure but I imagine that normal let variables internally somewhat behave like stores and other dom things like the width are also possible so I think it would be easiest to make this a built in feature, of course dont take me too seriously because I have no idea of the source code.

Maximinodotpy avatar Jul 11 '23 08:07 Maximinodotpy

Without store you can use callback in this case


export function focused(node, onFocused) {
       const onChange = () => onFocused(document.activeElement == node);
       node.addEventListener('focus', onChange);
       node.addEventListener('blur', onChange);
       ...
}

function onFocused(isFocused: boolean) {

}

<input use:focused={{onFocused}} />

StagnantIce avatar Jul 29 '23 13:07 StagnantIce

Hey @Maximinodotpy there is a onFocus and onBlur exists, if that still doesn't full fill your need, @Rich-Harris I would like to work on this part of the feature.

RaiVaibhav avatar Apr 13 '24 20:04 RaiVaibhav

Why do we need this? You can just check if something is focused by using document.activeElement.

trueadm avatar Apr 18 '24 10:04 trueadm

Kindly close the PR if it not needed

RaiVaibhav avatar Apr 18 '24 10:04 RaiVaibhav

I'd say the main reason to have this would be the write access: You can focus the element declaratively without using bind:this first or using an action.

brunnerh avatar Apr 18 '24 11:04 brunnerh

Write access is tricky with focus. If you try to bind focus to two elements by accident, you cause so many issues with accessibility. It's much better to handle focus management with a global state machine instead of on a component-by-component basis. It gets even more complex too when working with elements that can have focus but not be in scroll view – so managing scroll + focus kind of flow together.

trueadm avatar Apr 18 '24 12:04 trueadm

Few things I came accross while writing a PR, if a single variable bind to two different element, binding multiple Focus on multiple elements, multiple issues related to accessibility like screen reader, scrolling behaviour.

PR cover above scenarios but again focus itself have option like preventScroll, so I am not sure, letting he user bind the focus is a good idea, because if they need something after component mount, they can use, autofocus?

RaiVaibhav avatar Apr 18 '24 12:04 RaiVaibhav

Sorry, I don't think this is something we want to invest our time into now. Like I said, focus is complicated and easy to mess up and hard to debug as to why.

trueadm avatar Apr 18 '24 12:04 trueadm

Actually, I think this would be best if it was read-only, rather than writable. Then I can see a way forward for the PR too :)

trueadm avatar Apr 18 '24 12:04 trueadm

Let me know if I can work on this @trueadm, I would be happy to open a new PR

RaiVaibhav avatar Apr 18 '24 12:04 RaiVaibhav

@RaiVaibhav Sure, go for it :)

trueadm avatar Apr 18 '24 12:04 trueadm