Skip to content

Commit

Permalink
feat(core): added onStartTyping utility + docs
Browse files Browse the repository at this point in the history
  • Loading branch information
novaotp committed Jan 31, 2025
1 parent d8ce465 commit c01e985
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 2 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sv-use/core",
"version": "1.3.2",
"version": "1.4.0",
"license": "MIT",
"description": "A collection of Svelte 5 utilities.",
"main": "./dist/index.js",
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/__internal__/configurable.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { BROWSER } from 'esm-env';

export interface ConfigurableWindow {
/** */
window?: Window;
}

export interface ConfigurableDocument {
document?: Document;
}

export const defaultWindow = BROWSER ? window : undefined;
export const defaultDocument = BROWSER ? document : undefined;
1 change: 1 addition & 0 deletions packages/core/src/sensors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './get-network/index.svelte.js';
export * from './has-left-page/index.svelte.js';
export * from './on-click-outside/index.svelte.js';
export * from './on-long-press/index.svelte.js';
export * from './on-start-typing/index.svelte.js';
74 changes: 74 additions & 0 deletions packages/core/src/sensors/on-start-typing/index.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { handleEventListener } from '../../browser/index.js';
import { noop } from '../../__internal__/utils.js';
import { defaultDocument, type ConfigurableDocument } from '../../__internal__/configurable.js';
import type { CleanupFunction } from '../../__internal__/types.js';

interface OnStartTypingOptions extends ConfigurableDocument {
/**
* Whether to auto-cleanup the event listener or not.
*
* If set to `true`, it must run in the component initialization lifecycle.
* @default true
*/
autoCleanup?: boolean;
}

/**
* Fires when users start typing on non-editable elements.
* @param callback The callback for when users start typing on non-editable elements.
* @param options Additional options to customize the behavior.
* @see https://svelte-librarian.github.io/sv-use/docs/core/sensors/on-start-typing
*/
export function onStartTyping(
callback: (event: KeyboardEvent) => void,
options: OnStartTypingOptions = {}
): CleanupFunction {
const { autoCleanup = true, document = defaultDocument } = options;

let cleanup: CleanupFunction = noop;

if (document) {
cleanup = handleEventListener(document, 'keydown', onKeydown, { autoCleanup, passive: true });
}

function onKeydown(event: KeyboardEvent) {
if (!isFocusedElementEditable() && isTypedCharValid(event)) {
callback(event);
}
}

function isFocusedElementEditable() {
if (!document) return;

if (!document.activeElement) return false;
if (document.activeElement === document.body) return false;

// Assume <input> and <textarea> elements are editable.
switch (document.activeElement.tagName) {
case 'INPUT':
case 'TEXTAREA':
return true;
}

// Check if any other focused element id editable.
return document.activeElement.hasAttribute('contenteditable');
}

function isTypedCharValid({ keyCode, metaKey, ctrlKey, altKey }: KeyboardEvent) {
if (metaKey || ctrlKey || altKey) return false;

// 0...9
if (keyCode >= 48 && keyCode <= 57) return true;

// A...Z
if (keyCode >= 65 && keyCode <= 90) return true;

// a...z
if (keyCode >= 97 && keyCode <= 122) return true;

// All other keys.
return false;
}

return cleanup;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { onStartTyping } from '$sv-use/core';
let input = $state<HTMLInputElement>();
onStartTyping(() => {
if (input !== document.activeElement) {
input?.focus();
}
});
</script>

<div class="relative flex w-full flex-col gap-2">
<note class="text-sm">Type anything</note>
<input
bind:this={input}
type="text"
placeholder="Start typing to focus"
class="rounded-md border border-zinc-300 px-3 py-2 text-sm"
/>
<input
type="text"
placeholder="Start typing has no effect here"
class="rounded-md border border-zinc-300 px-3 py-2 text-sm"
/>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# onStartTyping

Fires when users start typing on non-editable elements.

## Usage

```svelte
<script>
import { onStartTyping } from '@sv-use/core';
let input = $state();
onStartTyping(() => {
if (input !== document.activeElement) {
input?.focus();
}
});
</script>
<input bind:this={input} type="text" />
```

0 comments on commit c01e985

Please sign in to comment.