Add a drill-in button beside Data panel gradient preview widgets (#4398)

This commit is contained in:
Keavon Chambers
2026-08-03 04:31:48 -07:00
committed by GitHub
parent 2f24459344
commit 808662e3d2
5 changed files with 153 additions and 113 deletions

View File

@@ -117,23 +117,26 @@ impl LayoutMessageHandler {
}
LayoutGroup::Table(WidgetTable { rows, .. }) => {
for (row_index, row) in rows.iter().enumerate() {
for (value_index, value) in row.iter().enumerate() {
// Return if this is the correct ID
if value.widget_id == widget_id {
widget_path.push(row_index);
widget_path.push(value_index);
return Some((value, widget_path));
}
for (cell_index, cell) in row.iter().enumerate() {
for (value_index, value) in cell.iter().enumerate() {
// Return if this is the correct ID
if value.widget_id == widget_id {
widget_path.push(row_index);
widget_path.push(cell_index);
widget_path.push(value_index);
return Some((value, widget_path));
}
if let Widget::PopoverButton(popover) = &*value.widget {
stack.extend(
popover
.popover_layout
.0
.iter()
.enumerate()
.map(|(child, val)| ([widget_path.as_slice(), &[row_index, value_index, child]].concat(), val)),
);
if let Widget::PopoverButton(popover) = &*value.widget {
stack.extend(
popover
.popover_layout
.0
.iter()
.enumerate()
.map(|(child, val)| ([widget_path.as_slice(), &[row_index, cell_index, value_index, child]].concat(), val)),
);
}
}
}
}

View File

@@ -273,7 +273,7 @@ impl<'a> Iterator for WidgetIter<'a> {
self.next()
}
Some(LayoutGroup::Table(WidgetTable { rows, .. })) => {
self.table.extend(rows.iter().flatten().rev());
self.table.extend(rows.iter().flatten().flatten().rev());
self.next()
}
Some(LayoutGroup::Section(WidgetSection { layout, .. })) => {
@@ -325,7 +325,7 @@ impl<'a> Iterator for WidgetIterMut<'a> {
self.next()
}
Some(LayoutGroup::Table(WidgetTable { rows, .. })) => {
self.table.extend(rows.iter_mut().flatten().rev());
self.table.extend(rows.iter_mut().flatten().flatten().rev());
self.next()
}
Some(LayoutGroup::Section(WidgetSection { layout, .. })) => {
@@ -365,8 +365,9 @@ pub struct WidgetRow {
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct WidgetTable {
/// Rows of cells, each cell holding one or more widgets laid out inline.
#[serde(rename = "tableWidgets")]
pub rows: Vec<Vec<WidgetInstance>>,
pub rows: Vec<Vec<Vec<WidgetInstance>>>,
pub unstyled: bool,
}
@@ -412,7 +413,14 @@ impl LayoutGroup {
Self::Column(WidgetColumn { widgets })
}
/// A table with one widget per cell.
pub fn table(rows: Vec<Vec<WidgetInstance>>, unstyled: bool) -> Self {
let rows = rows.into_iter().map(|row| row.into_iter().map(|widget| vec![widget]).collect()).collect();
Self::Table(WidgetTable { rows, unstyled })
}
/// A table whose cells may each hold multiple widgets laid out inline.
pub fn table_of_cells(rows: Vec<Vec<Vec<WidgetInstance>>>, unstyled: bool) -> Self {
Self::Table(WidgetTable { rows, unstyled })
}
@@ -600,13 +608,17 @@ impl Diffable for LayoutGroup {
}
}
Self::Table(WidgetTable { rows, .. }) => {
for (row_idx, row) in rows.iter().enumerate() {
for (col_idx, widget) in row.iter().enumerate() {
widget_path.push(row_idx);
widget_path.push(col_idx);
widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map);
widget_path.pop();
widget_path.pop();
for (row_index, row) in rows.iter().enumerate() {
for (column_index, cell) in row.iter().enumerate() {
for (widget_index, widget) in cell.iter().enumerate() {
widget_path.push(row_index);
widget_path.push(column_index);
widget_path.push(widget_index);
widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map);
widget_path.pop();
widget_path.pop();
widget_path.pop();
}
}
}
}
@@ -626,13 +638,17 @@ impl Diffable for LayoutGroup {
}
}
Self::Table(WidgetTable { rows, .. }) => {
for (row_idx, row) in rows.iter_mut().enumerate() {
for (col_idx, widget) in row.iter_mut().enumerate() {
widget_path.push(row_idx);
widget_path.push(col_idx);
widget.replace_widget_ids(layout_target, widget_path, checkbox_map);
widget_path.pop();
widget_path.pop();
for (row_index, row) in rows.iter_mut().enumerate() {
for (column_index, cell) in row.iter_mut().enumerate() {
for (widget_index, widget) in cell.iter_mut().enumerate() {
widget_path.push(row_index);
widget_path.push(column_index);
widget_path.push(widget_index);
widget.replace_widget_ids(layout_target, widget_path, checkbox_map);
widget_path.pop();
widget_path.pop();
widget_path.pop();
}
}
}
}

View File

@@ -307,6 +307,10 @@ fn column_headings(value: &[&str]) -> Vec<WidgetInstance> {
value.iter().map(|text| TextLabel::new(*text).widget_instance()).collect()
}
fn single_widget_cells(widgets: Vec<WidgetInstance>) -> Vec<Vec<WidgetInstance>> {
widgets.into_iter().map(|widget| vec![widget]).collect()
}
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
let error = vec![TextLabel::new(x).widget_instance()];
vec![LayoutGroup::row(error)]
@@ -319,18 +323,20 @@ trait TableItemLayout {
data.breadcrumbs.push(self.identifier());
self.value_page(data)
}
/// Renders this value as a single inline widget inside an item of a `List`.
/// `target` is the [`PathStep`] to push when the widget is clicked to drill into the value.
/// Renders this value as the inline widgets filling one table cell inside an item of a `List`.
/// `target` is the [`PathStep`] to push when a widget is clicked to drill into the value.
/// `data` provides shared context (notably `network_interface`) for types whose label or content
/// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name).
/// The default is a button labeled with `identifier()`. Types whose values are best shown
/// inline (colors, transforms, primitives, etc.) override this to ignore `target` and
/// return a richer non-navigating widget.
fn value_widget(&self, target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextButton::new(self.identifier())
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
.narrow(true)
.widget_instance()
/// The default is a single button labeled with `identifier()`. Types whose values are best shown
/// inline (colors, transforms, primitives, etc.) override this to ignore `target` and return a
/// richer non-navigating widget, optionally joined by companions like a drill-in button.
fn value_widgets(&self, target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![
TextButton::new(self.identifier())
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
.narrow(true)
.widget_instance(),
]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![]
@@ -371,20 +377,20 @@ impl<T: TableItemLayout> TableItemLayout for Item<T> {
let attribute_keys: Vec<String> = self.attributes().keys().map(str::to_string).collect();
// A single element, so no leading ID column, unlike the `List` table
let mut values = vec![self.element().value_widget(PathStep::Element(0), data)];
let mut values = vec![self.element().value_widgets(PathStep::Element(0), data)];
for key in &attribute_keys {
let target = PathStep::Attribute { row: 0, key: key.clone() };
let widget = self.attributes().get_any(key).and_then(|any| dispatch_value_widget(any, target, data)).unwrap_or_else(|| {
let cell = self.attributes().get_any(key).and_then(|any| dispatch_value_widgets(any, target, data)).unwrap_or_else(|| {
let text = self.attributes().display_value(key, display_value_override).unwrap_or_else(|| "-".to_string());
TextLabel::new(text).narrow(true).widget_instance()
vec![TextLabel::new(text).narrow(true).widget_instance()]
});
values.push(widget);
values.push(cell);
}
let mut column_names = vec!["element"];
column_names.extend(attribute_keys.iter().map(|s| s.as_str()));
vec![LayoutGroup::table(vec![column_headings(&column_names), values], false)]
vec![LayoutGroup::table_of_cells(vec![single_widget_cells(column_headings(&column_names)), values], false)]
}
}
@@ -429,14 +435,17 @@ impl<T: TableItemLayout> TableItemLayout for List<T> {
let mut rows = (0..self.len())
.map(|index| {
let element = self.element(index).unwrap();
let mut values = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.value_widget(PathStep::Element(index), data)];
let mut values = vec![
vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance()],
element.value_widgets(PathStep::Element(index), data),
];
for key in &attribute_keys {
let target = PathStep::Attribute { row: index, key: key.clone() };
let widget = self.attribute_any(key, index).and_then(|any| dispatch_value_widget(any, target, data)).unwrap_or_else(|| {
let cell = self.attribute_any(key, index).and_then(|any| dispatch_value_widgets(any, target, data)).unwrap_or_else(|| {
let text = self.attribute_display_value(key, index, display_value_override).unwrap_or_else(|| "-".to_string());
TextLabel::new(text).narrow(true).widget_instance()
vec![TextLabel::new(text).narrow(true).widget_instance()]
});
values.push(widget);
values.push(cell);
}
values
})
@@ -444,9 +453,9 @@ impl<T: TableItemLayout> TableItemLayout for List<T> {
let mut column_names = vec!["", "element"];
column_names.extend(attribute_keys.iter().map(|s| s.as_str()));
rows.insert(0, column_headings(&column_names));
rows.insert(0, single_widget_cells(column_headings(&column_names)));
vec![LayoutGroup::table(rows, false)]
vec![LayoutGroup::table_of_cells(rows, false)]
}
}
@@ -478,8 +487,8 @@ impl TableItemLayout for DashPattern {
self.value_page(data)
}
// Label the spreadsheet's element button with the inner list's identifier, like Artboard
fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
self.0.value_widget(target, data)
fn value_widgets(&self, target: PathStep, data: &LayoutData) -> Vec<WidgetInstance> {
self.0.value_widgets(target, data)
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.0.layout_with_breadcrumb(data)
@@ -498,8 +507,8 @@ impl TableItemLayout for BoxCorners {
self.value_page(data)
}
// Label the spreadsheet's element button with the inner list's identifier, like Artboard
fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
self.0.value_widget(target, data)
fn value_widgets(&self, target: PathStep, data: &LayoutData) -> Vec<WidgetInstance> {
self.0.value_widgets(target, data)
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.0.layout_with_breadcrumb(data)
@@ -709,16 +718,17 @@ impl TableItemLayout for Color {
fn identifier(&self) -> String {
format!("Color (#{})", SRGBA8::from(*self).to_rgba_hex())
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(*self)))
.disabled(true)
.menu_direction(Some(MenuDirection::Top))
.narrow(true)
.widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![
ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(*self)))
.disabled(true)
.menu_direction(Some(MenuDirection::Top))
.narrow(true)
.widget_instance(),
]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![self.value_widget(PathStep::Element(0), _data)];
vec![LayoutGroup::row(widgets)]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -729,16 +739,27 @@ impl TableItemLayout for Gradient {
fn identifier(&self) -> String {
format!("Gradient ({} stops)", self.len())
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(self.clone())))
.menu_direction(Some(MenuDirection::Top))
.disabled(true)
.narrow(true)
.widget_instance()
// The wrapping `Item` already contributes the breadcrumb; the inner list supplies the next level
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.value_page(data)
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![self.value_widget(PathStep::Element(0), _data)];
vec![LayoutGroup::row(widgets)]
// The preview widget doesn't navigate, so a drill-in button beside it opens the newtype's underlying color list
fn value_widgets(&self, target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![
TextButton::new(self.as_color_list().identifier())
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
.narrow(true)
.widget_instance(),
Separator::new(SeparatorStyle::Related).widget_instance(),
ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(self.clone())))
.menu_direction(Some(MenuDirection::Top))
.disabled(true)
.narrow(true)
.widget_instance(),
]
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.as_color_list().layout_with_breadcrumb(data)
}
}
@@ -854,11 +875,11 @@ impl TableItemLayout for bool {
fn identifier(&self) -> String {
"Bool".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
CheckboxInput::new(*self).disabled(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![CheckboxInput::new(*self).disabled(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -888,11 +909,11 @@ impl TableItemLayout for Option<f64> {
fn identifier(&self) -> String {
"Option<f64>".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -903,11 +924,11 @@ impl TableItemLayout for DVec2 {
fn identifier(&self) -> String {
"Vec2".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -918,11 +939,11 @@ impl TableItemLayout for Vec2 {
fn identifier(&self) -> String {
"Vec2".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -933,11 +954,11 @@ impl TableItemLayout for DAffine2 {
fn identifier(&self) -> String {
"Transform".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -948,12 +969,12 @@ impl TableItemLayout for Affine2 {
fn identifier(&self) -> String {
"Transform".to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()
vec![TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -968,11 +989,11 @@ macro_rules! impl_table_item_layout_for_choice_enum {
fn identifier(&self) -> String {
self.to_string()
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(self.to_string()).narrow(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(self.to_string()).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
)*
@@ -1022,11 +1043,11 @@ impl TableItemLayout for ReferencePoint {
fn identifier(&self) -> String {
format!("{self:?}")
}
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
TextLabel::new(self.identifier()).narrow(true).widget_instance()
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(self.identifier()).narrow(true).widget_instance()]
}
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))]
}
}
@@ -1056,7 +1077,7 @@ impl TableItemLayout for NodeId {
// in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing
// `List<NodeId>` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon
// signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses.
fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
fn value_widgets(&self, target: PathStep, data: &LayoutData) -> Vec<WidgetInstance> {
let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path);
let mut button = TextButton::new(label)
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
@@ -1065,7 +1086,7 @@ impl TableItemLayout for NodeId {
let icon = if data.network_interface.is_layer(self, &data.node_lookup_network_path) { "Layer" } else { "Node" };
button = button.icon(icon);
}
button.widget_instance()
vec![button.widget_instance()]
}
// The value page shows the node's kind, name (editable), lock/visibility toggles, and a "Select Layer/Node" action button.
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
@@ -1240,20 +1261,20 @@ fn display_value_override(any: &dyn Any) -> Option<String> {
None
}
/// Type-dispatched widget for displaying an attribute value in a `List<T>` item.
/// Delegates to [`TableItemLayout::value_widget`] so the same widget code is shared between
/// Type-dispatched cell widgets for displaying an attribute value in a `List<T>` item.
/// Delegates to [`TableItemLayout::value_widgets`] so the same widget code is shared between
/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized
/// types so the caller can fall back to a debug-formatted [`TextLabel`].
fn dispatch_value_widget(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option<WidgetInstance> {
fn dispatch_value_widgets(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option<Vec<WidgetInstance>> {
// `NodeIdPath` (e.g. the `editor:layer_path` attribute) drills into its inner path list, matching `drilldown_attribute_layout`.
if let Some(path) = any.downcast_ref::<NodeIdPath>() {
return Some(path.0.value_widget(target, data));
return Some(path.0.value_widgets(target, data));
}
macro_rules! check {
( $($ty:ty),* $(,)? ) => {
$(
if let Some(value) = any.downcast_ref::<$ty>() {
return Some(value.value_widget(target, data));
return Some(value.value_widgets(target, data));
}
)*
};
@@ -1291,14 +1312,14 @@ fn table_node_id_path_layout_with_breadcrumb(path: &List<NodeId>, data: &mut Lay
let node_id = path.element(index).unwrap();
let prefix: Vec<NodeId> = path.iter_element_values().take(index).copied().collect();
let saved = std::mem::replace(&mut data.node_lookup_network_path, prefix);
let widget = node_id.value_widget(PathStep::Element(index), data);
let widgets = node_id.value_widgets(PathStep::Element(index), data);
data.node_lookup_network_path = saved;
vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), widget]
vec![vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance()], widgets]
})
.collect::<Vec<_>>();
rows.insert(0, column_headings(&["", "element"]));
rows.insert(0, single_widget_cells(column_headings(&["", "element"])));
vec![LayoutGroup::table(rows, false)]
vec![LayoutGroup::table_of_cells(rows, false)]
}
/// Type-dispatched recursion into an attribute value for the Data panel breadcrumb navigation.

View File

@@ -12,9 +12,9 @@
<tbody>
{#each widgetData.tableWidgets as row}
<tr>
{#each row as widget}
{#each row as cell}
<td colspan={row.length < columns ? columns - row.length + 1 : undefined}>
<WidgetSpan direction="row" widgets={[widget]} {layoutTarget} narrow={true} />
<WidgetSpan direction="row" widgets={cell} {layoutTarget} narrow={true} />
</td>
{/each}
</tr>

View File

@@ -1,6 +1,6 @@
import type { Layout, LayoutGroup, WidgetDiff, WidgetInstance } from "/wrapper/pkg/graphite_wasm_wrapper";
type UIItem = Layout | LayoutGroup | WidgetInstance[] | WidgetInstance;
type UIItem = Layout | LayoutGroup | WidgetInstance[][] | WidgetInstance[] | WidgetInstance;
// Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument
export function patchLayout(layout: /* &mut */ Layout, diffs: WidgetDiff[]) {
diffs.forEach((update) => {