1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::lowering::NodeBuilder;
use crate::ui::traits::Lower;
use crate::ui::Node;
use crate::ActionEnvelope;
use fission_ir::{ActionEntry, ActionSet, NodeId, Op, Role, Semantics};
use serde::{Deserialize, Serialize};
/// Wraps a subtree in an explicit semantics node.
///
/// Use `SemanticsRegion` when a shell or renderer needs a stable semantic
/// target around an otherwise normal widget subtree. For example, the server
/// shell uses semantic regions as mount points for focused browser islands.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SemanticsRegion {
/// Explicit node identity for the region.
pub id: Option<NodeId>,
/// Stable semantic identifier exposed to renderers and shell adapters.
pub identifier: Option<String>,
/// Optional accessible label for the region.
pub label: Option<String>,
/// Semantic role. Defaults to a generic region.
pub role: Role,
/// Actions attached to the semantic region.
pub actions: ActionSet,
/// Wrapped child subtree.
pub child: Option<Box<Node>>,
}
impl SemanticsRegion {
/// Creates a semantic wrapper around an existing child node.
///
/// Use builder methods to add a stable identifier, accessible label, role,
/// or action metadata before converting the region back into a `Node`.
pub fn new(child: Node) -> Self {
Self {
child: Some(Box::new(child)),
..Default::default()
}
}
/// Sets an explicit node id for the region.
///
/// This is useful when generated browser artifacts need to send actions
/// back to a known mount point. Prefer leaving it unset unless the shell or
/// renderer requires a stable id.
pub fn id(mut self, id: NodeId) -> Self {
self.id = Some(id);
self
}
/// Sets the semantic identifier exposed to shells and HTML renderers.
///
/// Identifiers are intended to be stable within a route. They are used by
/// tests, accessibility bridges, and progressive enhancement code to find
/// the right semantic region without depending on generated DOM structure.
pub fn identifier(mut self, identifier: impl Into<String>) -> Self {
self.identifier = Some(identifier.into());
self
}
/// Sets the accessible label for the semantic region.
///
/// Use this when the wrapped child does not already expose enough text for
/// assistive technologies to describe the region or control clearly.
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
/// Sets the semantic role of the region.
///
/// Choose the role that matches the user-visible behavior of the wrapped
/// child. For example, a styled region that behaves like a button should use
/// `Role::Button` and expose a default action.
pub fn role(mut self, role: Role) -> Self {
self.role = role;
self
}
/// Attaches the action that should run when the region is activated.
///
/// This is the semantic equivalent of a button press. It lets renderers
/// expose activation consistently across mouse, keyboard, accessibility,
/// and browser-island event paths.
pub fn default_action(mut self, action: ActionEnvelope) -> Self {
self.actions.entries.push(ActionEntry {
trigger: fission_ir::semantics::ActionTrigger::Default,
action_id: action.id.as_u128(),
payload_data: Some(action.payload),
});
self
}
/// Converts the semantic region into a normal widget tree node.
pub fn into_node(self) -> Node {
Node::SemanticsRegion(self)
}
}
impl Default for SemanticsRegion {
fn default() -> Self {
Self {
id: None,
identifier: None,
label: None,
role: Role::Generic,
actions: ActionSet::default(),
child: None,
}
}
}
impl Lower for SemanticsRegion {
fn lower(&self, cx: &mut crate::LoweringContext) -> NodeId {
let id = self.id.unwrap_or_else(|| cx.next_node_id());
cx.push_scope(id);
let semantics = Semantics {
role: self.role,
identifier: self.identifier.clone(),
label: self.label.clone(),
actions: self.actions.clone(),
..Default::default()
};
let child_id = self.child.as_ref().map(|child| child.lower(cx));
let mut builder = NodeBuilder::new(id, Op::Semantics(semantics));
if let Some(child_id) = child_id {
builder.add_child(child_id);
}
let node_id = builder.build(cx);
cx.pop_scope();
node_id
}
}