blitz-pdf 0.1.7 → 0.1.9

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 (57) hide show
  1. package/README.md +44 -5
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/dist/api.d.ts +2 -1
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +28 -3
  6. package/dist/api.js.map +1 -1
  7. package/dist/browser.d.ts +2 -1
  8. package/dist/browser.d.ts.map +1 -1
  9. package/dist/browser.js +1 -1
  10. package/dist/browser.js.map +1 -1
  11. package/dist/node.d.ts +2 -1
  12. package/dist/node.d.ts.map +1 -1
  13. package/dist/node.js +6 -2
  14. package/dist/node.js.map +1 -1
  15. package/dist/types.d.ts +14 -0
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/worker-host.d.ts +12 -5
  18. package/dist/worker-host.d.ts.map +1 -1
  19. package/dist/worker-host.js +187 -20
  20. package/dist/worker-host.js.map +1 -1
  21. package/dist/worker-protocol.d.ts +5 -0
  22. package/dist/worker-protocol.d.ts.map +1 -1
  23. package/dist/worker-runtime.d.ts +1 -1
  24. package/dist/worker-runtime.d.ts.map +1 -1
  25. package/dist/worker-runtime.js +22 -6
  26. package/dist/worker-runtime.js.map +1 -1
  27. package/licenses/RUST_DEPENDENCIES.txt +34 -40
  28. package/package.json +1 -1
  29. package/src/api.ts +29 -4
  30. package/src/browser.ts +2 -1
  31. package/src/node.ts +7 -2
  32. package/src/types.ts +15 -0
  33. package/src/worker-host.ts +176 -23
  34. package/src/worker-protocol.ts +5 -5
  35. package/src/worker-runtime.ts +20 -6
  36. package/vendor/README.md +19 -5
  37. package/vendor/blitz-dom/Cargo.toml +6 -6
  38. package/vendor/blitz-dom/src/accessibility.rs +175 -2
  39. package/vendor/blitz-dom/src/config.rs +8 -10
  40. package/vendor/blitz-dom/src/document.rs +66 -3
  41. package/vendor/blitz-dom/src/events/keyboard.rs +2 -6
  42. package/vendor/blitz-dom/src/layout/construct.rs +5 -300
  43. package/vendor/blitz-dom/src/layout/inline/build.rs +309 -0
  44. package/vendor/blitz-dom/src/layout/{inline.rs → inline/layout.rs} +1 -1
  45. package/vendor/blitz-dom/src/layout/inline/mod.rs +4 -0
  46. package/vendor/blitz-dom/src/layout/mod.rs +3 -1
  47. package/vendor/blitz-dom/src/layout/replaced.rs +13 -4
  48. package/vendor/blitz-dom/src/mutator.rs +47 -0
  49. package/vendor/blitz-dom/src/node/element.rs +17 -0
  50. package/vendor/blitz-dom/src/node/mod.rs +1 -0
  51. package/vendor/blitz-dom/src/node/node.rs +46 -5
  52. package/vendor/blitz-dom/src/node/text.rs +11 -11
  53. package/vendor/blitz-dom/src/resolve.rs +2 -2
  54. package/vendor/blitz-dom/src/stylo.rs +30 -22
  55. package/vendor/blitz-dom/src/util.rs +1 -1
  56. package/vendor/blitz-paint/Cargo.toml +4 -4
  57. package/wasm/blitz_pdf_bg.wasm +2 -2
@@ -141,10 +141,8 @@ pub fn compute_replaced_layout(
141
141
  // element get no preferred aspect ratio at all.
142
142
  let is_usable_ratio = |ratio: &f32| ratio.is_finite() && *ratio > 0.0;
143
143
  let intrinsic = context.intrinsic_sizes;
144
- let aspect_ratio: Option<f32> = style
145
- .aspect_ratio()
146
- .filter(is_usable_ratio)
147
- .or(intrinsic.ratio.filter(is_usable_ratio));
144
+ let style_aspect_ratio = style.aspect_ratio().filter(is_usable_ratio);
145
+ let aspect_ratio: Option<f32> = style_aspect_ratio.or(intrinsic.ratio.filter(is_usable_ratio));
148
146
 
149
147
  // Concrete object size per the CSS default sizing algorithm with no
150
148
  // specified size (https://drafts.csswg.org/css-images/#default-sizing):
@@ -200,6 +198,17 @@ pub fn compute_replaced_layout(
200
198
  None => default_size,
201
199
  },
202
200
  };
201
+ // The `aspect-ratio` property coerces the natural height to match the natural width, in the
202
+ // box-sizing box (https://github.com/w3c/csswg-drafts/issues/11236).
203
+ let inherent_size = match style_aspect_ratio {
204
+ Some(ratio) => Size {
205
+ width: inherent_size.width,
206
+ height: ((inherent_size.width + box_sizing_adjustment.width) / ratio
207
+ - box_sizing_adjustment.height)
208
+ .max(0.0),
209
+ },
210
+ None => inherent_size,
211
+ };
203
212
 
204
213
  // Percentages are only resolved against zero in the inline axis.
205
214
  // See https://www.w3.org/TR/css-sizing-3/#replaced-percentage-min-contribution
@@ -15,6 +15,7 @@ use blitz_traits::shell::Viewport;
15
15
  use style::Atom;
16
16
  use style::invalidation::element::restyle_hints::RestyleHint;
17
17
  use style::stylesheets::OriginSet;
18
+ use style_dom::ElementState;
18
19
  use thin_vec::ThinVec;
19
20
 
20
21
  macro_rules! tag_and_attr {
@@ -347,6 +348,10 @@ impl DocumentMutator<'_> {
347
348
  element.id = Some(Atom::from(value))
348
349
  }
349
350
 
351
+ if *attr == local_name!("class") {
352
+ element.classes = crate::node::parse_classes(value);
353
+ }
354
+
350
355
  if *attr == local_name!("value") {
351
356
  if let Some(input_data) = element.text_input_data_mut() {
352
357
  // Update text input value
@@ -452,6 +457,10 @@ impl DocumentMutator<'_> {
452
457
  element.id = None;
453
458
  }
454
459
 
460
+ if name.local == local_name!("class") {
461
+ element.classes.clear();
462
+ }
463
+
455
464
  // As in `set_attribute`: taking one of these away can make the element
456
465
  // unfocusable again.
457
466
  if name.local == local_name!("tabindex")
@@ -670,6 +679,12 @@ impl DocumentMutator<'_> {
670
679
  ) {
671
680
  let new_parent_is_in_document = self.doc.nodes[parent_id].flags.is_in_document();
672
681
  self.mutations_occurred |= new_parent_is_in_document && !child_ids.is_empty();
682
+
683
+ // If one of the moved subtrees contains the focused node, the ancestors it is
684
+ // detached from stop matching :focus-within. Record them while the parent links
685
+ // are still intact; the state is transferred after the children are reattached.
686
+ let mut focus_within_old_chain: Option<Vec<NodeId>> = None;
687
+
673
688
  // Detach the children from their old parents *before* inserting them into
674
689
  // the new parent (matching DOM `insertBefore` semantics). If a child is
675
690
  // being moved within the same parent then detaching it after insertion
@@ -677,6 +692,11 @@ impl DocumentMutator<'_> {
677
692
  // parent's child list, and anchor indices would be computed against a
678
693
  // child list that still contains the moved nodes.
679
694
  for child_id in child_ids.iter().copied() {
695
+ let child = &self.doc.nodes[child_id];
696
+ if child.contains_focus() {
697
+ focus_within_old_chain = Some(self.doc.maybe_node_element_ancestors(child.parent));
698
+ }
699
+
680
700
  let child = &mut self.doc.nodes[child_id];
681
701
  let child_was_in_doc = child.flags.is_in_document();
682
702
  self.mutations_occurred |= child_was_in_doc;
@@ -732,6 +752,33 @@ impl DocumentMutator<'_> {
732
752
  }
733
753
  }
734
754
 
755
+ // Transfer :focus-within from the old ancestor chain to the new one (the
756
+ // shared prefix stays). If the subtree left the document, the focus was
757
+ // cleared with it and only the old chain needs the state removed.
758
+ if let Some(old_chain) = focus_within_old_chain {
759
+ let new_chain = match self.doc.focus_node_id {
760
+ Some(_) => self.doc.node_element_ancestors(parent_id),
761
+ None => Vec::new(),
762
+ };
763
+ let same_count = old_chain
764
+ .iter()
765
+ .zip(&new_chain)
766
+ .take_while(|(o, n)| o == n)
767
+ .count();
768
+ for &id in old_chain.iter().skip(same_count) {
769
+ self.doc
770
+ .snapshot_node_and(id, ElementState::FOCUS_WITHIN, |node| {
771
+ node.remove_focus_within()
772
+ });
773
+ }
774
+ for &id in new_chain.iter().skip(same_count) {
775
+ self.doc
776
+ .snapshot_node_and(id, ElementState::FOCUS_WITHIN, |node| {
777
+ node.add_focus_within()
778
+ });
779
+ }
780
+ }
781
+
735
782
  self.maybe_record_node(parent_id);
736
783
  }
737
784
 
@@ -42,6 +42,10 @@ macro_rules! local_names {
42
42
  };
43
43
  }
44
44
 
45
+ pub(crate) fn parse_classes(value: &str) -> ThinVec<Atom> {
46
+ value.split_ascii_whitespace().map(Atom::from).collect()
47
+ }
48
+
45
49
  pub struct ElementData {
46
50
  /// The elements tag name, namespace and prefix
47
51
  pub name: QualName,
@@ -49,6 +53,12 @@ pub struct ElementData {
49
53
  /// The elements id attribute parsed as an atom (if it has one)
50
54
  pub id: Option<Atom>,
51
55
 
56
+ /// The elements class attribute split into atoms (empty if it has none).
57
+ /// Kept in sync with `attrs` by `ElementData::new` and the mutator's
58
+ /// `set_attribute`/`remove_attribute`, so selector matching can compare
59
+ /// atoms instead of re-splitting and re-interning the attribute string.
60
+ pub classes: ThinVec<Atom>,
61
+
52
62
  /// The element's attributes
53
63
  pub attrs: Attributes,
54
64
 
@@ -300,6 +310,7 @@ impl Clone for ElementData {
300
310
  Self {
301
311
  name: self.name.clone(),
302
312
  id: self.id.clone(),
313
+ classes: self.classes.clone(),
303
314
  attrs: self.attrs.clone(),
304
315
  is_focussable: self.is_focussable,
305
316
  style_attribute: self.style_attribute.clone(),
@@ -404,10 +415,16 @@ impl ElementData {
404
415
  .find(|attr| &attr.name.local == "id")
405
416
  .map(|attr| attr.value.as_ref())
406
417
  .map(|value: &str| Atom::from(value));
418
+ let classes = attrs
419
+ .iter()
420
+ .find(|attr| &attr.name.local == "class")
421
+ .map(|attr| parse_classes(&attr.value))
422
+ .unwrap_or_default();
407
423
 
408
424
  let mut data = ElementData {
409
425
  name,
410
426
  id: id_attr_atom,
427
+ classes,
411
428
  attrs: Attributes::new(attrs),
412
429
  is_focussable: false,
413
430
  style_attribute: Default::default(),
@@ -17,6 +17,7 @@ pub use attributes::{Attribute, Attributes};
17
17
  pub use custom_widget::{
18
18
  ComputedStyles, CustomWidgetData, CustomWidgetStatus, ProxyRenderContext, Widget,
19
19
  };
20
+ pub(crate) use element::parse_classes;
20
21
  pub use element::{
21
22
  CanvasData, DocumentData, ElementData, ImageData, ImageResourceData, LayoutData,
22
23
  ListItemLayout, ListItemLayoutPosition, Marker, RasterImageData, SpecialElementData,
@@ -122,6 +122,10 @@ pub struct Node {
122
122
 
123
123
  // Flags
124
124
  pub flags: NodeFlags,
125
+ /// Whether the node's box is out of flow (absolutely positioned and not `display: none`).
126
+ /// Written by the style traversal from the node's new primary style, so that layout
127
+ /// passes can test it without reading styles.
128
+ pub out_of_flow: AtomicBool,
125
129
 
126
130
  /// Node type (Element, TextNode, etc) specific data.
127
131
  ///
@@ -418,6 +422,7 @@ impl Node {
418
422
  sc_contribution_cache: RefCell::new(ThinVec::new()),
419
423
 
420
424
  flags: NodeFlags::empty(),
425
+ out_of_flow: AtomicBool::new(false),
421
426
  data,
422
427
  }
423
428
  }
@@ -715,6 +720,29 @@ impl Node {
715
720
  .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
716
721
  }
717
722
 
723
+ /// Mark this node as containing focus (`:focus-within`). Set on the
724
+ /// focused element and all of its DOM ancestors.
725
+ pub fn add_focus_within(&mut self) {
726
+ if let Some(data) = self.element_data_mut() {
727
+ data.element_state.insert(ElementState::FOCUS_WITHIN);
728
+ }
729
+ self.mark_ancestors_dirty();
730
+ }
731
+
732
+ /// Mark this node as no longer containing focus (`:focus-within`).
733
+ pub fn remove_focus_within(&mut self) {
734
+ if let Some(data) = self.element_data_mut() {
735
+ data.element_state.remove(ElementState::FOCUS_WITHIN);
736
+ }
737
+ self.mark_ancestors_dirty();
738
+ }
739
+
740
+ /// Whether this node is the focused node or one of its DOM ancestors (`:focus-within`).
741
+ pub fn contains_focus(&self) -> bool {
742
+ self.element_data()
743
+ .is_some_and(|data| data.element_state.contains(ElementState::FOCUS_WITHIN))
744
+ }
745
+
718
746
  pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
719
747
  if let Some(data) = self.element_data_mut() {
720
748
  data.element_state
@@ -1222,11 +1250,15 @@ impl Node {
1222
1250
  /// block's hoisted child list rather than from its parent (`display: none` boxes
1223
1251
  /// generate no box and are never hoisted).
1224
1252
  pub fn is_out_of_flow(&self) -> bool {
1225
- self.primary_styles().is_some_and(|s| {
1226
- let box_style = s.get_box();
1227
- stylo_taffy::convert::position(box_style.position).is_out_of_flow()
1228
- && stylo_taffy::convert::display(box_style.display) != taffy::Display::None
1229
- })
1253
+ self.primary_styles()
1254
+ .is_some_and(|s| style_is_out_of_flow(&s))
1255
+ }
1256
+
1257
+ /// Record in [`Node::out_of_flow`] whether `style` (the node's new primary style)
1258
+ /// makes its box out of flow.
1259
+ pub(crate) fn record_out_of_flow(&self, style: &ComputedValues) {
1260
+ self.out_of_flow
1261
+ .store(style_is_out_of_flow(style), Ordering::Relaxed);
1230
1262
  }
1231
1263
 
1232
1264
  pub fn text_content(&self) -> String {
@@ -1947,6 +1979,15 @@ impl std::fmt::Debug for Node {
1947
1979
  }
1948
1980
  }
1949
1981
 
1982
+ /// Whether a box with `style` is out of flow: Taffy positions it from its containing
1983
+ /// block's hoisted child list rather than from its parent (`display: none` boxes generate
1984
+ /// no box and are never hoisted).
1985
+ pub(crate) fn style_is_out_of_flow(style: &ComputedValues) -> bool {
1986
+ let box_style = style.get_box();
1987
+ matches!(box_style.position, Position::Absolute | Position::Fixed)
1988
+ && !box_style.display.is_none()
1989
+ }
1990
+
1950
1991
  #[cfg(test)]
1951
1992
  mod test {
1952
1993
  use style_dom::ElementState;
@@ -3,7 +3,7 @@ use blitz_traits::{
3
3
  node_id::NodeId,
4
4
  shell::ShellProvider,
5
5
  };
6
- use keyboard_types::{Key, Modifiers};
6
+ use keyboard_types::{Key, Modifiers, NamedKey};
7
7
  use parley::{ContentWidths, FontContext, LayoutContext};
8
8
 
9
9
  use crate::util::ACTION_MOD;
@@ -249,7 +249,7 @@ impl TextInputData {
249
249
  }
250
250
  return Some(GeneratedTextInputEvent::Select);
251
251
  }
252
- Key::ArrowLeft => {
252
+ Key::Named(NamedKey::ArrowLeft) => {
253
253
  if action_mod {
254
254
  if shift {
255
255
  driver.select_word_left()
@@ -263,7 +263,7 @@ impl TextInputData {
263
263
  }
264
264
  return Some(GeneratedTextInputEvent::Select);
265
265
  }
266
- Key::ArrowRight => {
266
+ Key::Named(NamedKey::ArrowRight) => {
267
267
  if action_mod {
268
268
  if shift {
269
269
  driver.select_word_right()
@@ -277,7 +277,7 @@ impl TextInputData {
277
277
  }
278
278
  return Some(GeneratedTextInputEvent::Select);
279
279
  }
280
- Key::ArrowUp => {
280
+ Key::Named(NamedKey::ArrowUp) => {
281
281
  if shift {
282
282
  driver.select_up()
283
283
  } else {
@@ -285,7 +285,7 @@ impl TextInputData {
285
285
  }
286
286
  return Some(GeneratedTextInputEvent::Select);
287
287
  }
288
- Key::ArrowDown => {
288
+ Key::Named(NamedKey::ArrowDown) => {
289
289
  if shift {
290
290
  driver.select_down()
291
291
  } else {
@@ -293,7 +293,7 @@ impl TextInputData {
293
293
  }
294
294
  return Some(GeneratedTextInputEvent::Select);
295
295
  }
296
- Key::Home => {
296
+ Key::Named(NamedKey::Home) => {
297
297
  if action_mod {
298
298
  if shift {
299
299
  driver.select_to_text_start()
@@ -307,7 +307,7 @@ impl TextInputData {
307
307
  }
308
308
  return Some(GeneratedTextInputEvent::Select);
309
309
  }
310
- Key::End => {
310
+ Key::Named(NamedKey::End) => {
311
311
  if action_mod {
312
312
  if shift {
313
313
  driver.select_to_text_end()
@@ -321,7 +321,7 @@ impl TextInputData {
321
321
  }
322
322
  return Some(GeneratedTextInputEvent::Select);
323
323
  }
324
- Key::Delete => {
324
+ Key::Named(NamedKey::Delete) => {
325
325
  if action_mod {
326
326
  driver.delete_word()
327
327
  } else {
@@ -332,7 +332,7 @@ impl TextInputData {
332
332
 
333
333
  // On macOS this is handled by the apple standard keybindings
334
334
  #[cfg(not(target_os = "macos"))]
335
- Key::Backspace => {
335
+ Key::Named(NamedKey::Backspace) => {
336
336
  if action_mod {
337
337
  driver.backdelete_word()
338
338
  } else {
@@ -349,7 +349,7 @@ impl TextInputData {
349
349
  return Some(GeneratedTextInputEvent::Submit);
350
350
  }
351
351
  }
352
- Key::Enter => {
352
+ Key::Named(NamedKey::Enter) => {
353
353
  if is_multiline {
354
354
  driver.insert_or_replace_selection("\n");
355
355
  return Some(GeneratedTextInputEvent::Input);
@@ -358,7 +358,7 @@ impl TextInputData {
358
358
  }
359
359
  }
360
360
  Key::Character(s)
361
- if !mods.contains(Modifiers::CONTROL) && !mods.contains(Modifiers::SUPER) =>
361
+ if !mods.contains(Modifiers::CONTROL) && !mods.contains(Modifiers::META) =>
362
362
  {
363
363
  driver.insert_or_replace_selection(&s);
364
364
  return Some(GeneratedTextInputEvent::Input);
@@ -26,10 +26,10 @@ use crate::{
26
26
  LayoutPassState,
27
27
  construct::{
28
28
  ConstructionTask, ConstructionTaskData, ConstructionTaskResult,
29
- ConstructionTaskResultData, LayoutChildren, build_inline_layout_into,
30
- collect_layout_children,
29
+ ConstructionTaskResultData, LayoutChildren, collect_layout_children,
31
30
  },
32
31
  damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC},
32
+ inline::build_inline_layout_into,
33
33
  },
34
34
  node::TextBrush,
35
35
  };
@@ -26,6 +26,13 @@ use style::bloom::each_relevant_element_hash;
26
26
  use style::color::AbsoluteColor;
27
27
  use style::data::{ElementDataMut, ElementDataRef};
28
28
  use style::global_style_data::STYLE_THREAD_POOL;
29
+
30
+ /// Serialises `Parallel` style traversals across documents. Stylo's per-worker
31
+ /// caches (`StyleSharingCache`, `StyleBloom`) live in native thread-locals that
32
+ /// a traversal borrows mutably for its whole duration, so two traversals in
33
+ /// flight on the shared pool at once panic with `already mutably borrowed`
34
+ /// (<https://github.com/DioxusLabs/blitz/issues/430>).
35
+ static PARALLEL_TRAVERSAL_LOCK: Mutex<()> = Mutex::new(());
29
36
  use style::invalidation::element::restyle_hints::RestyleHint;
30
37
  use style::properties::ComputedValues;
31
38
  use style::properties::{Importance, PropertyDeclaration};
@@ -147,8 +154,13 @@ impl crate::document::BaseDocument {
147
154
  // Style the elements, resolving their data
148
155
  let mut traverser = RecalcStyle::new(context);
149
156
  // `Sequential` bypasses Stylo's global pool. See `StyleThreading`.
150
- let pool_guard = matches!(self.style_threading, StyleThreading::Parallel)
151
- .then(|| STYLE_THREAD_POOL.pool());
157
+ let parallel = matches!(self.style_threading, StyleThreading::Parallel);
158
+ let _traversal_lock = parallel.then(|| {
159
+ PARALLEL_TRAVERSAL_LOCK
160
+ .lock()
161
+ .unwrap_or_else(|e| e.into_inner())
162
+ });
163
+ let pool_guard = parallel.then(|| STYLE_THREAD_POOL.pool());
152
164
  let rayon_pool = pool_guard.as_ref().and_then(|g| g.as_ref());
153
165
  style::driver::traverse_dom(&traverser, token, rayon_pool);
154
166
  nodes_needing_style_image_flush =
@@ -474,7 +486,9 @@ impl selectors::Element for BlitzNode<'_> {
474
486
  NonTSPseudoClass::Disabled => self.element_state().contains(ElementState::DISABLED),
475
487
  NonTSPseudoClass::Enabled => self.element_state().contains(ElementState::ENABLED),
476
488
  NonTSPseudoClass::Focus => self.element_state().contains(ElementState::FOCUS),
477
- NonTSPseudoClass::FocusWithin => false,
489
+ NonTSPseudoClass::FocusWithin => {
490
+ self.element_state().contains(ElementState::FOCUS_WITHIN)
491
+ }
478
492
  NonTSPseudoClass::FocusVisible => false,
479
493
  NonTSPseudoClass::Fullscreen => false,
480
494
  NonTSPseudoClass::Hover => self.element_state().contains(ElementState::HOVER),
@@ -569,18 +583,11 @@ impl selectors::Element for BlitzNode<'_> {
569
583
  search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
570
584
  case_sensitivity: selectors::attr::CaseSensitivity,
571
585
  ) -> bool {
572
- let class_attr = self.data.attr(local_name!("class"));
573
- if let Some(class_attr) = class_attr {
574
- // split the class attribute
575
- for pheme in class_attr.split_ascii_whitespace() {
576
- let atom = Atom::from(pheme);
577
- if case_sensitivity.eq_atom(&atom, search_name) {
578
- return true;
579
- }
580
- }
581
- }
582
-
583
- false
586
+ self.element_data().is_some_and(|data| {
587
+ data.classes
588
+ .iter()
589
+ .any(|class| case_sensitivity.eq_atom(class, search_name))
590
+ })
584
591
  }
585
592
 
586
593
  fn imported_part(
@@ -687,12 +694,9 @@ impl<'a> TElement for BlitzNode<'a> {
687
694
  where
688
695
  F: FnMut(&style::values::AtomIdent),
689
696
  {
690
- let class_attr = self.data.attr(local_name!("class"));
691
- if let Some(class_attr) = class_attr {
692
- // split the class attribute
693
- for pheme in class_attr.split_ascii_whitespace() {
694
- let atom = Atom::from(pheme); // interns the string
695
- callback(AtomIdent::cast(&atom));
697
+ if let Some(data) = self.element_data() {
698
+ for class in &data.classes {
699
+ callback(AtomIdent::cast(class));
696
700
  }
697
701
  }
698
702
  }
@@ -703,7 +707,7 @@ impl<'a> TElement for BlitzNode<'a> {
703
707
  {
704
708
  if let Some(attrs) = self.data.attrs() {
705
709
  for attr in attrs.iter() {
706
- callback(&GenericAtomIdent(attr.name.local.clone()));
710
+ callback(GenericAtomIdent::cast(&attr.name.local));
707
711
  }
708
712
  }
709
713
  }
@@ -1335,6 +1339,9 @@ impl<'dom> DomTraversal<BlitzNode<'dom>> for RecalcStyle<'_> {
1335
1339
  recalc_style_at(self, context, el, &mut data, note_child);
1336
1340
 
1337
1341
  sync_pseudo_element_styles(el, &data, &self.nodes_needing_style_image_flush);
1342
+ if let Some(style) = data.styles.get_primary() {
1343
+ el.record_out_of_flow(style);
1344
+ }
1338
1345
 
1339
1346
  if !data.damage.is_empty() {
1340
1347
  // Mark the ancestor chain so that the damage propagation pass
@@ -1449,6 +1456,7 @@ fn sync_pseudo_element_styles(
1449
1456
  .push(pe_node_id);
1450
1457
  }
1451
1458
  }
1459
+ pe_node.record_out_of_flow(&pe_style);
1452
1460
  pe_data.styles.primary = Some(pe_style);
1453
1461
  pe_data.set_restyled();
1454
1462
  }
@@ -5,7 +5,7 @@ use std::borrow::Cow;
5
5
  use style::color::AbsoluteColor;
6
6
 
7
7
  #[cfg(target_os = "macos")]
8
- pub(crate) const ACTION_MOD: Modifiers = Modifiers::SUPER;
8
+ pub(crate) const ACTION_MOD: Modifiers = Modifiers::META;
9
9
  #[cfg(not(target_os = "macos"))]
10
10
  pub(crate) const ACTION_MOD: Modifiers = Modifiers::CONTROL;
11
11
 
@@ -2,7 +2,7 @@
2
2
  name = "blitz-paint"
3
3
  description = "Paint a Blitz Document using anyrender"
4
4
  documentation = "https://docs.rs/blitz-paint"
5
- version = "0.3.0-beta.3"
5
+ version = "0.3.0-beta.4"
6
6
  license = "MIT OR Apache-2.0"
7
7
  homepage = "https://github.com/dioxuslabs/blitz"
8
8
  repository = "https://github.com/dioxuslabs/blitz"
@@ -26,15 +26,15 @@ scrollbars = ["blitz-dom/scrollbars"]
26
26
  # Blitz dependencies
27
27
  anyrender = { version = "0.14.0" }
28
28
  anyrender_svg = { version = "0.15.0", optional = true }
29
- blitz-traits = { version = "=0.3.0-beta.3", default-features = false, git = "https://github.com/DioxusLabs/blitz", rev = "e72650a6e67941852fddf025295cfee30c39ea32" }
30
- blitz-dom = { version = "=0.3.0-beta.3", default-features = false, git = "https://github.com/DioxusLabs/blitz", rev = "e72650a6e67941852fddf025295cfee30c39ea32" }
29
+ blitz-traits = { version = "=0.3.0-beta.4", default-features = false, git = "https://github.com/DioxusLabs/blitz", rev = "f8723f495c7c6bfb611a923a21ed5c8e0fe95d55" }
30
+ blitz-dom = { version = "=0.3.0-beta.4", default-features = false, git = "https://github.com/DioxusLabs/blitz", rev = "f8723f495c7c6bfb611a923a21ed5c8e0fe95d55" }
31
31
 
32
32
  # Servo dependencies
33
33
  style = { version = "0.22.0", package = "stylo" }
34
34
  euclid = { version = "0.22" }
35
35
 
36
36
  # DioxusLabs dependencies
37
- taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "6c2106e3df79493d304f954dc2cd4a4a382981d3", default-features = false, features = ["std", "flexbox", "flexbox_balance", "grid", "block_layout", "content_size", "calc", "detailed_layout_info"] }
37
+ taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4e59968eda29ceeb0bbaeb3993c8cd38baa3d0a6", default-features = false, features = ["std", "flexbox", "flexbox_balance", "grid", "block_layout", "content_size", "calc", "detailed_layout_info"] }
38
38
 
39
39
  # Linebender + WGPU dependencies
40
40
  parley = { version = "0.12", default-features = false, features = ["std", "bytemuck"] }
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/wasm/blitz_pdf_bg.wasm
3
- size: 39720639 bytes
4
- sha256: 3be4532b454d4d2ca811c12eb0c047f65b18eddc07dd925eabf210d529b00318
3
+ size: 72552047 bytes
4
+ sha256: 2167d21c26e52f4b969c583622e08710eb5185242322f8eb90fd01f2b53e7402