listselect

package
v0.6.15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package listselect owns one concern: the multi-selection mode a record list enters when its host is about to act on several rows at once. It is a lego piece — targetlist and targetdate assemble it, they do not re-declare it.

Sibling of listgap, and for the same reason: two lists that must stay visually and behaviourally interchangeable (crudview swaps one for the other) cannot each own a private copy of the rule.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyHeader added in v0.6.10

func ApplyHeader(s *style.Sheet) *style.Sheet

ApplyHeader adds the in-flow selection-header skin to s. The strip is the host root's first child, ALWAYS in flow: it never carries Hide(), so the list never shifts when selection mode opens or closes. Its reserved height is NOT a bespoke min-height and not the app's full control-row height (that reserves far more than an icon needs): partAllSpacer is a child that stays visible in every mode, sized like the select-all box (IconBox(IconMd)), so the strip's natural flex height IS that icon's footprint — nothing bigger, nothing declared twice.

The count is ALSO always visible — "k / N" is useful outside selection mode too (N alone answers "how many records are there"), so it is never gated on Open. Only the select-all BOX is: it is an action control that means nothing without selection mode, so it alone stays hidden until then. The always-visible spacer mirrors the box's exact footprint on the leading edge, which is what keeps the count truly centred in BOTH modes instead of recentring itself the moment the box appears — Header() builds the markup in that [spacer][count][box] order; PushEnd sends the box past the count to the strip's trailing edge without needing a fixed-width count.

Unlike the per-row check (ApplyRow), nothing here is OnEdge: the strip is in flow, a normal row above the <ul>, so nothing can overlap the first row.

func ApplyRow added in v0.6.10

func ApplyRow(s *style.Sheet, row widget.Part) *style.Sheet

ApplyRow adds the per-row selection-check skin to s (the host widget's sheet). The host calls this instead of hand-writing the block. row is the host's own row part (its class differs per widget), used only for the danger wash under a marked row.

The check rides its row's top-end corner (OnEdge), out of the flow: the label never shifts when selection mode opens, and the box reads as a badge on the row rather than a column of its own.

Three reveal facts, and each hangs off exactly the element that owns it:

  • The BOX exists only inside the list root's Open state (selection mode). In normal mode the root has no data-open, so there is no square at all — not an empty one. Hide() is the sole display authority in the base rule; the flex centring lives in the Open reveal, so nothing in the base competes with it (a base rule mixing Hide() with CenterContent() emits two `display` values and the revealed box comes back as a block, which stacks its glyph off-centre — the exact defect this split fixes).

  • WHICH GLYPH shows, and what colour the box wears, is the BOX's own Selected/Invalid state — written by the row ONLY in selection mode (see RowOf). Never the ROW's Selected/Invalid: those also mean "this is the loaded record" in normal mode, and a glyph must never appear then. Invalid → trash on a solid Danger box (white glyph via --color-on-danger); Selected → pencil on a solid AccentInverse box (white via --color-on-primary). A plain Accent/Inset box would tint the glyph near-black through currentColor.

  • The ROW carries the danger wash under the whole row while it is marked for delete (the row binds Invalid too, alongside the box).

func Header(m *Mode, ids func() []string, name widget.Name) *Element

Header builds the in-flow selection header strip: a select-all / deselect-all box and a count that reads "k / N". Child() it above the list <ul>.

The box always carries the same selectall glyph — never trash or pencil. Those name the ACTION the marked rows are about to feed (delete, bulk edit); the box's own job is the SELECTION, not the action, and a host already shows the action glyph on its own commit button (crudview's footer 🗑/✏). Painting that same glyph here duplicated it. The box's background still tracks the danger tone (Danger red / Accent amber, via its Invalid/Selected state below) — only the glyph on top stays fixed.

Both states additionally require Count() > 0: painting the box the moment selection mode opens, before anything is checked, gave tapping select-all no visible effect — the box already looked "active". Gating on an actual mark makes the tap read as a real state change: resting/Inset at zero, Danger/Accent from one mark onward.

The strip itself always reserves its row height — never Hide()'s — so the list never shifts when selection mode opens or closes. The count is ALWAYS visible (see ApplyHeader); only the box is hidden in normal mode and revealed on the root's Open state. The spacer stays visible always, sized like the box, which is what reserves the strip's height AND keeps the count centred in both modes — the count lands in the strip's true visual center instead of the center of whatever space is left once the box appears; the box itself rides the trailing edge (ApplyHeader's PushEnd), away from the count.

ids returns the current rows in render order; the count is its length. name is the host's WidgetName().

Types

type Mode

type Mode struct {

	// OnChange fires after every toggle with the current count, so a host can
	// label its commit button ("🗑 3") and disable it at zero.
	OnChange func(n int)
	// contains filtered or unexported fields
}

Mode is the selection state of one list. The zero value is a usable list in normal mode — a list is a list until its host says otherwise.

func (*Mode) Changed added in v0.6.5

func (m *Mode) Changed() *SignalBool

Changed flips on every Toggle. Rows read it first in their state derives (see the changed field); without that read a tap would update nothing on screen.

func (*Mode) CheckAll added in v0.6.9

func (m *Mode) CheckAll(ids []string)

CheckAll marks every id in ids — the caller's CURRENT render order — and replaces any previous selection. It owns a fresh backing array (never aliases the caller's slice). Fires Changed() and OnChange with the new count. This is the master check's "select all" action.

func (*Mode) CheckedIDs

func (m *Mode) CheckedIDs(ids []string) []string

CheckedIDs returns the marked ids in the order given by ids, which the caller passes as its current render order.

Ordering is NOT optional and NOT the caller's problem to remember: checked accumulates in TAP order, and a host building a confirmation message from tap order would list rows in an order that matches nothing on screen. Taking the render order as a parameter is what makes the wrong version unwritable — there is no accessor that returns the raw slice.

func (*Mode) Clear added in v0.6.9

func (m *Mode) Clear()

Clear unmarks every row WITHOUT leaving selection mode — unlike SetOn(false), which also exits the mode. The master check's "deselect all". A no-op (no signal churn) when nothing is marked.

func (*Mode) Count added in v0.6.9

func (m *Mode) Count() int

Count reports how many rows are currently marked. The master check reads it to decide its tri-state (none / some / all) and to render "n / total".

func (*Mode) Danger added in v0.6.5

func (m *Mode) Danger() *SignalBool

Danger reports the tone signal a row binds its Invalid state to. Never a bool: the skin has to react.

func (*Mode) IsChecked

func (m *Mode) IsChecked(id string) bool

IsChecked answers for one id — what a row binds its check state to.

func (*Mode) On

func (m *Mode) On() *SignalBool

On reports the signal a component binds its root state to, so the stylesheet can reveal the checks. Never a bool: the skin has to react.

func (*Mode) SetDanger added in v0.6.5

func (m *Mode) SetDanger(on bool)

SetDanger arms or disarms the danger tone. Additive: a host that never calls it gets no red anywhere, whatever the selection does.

func (*Mode) SetOn

func (m *Mode) SetOn(on bool)

SetOn enters or leaves selection mode. Leaving ALWAYS clears the marks: a mode the user cancelled must not leave a hidden selection behind for the next entry to inherit silently.

func (*Mode) Toggle

func (m *Mode) Toggle(id string)

Toggle marks or unmarks one id and fires OnChange.

type Row added in v0.6.10

type Row struct {
	Check  *Element
	Edit   *SignalBool
	Danger *SignalBool
}

Row is the per-row selection wiring listselect hands a target* widget so the three widgets stop hand-rolling identical derives. Build it once per row in buildRow.

  • Check is the glyph box: place it in the row. It reveals a trash glyph when the row is marked while the danger tone is armed, a pencil when marked while it is not, and is invisible otherwise.
  • Edit is "marked, danger tone OFF" — the widget ORs this with its own "this is the loaded record" highlight for the row's Selected state.
  • Danger is "marked, danger tone ON" — bind the row's Invalid state to it.

func RowOf added in v0.6.10

func RowOf(m *Mode, id string, name widget.Name) Row

RowOf builds the selection wiring for one row id. name is the host widget's WidgetName() — listselect namespaces its parts under it so the CSS (ApplyRow) and the element agree.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL