fix(datums): tighten primary-flag handling
- setAxisRole: gate the "clear other datums' flags" loop on role !== null. Previously, calling setAxisRole(id, null) — e.g. clicking "None" on an unflagged line button — would silently strip the primary off whichever rect/ellipse legitimately held it. The docstring's "no-op if it wasn't set" promise now actually holds. - setAxisRole: replace the bare else in the null branch with an explicit `else if (target.type === "ellipse")` so a future datum type added to the union can't quietly inherit isPrimary. - pickPrimary: extract `flaggedPrimary(d)` so the user-flag check reads as "is this datum flagged?" rather than implying a check order across types. Adds a comment documenting that mutual exclusion is enforced by setAxisRole and that bypassing the store would fall back to array-order tie-breaking.
This commit is contained in:
parent
c2f7bf0df2
commit
23d3297434
@ -265,23 +265,28 @@ type Primary =
|
||||
| { kind: "line"; datum: LineDatum }
|
||||
| { kind: "ellipse"; datum: EllipseDatum }
|
||||
|
||||
function flaggedPrimary(d: Datum): Primary | null {
|
||||
if (d.type === "rectangle" && d.isAxisReference) return { kind: "rect", datum: d }
|
||||
if (d.type === "line" && d.axisRole) return { kind: "line", datum: d }
|
||||
if (d.type === "ellipse" && d.isPrimary) return { kind: "ellipse", datum: d }
|
||||
return null
|
||||
}
|
||||
|
||||
function pickPrimary(datums: Datum[]): Primary {
|
||||
if (datums.length === 0) throw new Error("No datums provided.")
|
||||
|
||||
// User-flagged primary wins regardless of type priority. Rect's
|
||||
// `isAxisReference` and line's `axisRole` carry axis semantics on top
|
||||
// of "primary"; ellipse's `isPrimary` is a pure primary flag (ellipses
|
||||
// don't define axis directions on their own).
|
||||
// User-flagged primary wins regardless of type. The store
|
||||
// (`setAxisRole`) enforces mutual exclusion across the three flag
|
||||
// kinds — `RectDatum.isAxisReference`, `LineDatum.axisRole`, and
|
||||
// `EllipseDatum.isPrimary` — so at most one datum is flagged at any
|
||||
// time. Within a single datum only one of those fields can be set
|
||||
// (discriminated union), so the check order inside `flaggedPrimary`
|
||||
// is moot. If a future caller bypasses `setAxisRole` and creates
|
||||
// multiple flagged datums, the first one in array order wins —
|
||||
// deterministic but not semantic.
|
||||
for (const d of datums) {
|
||||
if (d.type === "rectangle" && d.isAxisReference) {
|
||||
return { kind: "rect", datum: d }
|
||||
}
|
||||
if (d.type === "line" && d.axisRole) {
|
||||
return { kind: "line", datum: d }
|
||||
}
|
||||
if (d.type === "ellipse" && d.isPrimary) {
|
||||
return { kind: "ellipse", datum: d }
|
||||
}
|
||||
const flagged = flaggedPrimary(d)
|
||||
if (flagged) return flagged
|
||||
}
|
||||
|
||||
const typeRank = (d: Datum): number =>
|
||||
|
||||
@ -113,16 +113,23 @@ export const useAppStore = defineStore("app", () => {
|
||||
id: string,
|
||||
role: "rect" | "x" | "y" | "ellipse" | null,
|
||||
) {
|
||||
// Clear any existing flag on other datums.
|
||||
for (let i = 0; i < datums.value.length; i++) {
|
||||
const d = datums.value[i]
|
||||
if (!d || d.id === id) continue
|
||||
if (d.type === "rectangle" && d.isAxisReference) {
|
||||
datums.value[i] = { ...d, isAxisReference: false }
|
||||
} else if (d.type === "line" && d.axisRole) {
|
||||
datums.value[i] = { ...d, axisRole: null }
|
||||
} else if (d.type === "ellipse" && d.isPrimary) {
|
||||
datums.value[i] = { ...d, isPrimary: false }
|
||||
// Only clear *other* datums' flags when actually assigning a new
|
||||
// primary. A pure clear (`role === null`) must be a no-op against
|
||||
// the rest of the set, otherwise clicking "None" on an unflagged
|
||||
// line would silently strip the primary off whatever rect/ellipse
|
||||
// legitimately holds it. The docstring guarantees "no-op if it
|
||||
// wasn't set" for the target too — see the null branch below.
|
||||
if (role !== null) {
|
||||
for (let i = 0; i < datums.value.length; i++) {
|
||||
const d = datums.value[i]
|
||||
if (!d || d.id === id) continue
|
||||
if (d.type === "rectangle" && d.isAxisReference) {
|
||||
datums.value[i] = { ...d, isAxisReference: false }
|
||||
} else if (d.type === "line" && d.axisRole) {
|
||||
datums.value[i] = { ...d, axisRole: null }
|
||||
} else if (d.type === "ellipse" && d.isPrimary) {
|
||||
datums.value[i] = { ...d, isPrimary: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
const idx = datums.value.findIndex((d) => d.id === id)
|
||||
@ -134,7 +141,7 @@ export const useAppStore = defineStore("app", () => {
|
||||
datums.value[idx] = { ...target, isAxisReference: false }
|
||||
} else if (target.type === "line") {
|
||||
datums.value[idx] = { ...target, axisRole: null }
|
||||
} else {
|
||||
} else if (target.type === "ellipse") {
|
||||
datums.value[idx] = { ...target, isPrimary: false }
|
||||
}
|
||||
return
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user