Sync selections between a Table view and a List view

The Table view syncs selections automatically via syncWithAppState, while the Tiles (List) view uses checkboxes that manually coordinate with AppState.
<Component name="Files">
  <AppState id="selections" bucket="sharedSelections" initialValue="{{ selectedIds: [] }}" />
  <DataSource id="files" url="/api/files" />
  <Text>Selected: {selections.value.selectedIds.length} items</Text>
  <Tabs>
    <TabItem label="Table View">
      <TableView data="{files}" />
    </TabItem>
    <TabItem label="Tiles View">
      <TilesView data="{files}" />
    </TabItem>
    <TabItem label="Debug">
      <DebugView />
    </TabItem>
  </Tabs>
</Component>
<Component name="TableView">
  <AppState id="selections" bucket="sharedSelections" />
  <Card>
    <Table
      data="{$props.data}"
      rowsSelectable="true"
      syncWithAppState="{selections}"
      loading="{!$props.data}"
    >
      <Column bindTo="name" />
      <Column bindTo="size" />
      <Column bindTo="type" />
    </Table>
  </Card>
</Component>
<Component name="TilesView">
  <AppState id="selections" bucket="sharedSelections" />
  <List data="{$props.data}">
    <Card>
      <HStack verticalAlignment="center">
        <Checkbox
          initialValue="{selections.value.selectedIds?.includes($item.id) || false}"
          onDidChange="(checked) => {
            const ids = selections.value.selectedIds || [];
            if (checked) {
              selections.update({ selectedIds: [...ids, $item.id] });
            } else {
              selections.update({ selectedIds: ids.filter(id => id !== $item.id) });
            }
          }"
        />
        <VStack>
          <Text>{$item.name}</Text>
          <Text variant="caption">{$item.size} - {$item.type}</Text>
        </VStack>
      </HStack>
    </Card>
  </List>
</Component>
The spread operator ...ids takes all existing IDs from the array, and creates a new array with those IDs plus the new one.
The filter method creates a new array with only items that meet a condition.
<Component name="DebugView">
  <AppState id="selections" bucket="sharedSelections" />
  <Card>
    <VStack>
      <Text>selections.value</Text>
      <Text variant="code">{JSON.stringify(selections.value, null, 2)}</Text>
    </VStack>
  </Card>
</Component>
<Component name="Files">
  <AppState id="selections" bucket="sharedSelections" initialValue="{{ selectedIds: [] }}" />
  <DataSource id="files" url="/api/files" />
  <Text>Selected: {selections.value.selectedIds.length} items</Text>
  <Tabs>
    <TabItem label="Table View">
      <TableView data="{files}" />
    </TabItem>
    <TabItem label="Tiles View">
      <TilesView data="{files}" />
    </TabItem>
    <TabItem label="Debug">
      <DebugView />
    </TabItem>
  </Tabs>
</Component>
<Component name="TableView">
  <AppState id="selections" bucket="sharedSelections" />
  <Card>
    <Table
      data="{$props.data}"
      rowsSelectable="true"
      syncWithAppState="{selections}"
      loading="{!$props.data}"
    >
      <Column bindTo="name" />
      <Column bindTo="size" />
      <Column bindTo="type" />
    </Table>
  </Card>
</Component>
<Component name="TilesView">
  <AppState id="selections" bucket="sharedSelections" />
  <List data="{$props.data}">
    <Card>
      <HStack verticalAlignment="center">
        <Checkbox
          initialValue="{selections.value.selectedIds?.includes($item.id) || false}"
          onDidChange="(checked) => {
            const ids = selections.value.selectedIds || [];
            if (checked) {
              selections.update({ selectedIds: [...ids, $item.id] });
            } else {
              selections.update({ selectedIds: ids.filter(id => id !== $item.id) });
            }
          }"
        />
        <VStack>
          <Text>{$item.name}</Text>
          <Text variant="caption">{$item.size} - {$item.type}</Text>
        </VStack>
      </HStack>
    </Card>
  </List>
</Component>
The spread operator ...ids takes all existing IDs from the array, and creates a new array with those IDs plus the new one.
The filter method creates a new array with only items that meet a condition.
<Component name="DebugView">
  <AppState id="selections" bucket="sharedSelections" />
  <Card>
    <VStack>
      <Text>selections.value</Text>
      <Text variant="code">{JSON.stringify(selections.value, null, 2)}</Text>
    </VStack>
  </Card>
</Component>