cjm-fasthtml-token-selector 0.0.1__py3-none-any.whl

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.
Files changed (31) hide show
  1. cjm_fasthtml_token_selector/__init__.py +1 -0
  2. cjm_fasthtml_token_selector/_modidx.py +104 -0
  3. cjm_fasthtml_token_selector/components/__init__.py +0 -0
  4. cjm_fasthtml_token_selector/components/inputs.py +46 -0
  5. cjm_fasthtml_token_selector/components/tokens.py +217 -0
  6. cjm_fasthtml_token_selector/core/__init__.py +0 -0
  7. cjm_fasthtml_token_selector/core/config.py +57 -0
  8. cjm_fasthtml_token_selector/core/constants.py +50 -0
  9. cjm_fasthtml_token_selector/core/html_ids.py +51 -0
  10. cjm_fasthtml_token_selector/core/models.py +37 -0
  11. cjm_fasthtml_token_selector/helpers/__init__.py +0 -0
  12. cjm_fasthtml_token_selector/helpers/tokenizer.py +76 -0
  13. cjm_fasthtml_token_selector/js/__init__.py +0 -0
  14. cjm_fasthtml_token_selector/js/core.py +181 -0
  15. cjm_fasthtml_token_selector/js/display.py +146 -0
  16. cjm_fasthtml_token_selector/js/navigation.py +228 -0
  17. cjm_fasthtml_token_selector/js/repeat.py +119 -0
  18. cjm_fasthtml_token_selector/keyboard/__init__.py +0 -0
  19. cjm_fasthtml_token_selector/keyboard/actions.py +157 -0
  20. cjm_fasthtml_token_selector-0.0.1.dist-info/METADATA +760 -0
  21. cjm_fasthtml_token_selector-0.0.1.dist-info/RECORD +31 -0
  22. cjm_fasthtml_token_selector-0.0.1.dist-info/WHEEL +5 -0
  23. cjm_fasthtml_token_selector-0.0.1.dist-info/entry_points.txt +2 -0
  24. cjm_fasthtml_token_selector-0.0.1.dist-info/licenses/LICENSE +201 -0
  25. cjm_fasthtml_token_selector-0.0.1.dist-info/top_level.txt +2 -0
  26. demos/__init__.py +0 -0
  27. demos/data.py +17 -0
  28. demos/gap.py +93 -0
  29. demos/shared.py +148 -0
  30. demos/span.py +92 -0
  31. demos/word.py +88 -0
@@ -0,0 +1,119 @@
1
+ """Custom key repeat engine with configurable initial delay, repeat interval, and throttle floor."""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/js/repeat.ipynb.
4
+
5
+ # %% auto #0
6
+ __all__ = ['generate_key_repeat_js']
7
+
8
+ # %% ../../nbs/js/repeat.ipynb #684c3b7a
9
+ from ..core.config import TokenSelectorConfig
10
+
11
+ # %% ../../nbs/js/repeat.ipynb #b02029c9
12
+ def _generate_movement_dispatch_js(
13
+ config:TokenSelectorConfig, # config for this instance
14
+ ) -> str: # JS code fragment for the movement dispatcher
15
+ """Generate the key-to-movement dispatch logic."""
16
+ left_key = config.left_key
17
+ right_key = config.right_key
18
+ mode = config.selection_mode
19
+
20
+ # Base movement for unmodified keys
21
+ base_dispatch = f"""
22
+ if (key === '{left_key}') return ns.moveLeft;
23
+ if (key === '{right_key}') return ns.moveRight;
24
+ """
25
+
26
+ # Shift-modified keys for span extend
27
+ if mode == "span":
28
+ shift_dispatch = f"""
29
+ if (key === '{left_key}') return ns.extendLeft;
30
+ if (key === '{right_key}') return ns.extendRight;
31
+ """
32
+ else:
33
+ shift_dispatch = """
34
+ return null;
35
+ """
36
+
37
+ return f"""
38
+ ns._getMovementFn = function(key, shiftKey) {{
39
+ if (shiftKey) {{{shift_dispatch}
40
+ }}
41
+ {base_dispatch}
42
+ return null;
43
+ }};
44
+ """
45
+
46
+ # %% ../../nbs/js/repeat.ipynb #0122283a
47
+ def generate_key_repeat_js(
48
+ config:TokenSelectorConfig, # config with timing settings
49
+ ) -> str: # JS code fragment for the IIFE
50
+ """Generate the custom key repeat engine JS."""
51
+ initial_delay = config.initial_delay
52
+ repeat_interval = config.repeat_interval
53
+ throttle_floor = config.throttle_floor
54
+
55
+ dispatch = _generate_movement_dispatch_js(config)
56
+
57
+ return dispatch + f"""
58
+ // Key repeat engine state
59
+ ns._repeatTimer = null;
60
+ ns._repeatKey = null;
61
+ ns._repeatShift = false;
62
+ ns._lastMoveTime = 0;
63
+
64
+ ns._handleKeyDown = function(e) {{
65
+ if (!ns.active) return;
66
+
67
+ var moveFn = ns._getMovementFn(e.key, e.shiftKey);
68
+ if (!moveFn) return;
69
+
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+
73
+ // If same key already held, let the timer run
74
+ if (ns._repeatKey === e.key && ns._repeatShift === e.shiftKey) return;
75
+
76
+ // Cancel any existing repeat
77
+ if (ns._repeatTimer) {{
78
+ clearTimeout(ns._repeatTimer);
79
+ ns._repeatTimer = null;
80
+ }}
81
+
82
+ ns._repeatKey = e.key;
83
+ ns._repeatShift = e.shiftKey;
84
+
85
+ // Immediate first movement
86
+ var now = Date.now();
87
+ if (now - ns._lastMoveTime >= {throttle_floor}) {{
88
+ moveFn();
89
+ ns._lastMoveTime = now;
90
+ }}
91
+
92
+ // Start repeat after initial delay, then switch to interval
93
+ ns._repeatTimer = setTimeout(function repeatTick() {{
94
+ var fn = ns._getMovementFn(ns._repeatKey, ns._repeatShift);
95
+ if (!fn || !ns.active) {{
96
+ ns._repeatKey = null;
97
+ ns._repeatShift = false;
98
+ return;
99
+ }}
100
+ var tickNow = Date.now();
101
+ if (tickNow - ns._lastMoveTime >= {throttle_floor}) {{
102
+ fn();
103
+ ns._lastMoveTime = tickNow;
104
+ }}
105
+ ns._repeatTimer = setTimeout(repeatTick, {repeat_interval});
106
+ }}, {initial_delay});
107
+ }};
108
+
109
+ ns._handleKeyUp = function(e) {{
110
+ if (e.key === ns._repeatKey) {{
111
+ if (ns._repeatTimer) {{
112
+ clearTimeout(ns._repeatTimer);
113
+ ns._repeatTimer = null;
114
+ }}
115
+ ns._repeatKey = null;
116
+ ns._repeatShift = false;
117
+ }}
118
+ }};
119
+ """
File without changes
@@ -0,0 +1,157 @@
1
+ """Keyboard navigation library integration factories for token selector mode, actions, URL maps, and hidden action buttons."""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/keyboard/actions.ipynb.
4
+
5
+ # %% auto #0
6
+ __all__ = ['create_token_selector_mode', 'create_token_nav_actions', 'build_token_selector_url_map',
7
+ 'render_token_action_buttons']
8
+
9
+ # %% ../../nbs/keyboard/actions.ipynb #9cae2c4c
10
+ from typing import Any, Dict, Tuple
11
+
12
+ from fasthtml.common import Div, Button, Hidden
13
+
14
+ from cjm_fasthtml_tailwind.utilities.layout import display_tw
15
+
16
+ from cjm_fasthtml_keyboard_navigation.core.actions import KeyAction
17
+ from cjm_fasthtml_keyboard_navigation.core.modes import KeyboardMode
18
+
19
+ from ..core.config import TokenSelectorConfig
20
+ from ..core.html_ids import TokenSelectorHtmlIds
21
+ from ..js.core import global_callback_name
22
+
23
+ # %% ../../nbs/keyboard/actions.ipynb #a1223d9d
24
+ def create_token_selector_mode(
25
+ config:TokenSelectorConfig, # config for this instance
26
+ mode_name:str = "token-select", # mode name for the keyboard nav system
27
+ indicator_text:str = "Token Select", # mode indicator text
28
+ exit_key:str = "", # exit key (empty = programmatic only via Escape KeyAction)
29
+ exit_on_zone_change:bool = False, # whether to exit on zone change
30
+ ) -> KeyboardMode: # configured keyboard mode
31
+ """Create a keyboard mode that activates/deactivates the token selector."""
32
+ return KeyboardMode(
33
+ name=mode_name,
34
+ on_enter=global_callback_name(config.prefix, "activate"),
35
+ on_exit=global_callback_name(config.prefix, "deactivate"),
36
+ indicator_text=indicator_text,
37
+ exit_key=exit_key,
38
+ exit_on_zone_change=exit_on_zone_change,
39
+ )
40
+
41
+ # %% ../../nbs/keyboard/actions.ipynb #464fca0c
42
+ def create_token_nav_actions(
43
+ config:TokenSelectorConfig, # config for this instance
44
+ zone_id:str, # focus zone ID
45
+ mode_name:str = "token-select", # mode name (must match the mode)
46
+ confirm_button_id:str = "", # HTMX button ID for confirm action
47
+ cancel_button_id:str = "", # HTMX button ID for cancel action
48
+ ) -> Tuple[KeyAction, ...]: # non-movement keyboard actions
49
+ """Create keyboard actions for the token selector."""
50
+ actions = []
51
+ mode_scope = (mode_name,)
52
+
53
+ # Confirm: Enter
54
+ if confirm_button_id:
55
+ actions.append(KeyAction(
56
+ key="Enter",
57
+ htmx_trigger=confirm_button_id,
58
+ mode_names=mode_scope,
59
+ mode_exit=True,
60
+ description="Confirm selection",
61
+ hint_group="Token Select",
62
+ zone_ids=(zone_id,),
63
+ ))
64
+ # Space as alias (hidden from hints)
65
+ actions.append(KeyAction(
66
+ key=" ",
67
+ htmx_trigger=confirm_button_id,
68
+ mode_names=mode_scope,
69
+ mode_exit=True,
70
+ description="Confirm selection",
71
+ hint_group="Token Select",
72
+ show_in_hints=False,
73
+ zone_ids=(zone_id,),
74
+ ))
75
+
76
+ # Cancel: Escape
77
+ if cancel_button_id:
78
+ actions.append(KeyAction(
79
+ key="Escape",
80
+ htmx_trigger=cancel_button_id,
81
+ mode_names=mode_scope,
82
+ mode_exit=True,
83
+ description="Cancel",
84
+ hint_group="Token Select",
85
+ zone_ids=(zone_id,),
86
+ ))
87
+
88
+ # Home: move to first position
89
+ actions.append(KeyAction(
90
+ key="Home",
91
+ js_callback=global_callback_name(config.prefix, "moveToFirst"),
92
+ mode_names=mode_scope,
93
+ description="Go to start",
94
+ hint_group="Token Select",
95
+ zone_ids=(zone_id,),
96
+ ))
97
+
98
+ # End: move to last position
99
+ actions.append(KeyAction(
100
+ key="End",
101
+ js_callback=global_callback_name(config.prefix, "moveToLast"),
102
+ mode_names=mode_scope,
103
+ description="Go to end",
104
+ hint_group="Token Select",
105
+ zone_ids=(zone_id,),
106
+ ))
107
+
108
+ return tuple(actions)
109
+
110
+ # %% ../../nbs/keyboard/actions.ipynb #8b6a950a
111
+ def build_token_selector_url_map(
112
+ confirm_button_id:str, # button ID for confirm action
113
+ cancel_button_id:str, # button ID for cancel action
114
+ confirm_url:str, # URL for confirm action
115
+ cancel_url:str, # URL for cancel action
116
+ ) -> Dict[str, str]: # button ID -> URL mapping
117
+ """Build URL map for keyboard system with token selector action buttons."""
118
+ url_map = {}
119
+ if confirm_button_id and confirm_url:
120
+ url_map[confirm_button_id] = confirm_url
121
+ if cancel_button_id and cancel_url:
122
+ url_map[cancel_button_id] = cancel_url
123
+ return url_map
124
+
125
+ # %% ../../nbs/keyboard/actions.ipynb #039e1b6e
126
+ def render_token_action_buttons(
127
+ confirm_button_id:str, # button ID for confirm
128
+ cancel_button_id:str, # button ID for cancel
129
+ confirm_url:str, # URL for confirm action
130
+ cancel_url:str, # URL for cancel action
131
+ ids:TokenSelectorHtmlIds, # HTML IDs (for hx_include)
132
+ extra_include:str = "", # additional hx_include selectors
133
+ ) -> Any: # Div containing hidden action buttons
134
+ """Render hidden HTMX buttons for confirm/cancel actions."""
135
+ include = f"#{ids.anchor_input}, #{ids.focus_input}"
136
+ if extra_include:
137
+ include = f"{include}, {extra_include}"
138
+
139
+ buttons = []
140
+ if confirm_button_id and confirm_url:
141
+ buttons.append(Button(
142
+ id=confirm_button_id,
143
+ hx_post=confirm_url,
144
+ hx_swap="none",
145
+ hx_include=include,
146
+ cls=str(display_tw.hidden),
147
+ ))
148
+ if cancel_button_id and cancel_url:
149
+ buttons.append(Button(
150
+ id=cancel_button_id,
151
+ hx_post=cancel_url,
152
+ hx_swap="none",
153
+ hx_include=include,
154
+ cls=str(display_tw.hidden),
155
+ ))
156
+
157
+ return Div(*buttons, cls=str(display_tw.hidden))