blitz-pdf 0.1.3 → 0.1.4

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 (64) hide show
  1. package/README.md +6 -6
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +7 -6
  5. package/package.json +1 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,427 @@
1
+ use crate::Document;
2
+ use blitz_traits::events::{
3
+ BlitzPointerEvent, BlitzPointerId, DomEvent, DomEventData, EventState, Point, PointerCoords,
4
+ UiEvent,
5
+ };
6
+ use blitz_traits::node_id::NodeId;
7
+ use std::collections::VecDeque;
8
+
9
+ pub trait EventHandler {
10
+ fn handle_event(
11
+ &mut self,
12
+ chain: &[NodeId],
13
+ event: &mut DomEvent,
14
+ doc: &mut dyn Document,
15
+ event_state: &mut EventState,
16
+ );
17
+ }
18
+
19
+ pub struct NoopEventHandler;
20
+ impl EventHandler for NoopEventHandler {
21
+ fn handle_event(
22
+ &mut self,
23
+ _chain: &[NodeId],
24
+ _event: &mut DomEvent,
25
+ _doc: &mut dyn Document,
26
+ _event_state: &mut EventState,
27
+ ) {
28
+ // Do nothing
29
+ }
30
+ }
31
+
32
+ pub struct EventDriver<'doc, Handler: EventHandler> {
33
+ doc: &'doc mut dyn Document,
34
+ handler: Handler,
35
+ queue: VecDeque<DomEvent>,
36
+ }
37
+
38
+ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> {
39
+ pub fn new(doc: &'doc mut dyn Document, handler: Handler) -> Self {
40
+ EventDriver {
41
+ doc,
42
+ handler,
43
+ queue: VecDeque::with_capacity(4),
44
+ }
45
+ }
46
+
47
+ pub fn handle_pointer_move(&mut self, event: &BlitzPointerEvent) -> Option<NodeId> {
48
+ let mut doc = self.doc.inner_mut();
49
+
50
+ let prev_hover_node_id = doc.hover_node_id;
51
+ let changed = doc.set_hover_to(event.page_x(), event.page_y());
52
+ let hover_node_id = doc.hover_node_id;
53
+
54
+ drop(doc);
55
+
56
+ if !changed {
57
+ return prev_hover_node_id;
58
+ }
59
+
60
+ self.dispatch_hover_change(prev_hover_node_id, hover_node_id, event);
61
+
62
+ hover_node_id
63
+ }
64
+
65
+ /// End hover when the pointer leaves the document, dispatching out/leave
66
+ /// events to the previously hovered chain
67
+ pub fn handle_pointer_leave(&mut self, event: &BlitzPointerEvent) {
68
+ let mut doc = self.doc.inner_mut();
69
+ let prev_hover_node_id = doc.hover_node_id;
70
+ doc.clear_hover();
71
+ drop(doc);
72
+
73
+ self.dispatch_hover_change(prev_hover_node_id, None, event);
74
+ }
75
+
76
+ fn dispatch_hover_change(
77
+ &mut self,
78
+ prev_hover_node_id: Option<NodeId>,
79
+ hover_node_id: Option<NodeId>,
80
+ event: &BlitzPointerEvent,
81
+ ) {
82
+ let doc = self.doc.inner();
83
+ let mut old_chain = prev_hover_node_id
84
+ .map(|id| doc.node_chain(id))
85
+ .unwrap_or_default();
86
+ let mut new_chain = hover_node_id
87
+ .map(|id| doc.node_chain(id))
88
+ .unwrap_or_default();
89
+ old_chain.reverse();
90
+ new_chain.reverse();
91
+
92
+ // Find the difference in the node chain of the last hovered objected and the newest
93
+ let old_len = old_chain.len();
94
+ let new_len = new_chain.len();
95
+
96
+ let first_difference_index = old_chain
97
+ .iter()
98
+ .zip(&new_chain)
99
+ .position(|(old, new)| old != new)
100
+ .unwrap_or_else(|| old_len.min(new_len));
101
+
102
+ drop(doc);
103
+
104
+ let is_mouse = event.is_mouse();
105
+
106
+ if let Some(target) = prev_hover_node_id {
107
+ self.handle_dom_event(DomEvent::new(
108
+ target,
109
+ DomEventData::PointerOut(event.clone()),
110
+ ));
111
+ if is_mouse {
112
+ self.handle_dom_event(DomEvent::new(target, DomEventData::MouseOut(event.clone())));
113
+ }
114
+
115
+ // Send an mouseleave event to all old elements on the chain
116
+ for node_id in old_chain
117
+ .get(first_difference_index..)
118
+ .unwrap_or(&[])
119
+ .iter()
120
+ {
121
+ self.handle_dom_event(DomEvent::new(
122
+ *node_id,
123
+ DomEventData::PointerLeave(event.clone()),
124
+ ));
125
+ if is_mouse {
126
+ self.handle_dom_event(DomEvent::new(
127
+ *node_id,
128
+ DomEventData::MouseLeave(event.clone()),
129
+ ));
130
+ }
131
+ }
132
+ }
133
+
134
+ if let Some(target) = hover_node_id {
135
+ self.handle_dom_event(DomEvent::new(
136
+ target,
137
+ DomEventData::PointerOver(event.clone()),
138
+ ));
139
+
140
+ if is_mouse {
141
+ self.handle_dom_event(DomEvent::new(
142
+ target,
143
+ DomEventData::MouseOver(event.clone()),
144
+ ));
145
+ }
146
+
147
+ // Send an mouseenter event to all new elements on the chain
148
+ for node_id in new_chain
149
+ .get(first_difference_index..)
150
+ .unwrap_or(&[])
151
+ .iter()
152
+ {
153
+ self.handle_dom_event(DomEvent::new(
154
+ *node_id,
155
+ DomEventData::PointerEnter(event.clone()),
156
+ ));
157
+
158
+ if is_mouse {
159
+ self.handle_dom_event(DomEvent::new(
160
+ *node_id,
161
+ DomEventData::MouseEnter(event.clone()),
162
+ ));
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ pub fn handle_ui_event(&mut self, event: UiEvent) {
169
+ let doc = self.doc.inner();
170
+
171
+ // A non-hovering pointer (finger) ends hover once it is lifted or cancelled
172
+ let mut hover_end_event = None;
173
+ let mut hover_node_id = doc.hover_node_id;
174
+ let focussed_node_id = doc.focus_node_id;
175
+ let prev_hover = doc.hover_state();
176
+ drop(doc);
177
+
178
+ // A document the pointer has left has no say over the cursor
179
+ let is_leave = matches!(event, UiEvent::PointerLeave(_));
180
+
181
+ // Update document input state (hover, focus, active, etc)
182
+ match &event {
183
+ UiEvent::PointerMove(event) => {
184
+ hover_node_id = self.handle_pointer_move(event);
185
+ }
186
+ UiEvent::PointerDown(event) => {
187
+ hover_node_id = self.handle_pointer_move(event);
188
+ let mut doc = self.doc.inner_mut();
189
+ doc.active_node();
190
+ doc.set_mousedown_node_id(hover_node_id);
191
+ }
192
+ UiEvent::PointerUp(event) => {
193
+ hover_node_id = self.handle_pointer_move(event);
194
+ let mut doc = self.doc.inner_mut();
195
+ doc.unactive_node();
196
+
197
+ if event.is_primary && matches!(event.id, BlitzPointerId::Finger(_)) {
198
+ hover_end_event = Some(event.clone());
199
+ }
200
+ }
201
+ UiEvent::PointerCancel(event) => {
202
+ hover_node_id = self.handle_pointer_move(event);
203
+ let mut doc = self.doc.inner_mut();
204
+ doc.unactive_node();
205
+
206
+ if event.is_primary && matches!(event.id, BlitzPointerId::Finger(_)) {
207
+ hover_end_event = Some(event.clone());
208
+ }
209
+ }
210
+ UiEvent::PointerLeave(event) => {
211
+ self.handle_pointer_leave(event);
212
+ hover_node_id = None;
213
+ }
214
+ _ => {}
215
+ };
216
+
217
+ let target = match event {
218
+ UiEvent::PointerMove(_) => hover_node_id,
219
+ UiEvent::PointerUp(_) => hover_node_id,
220
+ UiEvent::PointerDown(_) => hover_node_id,
221
+ UiEvent::PointerCancel(_) => hover_node_id,
222
+ UiEvent::PointerLeave(_) => hover_node_id,
223
+ UiEvent::Wheel(_) => hover_node_id,
224
+ UiEvent::KeyUp(_) => focussed_node_id,
225
+ UiEvent::KeyDown(_) => focussed_node_id,
226
+ UiEvent::Ime(_) => focussed_node_id,
227
+ UiEvent::AppleStandardKeybinding(_) => focussed_node_id,
228
+ };
229
+ // Fall back to the root element. A document without a root element (e.g. an
230
+ // empty iframe sub-document) has no event target, so there is nothing to do.
231
+ let Some(target) = target.or_else(|| self.doc.inner().try_root_element().map(|el| el.id))
232
+ else {
233
+ return;
234
+ };
235
+
236
+ match event {
237
+ UiEvent::PointerMove(data) => {
238
+ self.handle_pointer_event(
239
+ target,
240
+ data,
241
+ DomEventData::PointerMove,
242
+ Some(DomEventData::MouseMove),
243
+ DomEventData::TouchMove,
244
+ );
245
+ }
246
+ UiEvent::PointerUp(data) => {
247
+ self.handle_pointer_event(
248
+ target,
249
+ data,
250
+ DomEventData::PointerUp,
251
+ Some(DomEventData::MouseUp),
252
+ DomEventData::TouchEnd,
253
+ );
254
+ }
255
+ UiEvent::PointerDown(data) => {
256
+ self.handle_pointer_event(
257
+ target,
258
+ data,
259
+ DomEventData::PointerDown,
260
+ Some(DomEventData::MouseDown),
261
+ DomEventData::TouchStart,
262
+ );
263
+ }
264
+ UiEvent::PointerCancel(data) => {
265
+ // `pointercancel` has no mouse-compatibility event, but does
266
+ // generate a `touchcancel` for touch-like inputs.
267
+ self.handle_pointer_event(
268
+ target,
269
+ data,
270
+ DomEventData::PointerCancel,
271
+ None::<fn(BlitzPointerEvent) -> DomEventData>,
272
+ DomEventData::TouchCancel,
273
+ );
274
+ }
275
+ // Out/leave events were dispatched by `handle_pointer_leave`
276
+ UiEvent::PointerLeave(_) => {}
277
+ UiEvent::Wheel(data) => {
278
+ self.handle_dom_event(DomEvent::new(target, DomEventData::Wheel(data)))
279
+ }
280
+ UiEvent::KeyUp(data) => {
281
+ self.handle_dom_event(DomEvent::new(target, DomEventData::KeyUp(data)))
282
+ }
283
+ UiEvent::KeyDown(data) => {
284
+ self.handle_dom_event(DomEvent::new(target, DomEventData::KeyDown(data)))
285
+ }
286
+ UiEvent::Ime(data) => {
287
+ self.handle_dom_event(DomEvent::new(target, DomEventData::Ime(data)))
288
+ }
289
+ UiEvent::AppleStandardKeybinding(data) => {
290
+ let mut dom_event =
291
+ DomEvent::new(target, DomEventData::AppleStandardKeybinding(data));
292
+ self.run_default_action(&mut dom_event);
293
+ self.process_queue();
294
+ }
295
+ };
296
+
297
+ // Update document input state (hover, focus, active, etc)
298
+ if let Some(event) = hover_end_event {
299
+ self.handle_pointer_leave(&event);
300
+ }
301
+
302
+ // Update the cursor once the event (including any forwarding to a
303
+ // sub-document) has been fully handled
304
+ let doc = self.doc.inner();
305
+ if !is_leave && doc.hover_state() != prev_hover {
306
+ doc.update_cursor();
307
+ }
308
+ }
309
+
310
+ pub fn handle_dom_event(&mut self, event: DomEvent) {
311
+ self.queue.push_back(event);
312
+ self.process_queue();
313
+ }
314
+
315
+ fn handle_pointer_event(
316
+ &mut self,
317
+ target: NodeId,
318
+ data: BlitzPointerEvent,
319
+ make_ptr_data: impl FnOnce(BlitzPointerEvent) -> DomEventData,
320
+ make_mouse_data: Option<impl FnOnce(BlitzPointerEvent) -> DomEventData>,
321
+ make_touch_data: impl FnOnce(BlitzPointerEvent) -> DomEventData,
322
+ ) {
323
+ let mut ptr_event = DomEvent::new(target, make_ptr_data(data.clone()));
324
+ let mut event_state = EventState::default();
325
+ event_state = self.run_handler_event(&mut ptr_event, event_state);
326
+
327
+ // Generate the corresponding compatibility event (mouse events for the
328
+ // mouse, touch events for fingers and pen/stylus input) and expose it to
329
+ // script. The default action is always run on the pointer event so that
330
+ // the shell layer and default actions remain pointer-based.
331
+ //
332
+ // `pointercancel` has no mouse equivalent, so `make_mouse_data` is `None`
333
+ // in that case and no mouse event is generated.
334
+ if !event_state.is_cancelled() {
335
+ if data.is_mouse() {
336
+ if let Some(make_mouse_data) = make_mouse_data {
337
+ let mut mouse_event = DomEvent::new(target, make_mouse_data(data));
338
+ event_state = self.run_handler_event(&mut mouse_event, event_state);
339
+ }
340
+ } else if data.is_finger() || data.is_pen() {
341
+ let mut touch_event = DomEvent::new(target, make_touch_data(data));
342
+ event_state = self.run_handler_event(&mut touch_event, event_state);
343
+ }
344
+ }
345
+
346
+ if !event_state.is_cancelled() {
347
+ self.run_default_action(&mut ptr_event);
348
+ }
349
+ self.process_queue();
350
+ }
351
+
352
+ fn process_queue(&mut self) {
353
+ while let Some(mut event) = self.queue.pop_front() {
354
+ let event_state = self.run_handler_event(&mut event, EventState::default());
355
+ if !event_state.is_cancelled() {
356
+ self.run_default_action(&mut event);
357
+ }
358
+ }
359
+ }
360
+
361
+ fn adjust_element_coords(
362
+ &self,
363
+ target: NodeId,
364
+ coords: &PointerCoords,
365
+ element: &mut Point<f32>,
366
+ ) {
367
+ if let Some(rect) = self.doc.inner().get_client_bounding_rect(target) {
368
+ element.x = coords.client_x - rect.x as f32;
369
+ element.y = coords.client_y - rect.y as f32;
370
+ }
371
+ }
372
+
373
+ fn run_handler_event(
374
+ &mut self,
375
+ event: &mut DomEvent,
376
+ initial_event_state: EventState,
377
+ ) -> EventState {
378
+ let chain = if event.bubbles {
379
+ let doc = self.doc.inner();
380
+ doc.node_chain(event.target)
381
+ } else {
382
+ vec![event.target]
383
+ };
384
+
385
+ match &mut event.data {
386
+ DomEventData::PointerMove(data)
387
+ | DomEventData::PointerDown(data)
388
+ | DomEventData::PointerUp(data)
389
+ | DomEventData::PointerCancel(data)
390
+ | DomEventData::PointerEnter(data)
391
+ | DomEventData::PointerLeave(data)
392
+ | DomEventData::PointerOver(data)
393
+ | DomEventData::PointerOut(data)
394
+ | DomEventData::MouseMove(data)
395
+ | DomEventData::MouseDown(data)
396
+ | DomEventData::MouseUp(data)
397
+ | DomEventData::MouseEnter(data)
398
+ | DomEventData::MouseLeave(data)
399
+ | DomEventData::MouseOver(data)
400
+ | DomEventData::MouseOut(data)
401
+ | DomEventData::TouchStart(data)
402
+ | DomEventData::TouchEnd(data)
403
+ | DomEventData::TouchMove(data)
404
+ | DomEventData::TouchCancel(data)
405
+ | DomEventData::Click(data)
406
+ | DomEventData::ContextMenu(data)
407
+ | DomEventData::DoubleClick(data) => {
408
+ self.adjust_element_coords(event.target, &data.coords, &mut data.element)
409
+ }
410
+ DomEventData::Wheel(data) => {
411
+ self.adjust_element_coords(event.target, &data.coords, &mut data.element)
412
+ }
413
+ _ => {}
414
+ }
415
+
416
+ let mut event_state = initial_event_state;
417
+ self.handler
418
+ .handle_event(&chain, event, self.doc, &mut event_state);
419
+
420
+ event_state
421
+ }
422
+
423
+ fn run_default_action(&mut self, event: &mut DomEvent) {
424
+ let mut doc = self.doc.inner_mut();
425
+ doc.handle_dom_event(event, |new_evt| self.queue.push_back(new_evt));
426
+ }
427
+ }
@@ -0,0 +1,40 @@
1
+ use blitz_traits::events::{BlitzFocusEvent, DomEvent, DomEventData};
2
+
3
+ use crate::BaseDocument;
4
+
5
+ pub(crate) fn generate_focus_events(
6
+ doc: &mut BaseDocument,
7
+ update_focus: &mut dyn FnMut(&mut BaseDocument),
8
+ dispatch_event: &mut dyn FnMut(DomEvent),
9
+ ) {
10
+ // Update focus, tracking which node was focussed before and after
11
+ let old_focus = doc.get_focussed_node_id();
12
+ update_focus(doc);
13
+ let new_focus = doc.get_focussed_node_id();
14
+
15
+ if old_focus == new_focus {
16
+ return;
17
+ }
18
+
19
+ if let Some(old_focus) = old_focus {
20
+ dispatch_event(DomEvent::new(
21
+ old_focus,
22
+ DomEventData::Blur(BlitzFocusEvent),
23
+ ));
24
+ dispatch_event(DomEvent::new(
25
+ old_focus,
26
+ DomEventData::FocusOut(BlitzFocusEvent),
27
+ ));
28
+ }
29
+
30
+ if let Some(new_focus) = new_focus {
31
+ dispatch_event(DomEvent::new(
32
+ new_focus,
33
+ DomEventData::Focus(BlitzFocusEvent),
34
+ ));
35
+ dispatch_event(DomEvent::new(
36
+ new_focus,
37
+ DomEventData::FocusIn(BlitzFocusEvent),
38
+ ));
39
+ }
40
+ }
@@ -0,0 +1,31 @@
1
+ use blitz_traits::events::{BlitzImeEvent, DomEvent};
2
+
3
+ use crate::BaseDocument;
4
+
5
+ pub(crate) fn handle_ime_event<F: FnMut(DomEvent)>(
6
+ doc: &mut BaseDocument,
7
+ event: BlitzImeEvent,
8
+ dispatch_event: F,
9
+ ) {
10
+ if let Some(node_id) = doc.focus_node_id {
11
+ let node = &mut doc.nodes[node_id];
12
+ let text_input_data = node
13
+ .data
14
+ .downcast_element_mut()
15
+ .and_then(|el| el.text_input_data_mut());
16
+ if let Some(input_data) = text_input_data {
17
+ let generated_event = input_data.apply_ime_event(
18
+ &mut doc.font_ctx.lock().unwrap(),
19
+ &mut doc.layout_ctx,
20
+ event,
21
+ );
22
+
23
+ if let Some(generated_event) = generated_event {
24
+ doc.apply_generated_text_input_event(node_id, generated_event, dispatch_event);
25
+ }
26
+
27
+ #[cfg(feature = "tracing")]
28
+ tracing::debug!(node_id = ?node_id, "Sent ime event");
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,174 @@
1
+ use crate::events::focus::generate_focus_events;
2
+ use crate::{BaseDocument, node::GeneratedTextInputEvent, util::ACTION_MOD};
3
+ use blitz_traits::node_id::NodeId;
4
+ use blitz_traits::{
5
+ SmolStr,
6
+ events::{BlitzInputEvent, BlitzKeyEvent, DomEvent, DomEventData},
7
+ };
8
+ use keyboard_types::{Key, Modifiers};
9
+ use markup5ever::local_name;
10
+
11
+ pub(super) enum KeyboardOrTextInputEvent {
12
+ KeyPress(BlitzKeyEvent),
13
+ AppleStandardKeyBinding(SmolStr),
14
+ }
15
+
16
+ pub(crate) fn handle_key_or_input_event<F: FnMut(DomEvent)>(
17
+ doc: &mut BaseDocument,
18
+ target: NodeId,
19
+ event: KeyboardOrTextInputEvent,
20
+ mut dispatch_event: F,
21
+ ) {
22
+ if let KeyboardOrTextInputEvent::KeyPress(event) = &event {
23
+ if event.key == Key::Tab {
24
+ let backwards = event.modifiers.contains(Modifiers::SHIFT);
25
+ let old_focus = doc.focus_node_id;
26
+ generate_focus_events(
27
+ doc,
28
+ &mut |doc| {
29
+ if backwards {
30
+ doc.focus_prev_node();
31
+ } else {
32
+ doc.focus_next_node();
33
+ }
34
+ },
35
+ &mut dispatch_event,
36
+ );
37
+ if doc.focus_node_id != old_focus {
38
+ doc.shell_provider.request_redraw();
39
+ }
40
+ return;
41
+ }
42
+
43
+ // Handle copy (Ctrl+C/Cmd+C) for text selection when no text input is focused
44
+ if event.state.is_pressed() {
45
+ let action_mod = event.modifiers.contains(ACTION_MOD);
46
+ if action_mod {
47
+ if let Key::Character(c) = &event.key {
48
+ if c.to_lowercase() == "c" {
49
+ // Check if we have a text selection (and no focused text input)
50
+ let has_focused_text_input = doc.focus_node_id.is_some_and(|id| {
51
+ doc.get_node(id)
52
+ .and_then(|n| n.element_data())
53
+ .is_some_and(|e| e.text_input_data().is_some())
54
+ });
55
+
56
+ if !has_focused_text_input {
57
+ if let Some(text) = doc.get_selected_text() {
58
+ let _ = doc.shell_provider.set_clipboard_text(text);
59
+ return;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ if let Some(node_id) = doc.focus_node_id {
69
+ if target != node_id {
70
+ return;
71
+ }
72
+
73
+ let node = &mut doc.nodes[node_id];
74
+ let Some(element_data) = node.element_data_mut() else {
75
+ return;
76
+ };
77
+
78
+ if let Some(input_data) = element_data.text_input_data_mut() {
79
+ let generated_event = match event {
80
+ KeyboardOrTextInputEvent::KeyPress(blitz_key_event) => input_data
81
+ .apply_keypress_event(
82
+ &mut doc.font_ctx.lock().unwrap(),
83
+ &mut doc.layout_ctx,
84
+ &*doc.shell_provider,
85
+ blitz_key_event,
86
+ ),
87
+ KeyboardOrTextInputEvent::AppleStandardKeyBinding(command) => input_data
88
+ .apply_apple_standard_keybinding(
89
+ &mut doc.font_ctx.lock().unwrap(),
90
+ &mut doc.layout_ctx,
91
+ &*doc.shell_provider,
92
+ &command,
93
+ ),
94
+ };
95
+
96
+ if let Some(generated_event) = generated_event {
97
+ doc.apply_generated_text_input_event(node_id, generated_event, dispatch_event);
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ impl BaseDocument {
104
+ pub(crate) fn apply_generated_text_input_event<F: FnMut(DomEvent)>(
105
+ &mut self,
106
+ node_id: NodeId,
107
+ event: GeneratedTextInputEvent,
108
+ mut dispatch_event: F,
109
+ ) {
110
+ let node = &mut self.nodes[node_id];
111
+ let element_data = node
112
+ .element_data_mut()
113
+ .expect("apply_generated_text_input_event called on a node that is not an element");
114
+ let input_data = element_data
115
+ .text_input_data_mut()
116
+ .expect("apply_generated_text_input_event called on a node that is not a text input");
117
+
118
+ match event {
119
+ GeneratedTextInputEvent::Input => {
120
+ let value = input_data.editor.raw_text().to_string();
121
+ dispatch_event(DomEvent::new(
122
+ node_id,
123
+ DomEventData::Input(BlitzInputEvent { value }),
124
+ ));
125
+ self.shell_provider.request_redraw();
126
+ }
127
+ GeneratedTextInputEvent::Select | GeneratedTextInputEvent::PreEditChange => {
128
+ self.shell_provider.request_redraw();
129
+ }
130
+ GeneratedTextInputEvent::Submit => {
131
+ // TODO: Generate submit event that can be handled by script
132
+ implicit_form_submission(self, node_id);
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ /// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#field-that-blocks-implicit-submission
139
+ fn implicit_form_submission(doc: &BaseDocument, text_target: NodeId) {
140
+ let Some(form_owner_id) = doc.controls_to_form.get(&text_target) else {
141
+ return;
142
+ };
143
+ if doc
144
+ .controls_to_form
145
+ .iter()
146
+ .filter(|(_control_id, form_id)| *form_id == form_owner_id)
147
+ .filter_map(|(control_id, _)| doc.nodes[*control_id].element_data())
148
+ .filter(|element_data| {
149
+ element_data.attr(local_name!("type")).is_some_and(|t| {
150
+ matches!(
151
+ t,
152
+ "text"
153
+ | "search"
154
+ | "email"
155
+ | "url"
156
+ | "tel"
157
+ | "password"
158
+ | "date"
159
+ | "month"
160
+ | "week"
161
+ | "time"
162
+ | "datetime-local"
163
+ | "number"
164
+ )
165
+ })
166
+ })
167
+ .count()
168
+ > 1
169
+ {
170
+ return;
171
+ }
172
+
173
+ doc.submit_form(*form_owner_id, *form_owner_id);
174
+ }