feat(12.1-02): writable foreign keys, locked relation options, invisible columns

- FieldRelationContract.WritableForeignKey makes a belongsTo field over a
  protected foreign key writable; the protected key list is unchanged
- cabana.RelationLockProvider names related ids an administrator may not add
  or remove: options and labels carry locked, and a create or update that
  changes the locked subset is 403 before any row is written
- columns.yaml invisible keeps a column searchable and out of the rows
- a controller implementing pact.FilterOptions serves a scope filter's
  choices before the model
- SPA: locked chips and options in RelationField, DataTable skips invisible
  columns
- README, docs, OpenAPI document, TS types and dist updated
This commit is contained in:
Jakub Zych
2026-10-05 10:58:38 +02:00
parent f50d9b8f10
commit df5cace852
39 changed files with 1115 additions and 84 deletions

View File

@@ -187,8 +187,12 @@ function stateTextClass(states: string[]): string[] {
]
}
// An invisible column is searched on the server and never rendered (the
// rows do not carry it either).
const visibleColumns = computed(() => props.columns.filter((column) => !column.invisible))
const span = computed(() =>
Math.max(props.columns.length + (props.selectable ? 1 : 0) + (props.trailing ? 1 : 0), 1),
Math.max(visibleColumns.value.length + (props.selectable ? 1 : 0) + (props.trailing ? 1 : 0), 1),
)
function openFrom(event: MouseEvent, row: AdminRecord): void {
@@ -220,7 +224,7 @@ const widths = ['w-3/5', 'w-2/5', 'w-1/2', 'w-3/4', 'w-1/3', 'w-2/3', 'w-1/2', '
</button>
</th>
<th
v-for="column in columns"
v-for="column in visibleColumns"
:key="column.key"
scope="col"
:aria-sort="canSort(column) ? (sortState(column) ?? 'none') : undefined"
@@ -249,7 +253,7 @@ const widths = ['w-3/5', 'w-2/5', 'w-1/2', 'w-3/4', 'w-1/3', 'w-2/3', 'w-1/2', '
<td v-if="selectable" class="pl-5">
<span class="block size-[18px] rounded-checkbox bg-skel" />
</td>
<td v-for="column in columns" :key="column.key" class="px-3.5 first:pl-5 last:pr-5">
<td v-for="column in visibleColumns" :key="column.key" class="px-3.5 first:pl-5 last:pr-5">
<span :class="widths[(n - 1) % widths.length]" class="block h-3 rounded-[6px] bg-skel" />
</td>
<td v-if="trailing" class="pr-5" />
@@ -290,7 +294,7 @@ const widths = ['w-3/5', 'w-2/5', 'w-1/2', 'w-3/4', 'w-1/3', 'w-2/3', 'w-1/2', '
</button>
</td>
<td
v-for="(column, columnIndex) in columns"
v-for="(column, columnIndex) in visibleColumns"
:key="column.key"
:class="cellClass(column, columnIndex, statesOf(row))"
class="px-3.5 first:pl-5 last:pr-5"