@ninthforge/timeline 0.0.0-stage → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +81 -3
- package/dist/index.cjs +2451 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +416 -0
- package/dist/index.d.ts +416 -0
- package/dist/index.js +2404 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Timeline.tsx","../src/version.ts","../../core/src/diagnostics.ts","../../core/src/date.ts","../../core/src/scale.ts","../../core/src/zone.ts","../../core/src/instant.ts","../../core/src/hours.ts","../../core/src/number.ts","../../core/src/export.ts","../../core/src/roadmap.ts","../../core/src/host.ts","../../core/src/pointer.ts","../../editing/src/changes.ts","../../editing/src/useEditing.ts","../../editing/src/Toasts.tsx","../../editing/src/EmptyAction.tsx","../../editing/src/LicenceKeyField.tsx","../../editing/src/Watermark.tsx","../../license/src/codec.ts","../../license/src/browser.ts","../../license/src/index.ts","../../tokens/src/text.ts","../../tokens/src/index.ts","../src/layout.ts","../src/edit.ts","../src/styles.ts","../src/export.ts","../src/theme.ts","../src/useLicense.ts","../src/sample.ts"],"sourcesContent":["import * as React from 'react';\r\nimport { VERSION } from './version';\r\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react';\r\nimport { copyDiagnostics, cssScale, createRoadmapScale, exportFileName, isContextMenuKey, isHighlighted, isMultiSelect, printDocument, today as localToday, type PointerInfo, type RoadmapZoom } from '@ninthforge/core';\r\nimport { EmptyActionView, lockFor, Toasts, useEditing, type EditingSpec, nextUid, Watermark } from '@ninthforge/editing';\r\nimport { licenseMessage, renewalNotice as graceNotice, unlocksPro } from '@ninthforge/license';\r\nimport { markSvg, clampTextScale, scaleWithText, textSize, textVars } from '@ninthforge/tokens';\r\nimport { canEdit, dayLabel, describeChange, dragSpan, itemWords, keyEdit, keySpan, rangeLabel, spanChange, TIMELINE_FIELDS, type DragMode } from './edit';\r\nimport { exportTimelinePng, timelinePrintHtml } from './export';\r\nimport {\r\n appearance,\r\n buildLanes,\r\n cleanItems,\r\n connectorPath,\r\n dayOfIso,\r\n horizonColumns,\r\n isoOfDay,\r\n LABEL_INSIDE_MIN,\r\n LANE_HEAD_W,\r\n LANE_PAD,\r\n NO_LANE,\r\n place,\r\n ROW_H,\r\n timelineRange,\r\n type Appearance,\r\n type Lane,\r\n type Placed\r\n} from './layout';\r\nimport { css, HEAD_H, MONTH_ROW_H } from './styles';\r\nimport { accentOf, palette, resolveTheme } from './theme';\r\nimport type { TimelineChange, TimelineFields, TimelineItem, TimelineMode, TimelineProps, TimelineZoom } from './types';\r\nimport { useLicense } from './useLicense';\r\n\r\nconst ZOOMS: { id: TimelineZoom; label: string }[] = [\r\n { id: 'month', label: 'Month' },\r\n { id: 'quarter', label: 'Quarter' },\r\n { id: 'half', label: 'Half-year' }\r\n];\r\nconst MODES: { id: TimelineMode; label: string }[] = [\r\n { id: 'dates', label: 'Dates' },\r\n { id: 'horizon', label: 'Now · Next · Later' }\r\n];\r\nconst DRAG_THRESHOLD = 4;\r\nconst SETTLE_MS = 700;\r\n\r\nconst Chevron = () => (\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <path d=\"M3 4.5 L6 7.5 L9 4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\r\n </svg>\r\n);\r\n\r\ninterface Drag {\r\n id: string;\r\n mode: DragMode;\r\n x: number;\r\n start: number;\r\n end: number;\r\n moved: boolean;\r\n pointerId: number;\r\n /** CSS scale around the chart at pointer-down: screen px ÷ this = chart px. */\r\n sx: number;\r\n}\r\n\r\n/**\r\n * A month label that fits its column: the full name, else its first letter, else nothing, so a\r\n * bigger text size never makes labels collide (mono text is about 0.62 em per character, plus\r\n * 10 px of padding).\r\n */\r\nexport function monthLabel(label: string, width: number, px: number): string {\r\n const fits = (n: number) => n * px * 0.62 + 10 <= width;\r\n if (fits(label.length)) return label;\r\n return fits(1) ? label.slice(0, 1) : '';\r\n}\r\n\r\nexport function Timeline(props: TimelineProps) {\r\n const title = props.title ?? 'Roadmap';\r\n const accent = accentOf(props.theme, props.accent);\r\n const license = useLicense(props.licenseKey, { product: 'plan', tenantId: props.tenantId });\r\n const renewal = props.renewalNotice !== false ? graceNotice(license) : null;\r\n const todayDay = useMemo(() => {\r\n const d = dayOfIso(props.today);\r\n return Number.isNaN(d) ? localToday() : d;\r\n }, [props.today]);\r\n\r\n /* ---------- State ---------- */\r\n\r\n const [zoomState, setZoomState] = useState<TimelineZoom>(props.defaultZoom ?? 'quarter');\r\n const zoom = props.zoom ?? zoomState;\r\n const setZoom = (z: TimelineZoom) => {\r\n if (props.zoom === undefined) setZoomState(z);\r\n props.onZoomChange?.(z);\r\n };\r\n const [modeState, setModeState] = useState<TimelineMode>(props.defaultMode ?? 'dates');\r\n const mode = props.mode ?? modeState;\r\n const setMode = (m: TimelineMode) => {\r\n if (props.mode === undefined) setModeState(m);\r\n props.onModeChange?.(m);\r\n };\r\n const [filter, setFilter] = useState('');\r\n const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());\r\n const [trayOpen, setTrayOpen] = useState(false);\r\n const [selected, setSelectedState] = useState<string | null>(null);\r\n const setSelected = (id: string | null, multi = false) => {\r\n setSelectedState(id);\r\n props.onSelect?.(id, { multi });\r\n };\r\n // Host hooks: dimming, hover and context menu.\r\n const hl = props.highlighted && props.highlighted.length ? new Set(props.highlighted) : undefined;\r\n const hovering = useRef(false);\r\n const hostHover = (item: TimelineItem | null, at: PointerInfo) => {\r\n if (!props.onHover) return;\r\n if (item === null && !hovering.current) return;\r\n hovering.current = item !== null;\r\n props.onHover(item, at);\r\n };\r\n\r\n /* ---------- Editing (optimistic saves, undo, toasts: @ninthforge/editing) ---------- */\r\n\r\n const can = (action: 'move' | 'resize', item?: TimelineItem) => canEdit(props.editable, !!props.onItemChange, action, item);\r\n const placedRef = useRef(new Map<string, Placed>());\r\n const spec: EditingSpec<TimelineItem, TimelineFields> = {\r\n ...TIMELINE_FIELDS,\r\n // View-only without a key that unlocks Pro actions (Matt, 2026-11-08). Quiet while checking.\r\n ...lockFor(license, unlocksPro(license), 'Plan', 'timeline'),\r\n noun: 'item',\r\n describe: (change, nameOf) => describeChange(change as TimelineChange, nameOf(change.id), placedRef.current.get(change.id))\r\n };\r\n // Host data, made safe once per change (text as text, unique ids).\r\n const items = useMemo(() => cleanItems(props.items), [props.items]);\r\n const editing = useEditing<TimelineItem, TimelineFields, TimelineChange>(items, props.onItemChange, spec);\r\n const announce = editing.announce;\r\n // Items as saved or pending, without any drag preview: what a drop is measured against.\r\n const baseRef = useRef(editing.shown);\r\n baseRef.current = editing.shown;\r\n\r\n const uid = useState(() => nextUid('nft'))[0];\r\n // Text size (docs/specs/text-size.md): one scale for every role; rows, lanes and the lane names\r\n // column grow with it.\r\n const textScale = clampTextScale(props.textScale ?? 1);\r\n const rowH = scaleWithText(ROW_H, textScale);\r\n const laneHeadW = Math.round(LANE_HEAD_W * textScale);\r\n const labelLineH = Math.ceil(textSize('value', textScale, props.textSizes) * 1.34);\r\n const rootRef = useRef<HTMLDivElement>(null);\r\n const scrollRef = useRef<HTMLDivElement>(null);\r\n const dragRef = useRef<Drag | null>(null);\r\n const [preview, setPreview] = useState<{ id: string; start: number; end: number } | null>(null);\r\n const [dragging, setDragging] = useState(false);\r\n\r\n // Size: the axis fills the width, and narrow hosts get the tray as an overlay.\r\n const [width, setWidth] = useState(1000);\r\n useLayoutEffect(() => {\r\n const el = rootRef.current;\r\n if (!el) return;\r\n const measure = () => setWidth(el.clientWidth || 1000);\r\n measure();\r\n if (typeof ResizeObserver === 'undefined') return;\r\n const ro = new ResizeObserver(measure);\r\n ro.observe(el);\r\n return () => ro.disconnect();\r\n }, []);\r\n const narrow = width < 600;\r\n\r\n // Inherited direction (RTL hosts), unless `dir` says otherwise. Time always runs left to right.\r\n const [inheritedRtl, setInheritedRtl] = useState(false);\r\n useLayoutEffect(() => {\r\n const parent = rootRef.current?.parentElement;\r\n if (parent && typeof getComputedStyle === 'function') setInheritedRtl(getComputedStyle(parent).direction === 'rtl');\r\n }, []);\r\n const rtl = props.dir ? props.dir === 'rtl' : inheritedRtl;\r\n const dirAttr = rtl ? 'rtl' : undefined;\r\n\r\n // Entry animation only on first paint.\r\n const [settled, setSettled] = useState(true);\r\n useEffect(() => {\r\n if (props.animate === false) return;\r\n setSettled(false);\r\n const t = setTimeout(() => setSettled(true), SETTLE_MS);\r\n return () => clearTimeout(t);\r\n }, []);\r\n\r\n /* ---------- Data and layout ---------- */\r\n\r\n // What the user sees: host items with pending edits, and a drag preview on top.\r\n const shown = useMemo(() => {\r\n if (!preview) return editing.shown;\r\n // A milestone without an end keeps none; everything else shows the previewed end.\r\n return editing.shown.map((i) => (i.id === preview.id ? { ...i, start: isoOfDay(preview.start), end: i.end || preview.end !== preview.start ? isoOfDay(preview.end) : undefined } : i));\r\n }, [editing.shown, preview]);\r\n\r\n const range = useMemo(() => timelineRange(shown.map(place), todayDay), [shown, todayDay]);\r\n\r\n const scale = useMemo(\r\n () => createRoadmapScale({ from: range.from, to: range.to, zoom, fiscalYearStart: props.fiscalYearStart, minWidth: Math.max(0, width - laneHeadW - 2) }),\r\n [range.from, range.to, zoom, props.fiscalYearStart, width]\r\n );\r\n // Quarter zoom adds a month row under the quarters.\r\n const headH = scaleWithText(HEAD_H, textScale) + (scale.months.length ? scaleWithText(MONTH_ROW_H, textScale) : 0);\r\n const { lanes, unscheduled } = useMemo(() => buildLanes(shown, scale, { textScale, laneOrder: props.laneOrder, collapsed, filter }), [shown, scale, props.laneOrder, collapsed, filter]);\r\n const placedById = useMemo(() => {\r\n const m = new Map<string, { p: Placed; lane: Lane }>();\r\n for (const l of lanes) for (const p of l.items) m.set(p.id, { p, lane: l });\r\n return m;\r\n }, [lanes]);\r\n placedRef.current = new Map([...placedById].map(([id, v]) => [id, v.p]));\r\n const bodyHeight = lanes.reduce((h, l) => h + l.height, 0);\r\n\r\n const colorBy = props.colorBy ?? 'status';\r\n const looks = useMemo(() => {\r\n const seen = new Map<string, string>();\r\n return new Map(\r\n shown.map((it) => [it.id, appearance(it, { colorBy, accent, statusColors: props.statusColors, laneColors: props.laneColors, seen })] as const)\r\n );\r\n }, [shown, colorBy, accent, props.statusColors, props.laneColors]);\r\n const lookOf = (id: string): Appearance => looks.get(id) ?? { color: accent, text: '#fff', style: 'solid' };\r\n\r\n /* ---------- Scroll to today on first paint and when zoom changes ---------- */\r\n\r\n useEffect(() => {\r\n const el = scrollRef.current;\r\n if (!el || mode !== 'dates') return;\r\n const tx = scale.x(todayDay);\r\n el.scrollLeft = Math.max(0, tx - (el.clientWidth - laneHeadW) / 3);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [zoom, mode]);\r\n const goToday = () => {\r\n const el = scrollRef.current;\r\n if (!el) return;\r\n el.scrollLeft = Math.max(0, scale.x(todayDay) - (el.clientWidth - laneHeadW) / 3);\r\n announce(`Today, ${dayLabel(todayDay)}`);\r\n };\r\n\r\n /* ---------- Selection and keyboard ---------- */\r\n\r\n const words = (p: Placed, lane: Lane) =>\r\n itemWords(p, {\r\n lane: lane.label,\r\n status: p.item.status,\r\n dependsOn: (p.item.dependsOn ?? []).map((d) => placedById.get(d)?.p.item.name ?? '').filter(Boolean)\r\n });\r\n\r\n /** Where an item is on screen: the pointer position for keyboard hooks. */\r\n const anchorOf = (id: string): PointerInfo | null => {\r\n const el = typeof document !== 'undefined' ? document.getElementById(`${uid}-i-${id}`) : null;\r\n const r = el?.getBoundingClientRect();\r\n return r ? { clientX: r.left + r.width / 2, clientY: r.top + r.height / 2, multi: false } : null;\r\n };\r\n const select = (id: string | null, scrollIntoView = false, multi = false) => {\r\n setSelected(id, multi);\r\n if (id && scrollIntoView && props.onHover) {\r\n const item = placedById.get(id)?.p.item;\r\n requestAnimationFrame(() => {\r\n const at = anchorOf(id);\r\n if (item && at) hostHover(item, at);\r\n });\r\n }\r\n if (!id || !scrollIntoView) return;\r\n const hit = placedById.get(id);\r\n const el = scrollRef.current;\r\n if (!hit || !el) return;\r\n const x = scale.x(hit.p.start);\r\n if (x < el.scrollLeft || x > el.scrollLeft + el.clientWidth - laneHeadW - 60) el.scrollLeft = Math.max(0, x - 60);\r\n const top = hit.lane.top;\r\n if (top < el.scrollTop || top + hit.lane.height > el.scrollTop + el.clientHeight - headH) el.scrollTop = Math.max(0, top - 40);\r\n };\r\n\r\n /** Next item along a lane (←/→), or the nearest in the next lane (↑/↓). */\r\n const neighbour = (id: string | null, key: string): string | null => {\r\n const visible = lanes.filter((l) => !l.collapsed && l.items.length);\r\n if (!visible.length) return null;\r\n const cur = id ? placedById.get(id) : undefined;\r\n if (!cur) return visible[0]!.items[0]!.id;\r\n const li = visible.indexOf(cur.lane);\r\n const items = cur.lane.items;\r\n const i = items.indexOf(cur.p);\r\n if (key === 'ArrowRight') return items[i + 1]?.id ?? cur.p.id;\r\n if (key === 'ArrowLeft') return items[i - 1]?.id ?? cur.p.id;\r\n const target = visible[li + (key === 'ArrowDown' ? 1 : -1)];\r\n if (!target) return cur.p.id;\r\n let best = target.items[0]!;\r\n for (const p of target.items) if (Math.abs(p.start - cur.p.start) < Math.abs(best.start - cur.p.start)) best = p;\r\n return best.id;\r\n };\r\n\r\n const commit = (change: TimelineChange | null) => {\r\n if (change) editing.commit(change);\r\n };\r\n\r\n const onGridKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const mod = e.ctrlKey || e.metaKey;\r\n if (mod && !e.altKey && (e.key === 'z' || e.key === 'Z' || e.key === 'y' || e.key === 'Y')) {\r\n if (!props.onItemChange) return;\r\n if (e.key === 'y' || e.key === 'Y' || e.shiftKey) editing.redo();\r\n else editing.undo();\r\n e.preventDefault();\r\n return;\r\n }\r\n const ke = keyEdit(e);\r\n if (ke) {\r\n e.preventDefault();\r\n const hit = selected ? placedById.get(selected) : undefined;\r\n if (!hit) return;\r\n const action = ke.mode === 'end' ? 'resize' : 'move';\r\n if (!can(action, hit.p.item) || (ke.mode === 'end' && hit.p.milestone)) return;\r\n commit(spanChange(hit.p, keySpan(ke.mode, hit.p.start, hit.p.end, zoom as RoadmapZoom, ke.dir), 'keyboard'));\r\n return;\r\n }\r\n switch (e.key) {\r\n case 'ArrowLeft':\r\n case 'ArrowRight':\r\n case 'ArrowUp':\r\n case 'ArrowDown': {\r\n const next = neighbour(selected, e.key);\r\n if (next) select(next, true);\r\n break;\r\n }\r\n case 'Home':\r\n select(lanes.find((l) => l.items.length)?.items[0]?.id ?? null, true);\r\n break;\r\n case 'End': {\r\n const last = [...lanes].reverse().find((l) => l.items.length);\r\n select(last?.items[last.items.length - 1]?.id ?? null, true);\r\n break;\r\n }\r\n case 'Enter': {\r\n const hit = selected ? placedById.get(selected) : undefined;\r\n if (hit) props.onOpen?.(hit.p.item);\r\n break;\r\n }\r\n case 'Escape':\r\n select(null);\r\n break;\r\n default: {\r\n const hit = selected ? placedById.get(selected) : undefined;\r\n if (isContextMenuKey(e) && hit && props.onContextMenu) {\r\n const at = anchorOf(hit.p.id);\r\n if (at) props.onContextMenu(hit.p.item, at);\r\n break;\r\n }\r\n return;\r\n }\r\n }\r\n e.preventDefault();\r\n };\r\n\r\n /* ---------- Dragging ---------- */\r\n\r\n const onItemPointerDown = (e: React.PointerEvent<HTMLElement>, p: Placed) => {\r\n if (e.button !== 0) return;\r\n const edge = (e.target as HTMLElement).getAttribute('data-edge');\r\n const mode: DragMode = edge === 'start' ? 'start' : edge === 'end' ? 'end' : 'move';\r\n select(p.id, false, isMultiSelect(e));\r\n if (!can(mode === 'move' ? 'move' : 'resize', p.item)) return;\r\n dragRef.current = { id: p.id, mode, x: e.clientX, start: p.start, end: p.end, moved: false, pointerId: e.pointerId, sx: cssScale(rootRef.current).x };\r\n const move = (ev: PointerEvent) => {\r\n const d = dragRef.current;\r\n if (!d || ev.pointerId !== d.pointerId) return;\r\n const dx = (ev.clientX - d.x) / d.sx;\r\n if (!d.moved && Math.abs(dx) < DRAG_THRESHOLD) return;\r\n if (!d.moved) {\r\n d.moved = true;\r\n setDragging(true);\r\n }\r\n setPreview({ id: d.id, ...dragSpan(d.mode, d.start, d.end, Math.round(dx / scale.dayWidth)) });\r\n };\r\n const finish = (ev: PointerEvent | KeyboardEventInit, cancel = false) => {\r\n const d = dragRef.current;\r\n window.removeEventListener('pointermove', move);\r\n window.removeEventListener('pointerup', up);\r\n window.removeEventListener('pointercancel', cancelUp);\r\n window.removeEventListener('keydown', esc);\r\n dragRef.current = null;\r\n setDragging(false);\r\n if (!d || !d.moved || cancel) {\r\n setPreview(null);\r\n if (d?.moved && cancel) announce('Move cancelled');\r\n return;\r\n }\r\n const dx = 'clientX' in ev && typeof ev.clientX === 'number' ? (ev.clientX - d.x) / d.sx : 0;\r\n const span = dragSpan(d.mode, d.start, d.end, Math.round(dx / scale.dayWidth));\r\n setPreview(null);\r\n const base = baseRef.current.find((i) => i.id === d.id);\r\n const before = base ? place(base) : null;\r\n if (before) commit(spanChange(before, span, 'drag'));\r\n };\r\n const up = (ev: PointerEvent) => finish(ev);\r\n const cancelUp = (ev: PointerEvent) => finish(ev, true);\r\n const esc = (ev: globalThis.KeyboardEvent) => {\r\n if (ev.key === 'Escape') finish({}, true);\r\n };\r\n window.addEventListener('pointermove', move);\r\n window.addEventListener('pointerup', up);\r\n window.addEventListener('pointercancel', cancelUp);\r\n window.addEventListener('keydown', esc);\r\n };\r\n\r\n /* ---------- Export ---------- */\r\n\r\n const [exportOpen, setExportOpen] = useState(false);\r\n const exportRef = useRef<HTMLDivElement>(null);\r\n useEffect(() => {\r\n if (!exportOpen) return;\r\n exportRef.current?.querySelector<HTMLButtonElement>('[role=\"menuitem\"]')?.focus();\r\n const away = (e: PointerEvent) => {\r\n if (!exportRef.current?.contains(e.target as Node)) setExportOpen(false);\r\n };\r\n document.addEventListener('pointerdown', away, true);\r\n return () => document.removeEventListener('pointerdown', away, true);\r\n }, [exportOpen]);\r\n const exportMenuKeys = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const items = Array.from(exportRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"menuitem\"]') ?? []);\r\n const i = items.indexOf(document.activeElement as HTMLButtonElement);\r\n if (e.key === 'ArrowDown') items[(i + 1) % items.length]?.focus();\r\n else if (e.key === 'ArrowUp') items[(i - 1 + items.length) % items.length]?.focus();\r\n else if (e.key === 'Escape') {\r\n setExportOpen(false);\r\n exportRef.current?.querySelector<HTMLButtonElement>('[aria-haspopup]')?.focus();\r\n } else return;\r\n e.preventDefault();\r\n };\r\n const ownerDoc = () => rootRef.current?.ownerDocument ?? document;\r\n const exportInput = (forPrint: boolean) => ({\r\n title,\r\n lanes,\r\n unscheduled,\r\n // Print refits this scale to the page; PNG uses it as is.\r\n scale,\r\n looks,\r\n theme: palette(props.theme, props.accent, forPrint),\r\n today: todayDay,\r\n printedOn: dayLabel(localToday()),\r\n showDependencies: !!props.showDependencies,\r\n mode,\r\n fiscalYearStart: props.fiscalYearStart\r\n });\r\n // Copy diagnostics: starts inside the click (browsers need the gesture), then closes the menu.\r\n const copySupport = () => {\r\n const host = (props.diagnostics || undefined)?.() ?? {};\r\n void copyDiagnostics({\r\n product: 'Plan',\r\n control: 'Timeline',\r\n version: VERSION,\r\n licence: license,\r\n rows: props.items.length,\r\n ...host\r\n }).then((r) => editing.toast({ text: r.message, tone: r.ok ? 'info' : 'error' }));\r\n setExportOpen(false);\r\n };\r\n const printTimeline = () => {\r\n setExportOpen(false);\r\n printDocument(timelinePrintHtml(exportInput(true)), ownerDoc());\r\n announce('Print dialog opened');\r\n };\r\n const downloadPng = () => {\r\n setExportOpen(false);\r\n const ok = exportTimelinePng(exportInput(false), exportFileName(title, isoOfDay(localToday()), 'png', 'roadmap'), ownerDoc());\r\n if (ok) announce('Image downloaded');\r\n else editing.toast({ text: 'Too big for one image. Filter or collapse lanes first, or use Print.', tone: 'info' });\r\n };\r\n\r\n /* ---------- Render pieces ---------- */\r\n\r\n const toggleLane = (key: string) => {\r\n const next = new Set(collapsed);\r\n if (next.has(key)) next.delete(key);\r\n else next.add(key);\r\n setCollapsed(next);\r\n announce(`${key} ${next.has(key) ? 'collapsed' : 'expanded'}`);\r\n };\r\n\r\n const renderItem = (p: Placed, lane: Lane) => {\r\n const look = lookOf(p.id);\r\n const y = LANE_PAD + (lane.row.get(p.id) ?? 0) * rowH;\r\n const isSel = selected === p.id;\r\n const style = { '--bar': look.color, '--bar-text': look.text } as CSSProperties;\r\n const editable = can('move', p.item);\r\n const dim = hl && !isHighlighted(p.id, hl) ? '' : undefined;\r\n const common = {\r\n id: `${uid}-i-${p.id}`,\r\n role: 'gridcell',\r\n 'aria-selected': isSel,\r\n 'data-style': look.style,\r\n 'data-preview': preview?.id === p.id ? '' : undefined,\r\n 'data-edit': editable ? '' : undefined,\r\n 'data-dim': dim,\r\n onPointerDown: (e: React.PointerEvent<HTMLElement>) => onItemPointerDown(e, p),\r\n onMouseMove: props.onHover ? (e: React.MouseEvent<HTMLElement>) => hostHover(p.item, { clientX: e.clientX, clientY: e.clientY, multi: isMultiSelect(e) }) : undefined,\r\n onMouseLeave: props.onHover ? (e: React.MouseEvent<HTMLElement>) => hostHover(null, { clientX: e.clientX, clientY: e.clientY, multi: false }) : undefined,\r\n onContextMenu: props.onContextMenu\r\n ? (e: React.MouseEvent<HTMLElement>) => {\r\n e.preventDefault();\r\n props.onContextMenu!(p.item, { clientX: e.clientX, clientY: e.clientY, multi: isMultiSelect(e) });\r\n }\r\n : undefined,\r\n onDoubleClick: () => props.onOpen?.(p.item)\r\n };\r\n const sr = <span className=\"nf-t-sr\">{words(p, lane)}</span>;\r\n if (p.milestone) {\r\n const cx = scale.x(p.start) + scale.dayWidth / 2;\r\n return (\r\n <React.Fragment key={p.id}>\r\n <div {...common} className=\"nf-t-ms\" style={{ ...style, left: cx - 7, top: y + rowH / 2 - 9 }}>\r\n {sr}\r\n </div>\r\n <span className=\"nf-t-label\" aria-hidden=\"true\" data-dim={dim} style={{ left: cx + 12, top: y + (rowH - labelLineH) / 2 }}>\r\n {p.item.name}\r\n </span>\r\n </React.Fragment>\r\n );\r\n }\r\n const x0 = scale.x(p.start);\r\n const w = Math.max(6, scale.x(p.end + 1) - x0);\r\n const inside = w >= (LABEL_INSIDE_MIN * textScale);\r\n const resizable = can('resize', p.item);\r\n return (\r\n <React.Fragment key={p.id}>\r\n <div {...common} className=\"nf-t-bar\" style={{ ...style, left: x0, width: w, top: y + 4 }}>\r\n {sr}\r\n {inside ? <span className=\"nf-t-barname\" aria-hidden=\"true\">{p.item.name}</span> : null}\r\n {resizable ? (\r\n <>\r\n <span className=\"nf-t-h\" data-edge=\"start\" aria-hidden=\"true\" />\r\n <span className=\"nf-t-h\" data-edge=\"end\" aria-hidden=\"true\" />\r\n </>\r\n ) : null}\r\n </div>\r\n {!inside ? (\r\n <span className=\"nf-t-label\" aria-hidden=\"true\" data-dim={dim} style={{ left: x0 + w + 6, top: y + 8 }}>\r\n {p.item.name}\r\n </span>\r\n ) : null}\r\n {preview?.id === p.id ? (\r\n <div className=\"nf-t-draglabel\" aria-hidden=\"true\" style={{ left: x0, top: Math.max(0, y - 22) }}>\r\n {rangeLabel(p.start, p.end)}\r\n </div>\r\n ) : null}\r\n </React.Fragment>\r\n );\r\n };\r\n\r\n // Read-only dependency connectors: from the end of the predecessor to the start of the item.\r\n const connectors = () => {\r\n if (!props.showDependencies) return null;\r\n const paths: string[] = [];\r\n for (const { p, lane } of placedById.values()) {\r\n if (lane.collapsed) continue;\r\n for (const dep of p.item.dependsOn ?? []) {\r\n const from = placedById.get(dep);\r\n if (!from || from.lane.collapsed) continue;\r\n const fy = from.lane.top + LANE_PAD + (from.lane.row.get(from.p.id) ?? 0) * rowH + rowH / 2;\r\n const ty = lane.top + LANE_PAD + (lane.row.get(p.id) ?? 0) * rowH + rowH / 2;\r\n const fx = from.p.milestone ? scale.x(from.p.start) + scale.dayWidth / 2 + 7 : scale.x(from.p.end + 1);\r\n const tx = p.milestone ? scale.x(p.start) + scale.dayWidth / 2 - 7 : scale.x(p.start);\r\n paths.push(connectorPath(fx, fy, tx, ty, rowH));\r\n }\r\n }\r\n if (!paths.length) return null;\r\n return (\r\n <svg className=\"nf-t-deps\" width={scale.width} height={bodyHeight} aria-hidden=\"true\">\r\n {paths.map((d, i) => (\r\n <path key={i} d={d} markerEnd={`url(#${uid}-arrow)`} />\r\n ))}\r\n <defs>\r\n <marker id={`${uid}-arrow`} viewBox=\"0 0 6 6\" refX=\"5\" refY=\"3\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto\">\r\n <path d=\"M0,0 L6,3 L0,6 z\" style={{ fill: 'var(--nf-muted)', stroke: 'none' }} />\r\n </marker>\r\n </defs>\r\n </svg>\r\n );\r\n };\r\n\r\n const legendEntries = useMemo(() => {\r\n const out = new Map<string, Appearance>();\r\n for (const it of shown) {\r\n const key = colorBy === 'lane' ? (it.lane ?? '').trim() || NO_LANE : (it.status ?? '').trim();\r\n if (key && !out.has(key)) out.set(key, lookOf(it.id));\r\n }\r\n return [...out.entries()];\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [shown, looks, colorBy]);\r\n\r\n const style = {\r\n ...resolveTheme(props.theme, props.accent, props.colors),\r\n ...textVars(textScale, props.textSizes),\r\n '--nf-row': `${rowH}px`,\r\n '--nf-lanehead': `${laneHeadW}px`,\r\n '--nf-head': `${scaleWithText(HEAD_H, textScale)}px`,\r\n height: props.height ?? '100%'\r\n } as CSSProperties;\r\n const showToolbar = props.showToolbar !== false;\r\n const todayX = todayDay >= scale.startDay && todayDay < scale.endDay ? scale.x(todayDay) + scale.dayWidth / 2 : null;\r\n const totalItems = shown.length;\r\n\r\n return (\r\n <div\r\n ref={rootRef}\r\n dir=\"ltr\"\r\n data-rtl={rtl ? '' : undefined}\r\n data-narrow={narrow ? '' : undefined}\r\n data-static={props.animate === false ? '' : undefined}\r\n data-hl={hl ? '' : undefined}\r\n data-months={scale.months.length ? '' : undefined}\r\n className={`nf-tl${settled ? ' nf-t-settled' : ''}${dragging ? ' nf-t-dragging' : ''}${props.className ? ' ' + props.className : ''}`}\r\n style={style}\r\n >\r\n {/* Raw CSS: a text child gets HTML-escaped when server-rendered (quotes become "). */}\r\n <style dangerouslySetInnerHTML={{ __html: css }} />\r\n\r\n {showToolbar ? (\r\n <div className=\"nf-t-toolbar\" dir={dirAttr}>\r\n <div className=\"nf-t-title\">{title}</div>\r\n <div className=\"nf-t-seg\" role=\"group\" aria-label=\"Mode\">\r\n {MODES.map((m) => (\r\n <button key={m.id} type=\"button\" className=\"nf-t-btn\" aria-pressed={m.id === mode} onClick={() => setMode(m.id)}>\r\n {m.label}\r\n </button>\r\n ))}\r\n </div>\r\n {mode === 'dates' ? (\r\n <>\r\n <div className=\"nf-t-seg\" role=\"group\" aria-label=\"Zoom\">\r\n {ZOOMS.map((z) => (\r\n <button key={z.id} type=\"button\" className=\"nf-t-btn\" aria-pressed={z.id === zoom} onClick={() => setZoom(z.id)}>\r\n {z.label}\r\n </button>\r\n ))}\r\n </div>\r\n <button type=\"button\" className=\"nf-t-btn\" onClick={goToday}>\r\n Today\r\n </button>\r\n </>\r\n ) : null}\r\n <div className=\"nf-t-spacer\" />\r\n <input\r\n className=\"nf-t-filter\"\r\n type=\"search\"\r\n placeholder=\"Filter items\"\r\n aria-label=\"Filter items by name, lane or status\"\r\n value={filter}\r\n onChange={(e) => setFilter(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && filter) {\r\n setFilter('');\r\n e.preventDefault();\r\n }\r\n }}\r\n />\r\n {mode === 'dates' && unscheduled.length ? (\r\n <button type=\"button\" className=\"nf-t-btn\" aria-pressed={trayOpen} aria-controls={trayOpen ? `${uid}-tray` : undefined} onClick={() => setTrayOpen(!trayOpen)}>\r\n Not scheduled<span className=\"nf-t-count\">{unscheduled.length}</span>\r\n </button>\r\n ) : null}\r\n {props.exports !== false ? (\r\n <div className=\"nf-t-menuwrap\" ref={exportRef} onKeyDown={exportMenuKeys}>\r\n <button type=\"button\" className=\"nf-t-btn\" aria-haspopup=\"menu\" aria-expanded={exportOpen} onClick={() => setExportOpen(!exportOpen)}>\r\n Export\r\n </button>\r\n {exportOpen ? (\r\n <div className=\"nf-t-menu\" role=\"menu\" aria-label=\"Export\">\r\n <button type=\"button\" role=\"menuitem\" tabIndex={-1} onClick={printTimeline}>\r\n Print…\r\n </button>\r\n {mode === 'dates' ? (\r\n <button type=\"button\" role=\"menuitem\" tabIndex={-1} onClick={downloadPng}>\r\n Download PNG\r\n </button>\r\n ) : null}\r\n {props.diagnostics !== false ? (\r\n <button type=\"button\" role=\"menuitem\" tabIndex={-1} onClick={copySupport}>\r\n Copy diagnostics\r\n </button>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n\r\n <div className=\"nf-t-main\">\r\n {mode === 'horizon' ? (\r\n <HorizonBoard\r\n items={shown.filter((i) => [i.name, i.lane, i.status].some((v) => (v ?? '').toLowerCase().includes(filter.trim().toLowerCase())))}\r\n laneOrder={props.laneOrder}\r\n looks={looks}\r\n selected={selected}\r\n onSelect={(id) => select(id)}\r\n onOpen={props.onOpen}\r\n dir={dirAttr}\r\n label={`${title}, now, next and later`}\r\n />\r\n ) : (\r\n <div\r\n ref={scrollRef}\r\n className=\"nf-t-scroll\"\r\n role=\"grid\"\r\n aria-label={`${title}, ${scale.minor[0]?.label ?? ''} to ${scale.minor[scale.minor.length - 1]?.label ?? ''}`}\r\n aria-busy={props.loading ? true : undefined}\r\n aria-rowcount={lanes.length + 1}\r\n aria-activedescendant={selected && placedById.has(selected) ? `${uid}-i-${selected}` : undefined}\r\n aria-describedby={`${uid}-help`}\r\n tabIndex={0}\r\n onKeyDown={onGridKeyDown}\r\n >\r\n <div className=\"nf-t-head\" role=\"row\" aria-rowindex={1} style={{ width: laneHeadW + scale.width, height: headH }}>\r\n <div className=\"nf-t-corner\" role=\"columnheader\" dir={dirAttr}>\r\n Lanes\r\n </div>\r\n <div className=\"nf-t-axis\" role=\"columnheader\" style={{ width: scale.width, height: headH }}>\r\n <span className=\"nf-t-sr\">{`${scale.minor[0]?.label ?? ''} to ${scale.minor[scale.minor.length - 1]?.label ?? ''}`}</span>\r\n {scale.major.map((t) => (\r\n <div key={`M${t.day}`} className=\"nf-t-major\" aria-hidden=\"true\" style={{ left: t.x, width: t.width }}>\r\n {t.label}\r\n </div>\r\n ))}\r\n {scale.minor.map((t) => (\r\n <div key={`m${t.day}`} className=\"nf-t-minor\" aria-hidden=\"true\" style={{ left: t.x, width: t.width }}>\r\n {t.label}\r\n </div>\r\n ))}\r\n {scale.months.map((t) => (\r\n <div key={`mo${t.day}`} className=\"nf-t-month\" aria-hidden=\"true\" style={{ left: t.x, width: t.width }}>\r\n {monthLabel(t.label, t.width, textSize('caption', textScale, props.textSizes))}\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n <div className=\"nf-t-body\" style={{ height: Math.max(bodyHeight, 120), width: laneHeadW + scale.width }}>\r\n <div className=\"nf-t-layer\" aria-hidden=\"true\" style={{ left: laneHeadW, width: scale.width }}>\r\n {scale.minor.map((t) => (\r\n <div key={`v${t.day}`} className=\"nf-t-vline\" data-major={scale.major.some((m) => m.day === t.day) ? '' : undefined} style={{ left: t.x }} />\r\n ))}\r\n {todayX !== null ? <div className=\"nf-t-today\" style={{ left: todayX }} /> : null}\r\n </div>\r\n\r\n {lanes.map((lane) => (\r\n <div\r\n key={lane.key}\r\n className=\"nf-t-lane\"\r\n role=\"row\"\r\n aria-rowindex={lane.index + 2}\r\n data-alt={lane.index % 2 ? '' : undefined}\r\n style={{ top: lane.top, height: lane.height, width: laneHeadW + scale.width }}\r\n >\r\n <div className=\"nf-t-lanehead\" role=\"rowheader\" dir={dirAttr} style={{ height: lane.height }}>\r\n <button type=\"button\" className=\"nf-t-lanebtn\" aria-expanded={!lane.collapsed} tabIndex={-1} onClick={() => toggleLane(lane.key)}>\r\n <Chevron />\r\n <span>{lane.label}</span>\r\n <span className=\"nf-t-lanecount\">{lane.items.length}</span>\r\n </button>\r\n </div>\r\n <div className=\"nf-t-track\" style={{ width: scale.width, height: lane.height }}>\r\n {lane.collapsed ? null : lane.items.map((p) => renderItem(p, lane))}\r\n </div>\r\n </div>\r\n ))}\r\n\r\n <div className=\"nf-t-depwrap\" style={{ left: laneHeadW }}>{connectors()}</div>\r\n\r\n {lanes.length === 0 ? (\r\n <div className=\"nf-t-empty\" role=\"status\">\r\n {props.loading ? 'Loading items…' : filter ? `No items match “${filter}”.` : totalItems ? 'No items have dates yet.' : 'No items yet.'}\r\n {props.emptyAction && !props.loading && !filter && !totalItems ? <EmptyActionView action={props.emptyAction} prefix=\"nf-t\" buttonClass=\"nf-t-btn\" /> : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {mode === 'dates' && trayOpen && unscheduled.length ? (\r\n <aside className=\"nf-t-tray\" id={`${uid}-tray`} aria-label=\"Not scheduled\" dir={dirAttr}>\r\n <div className=\"nf-t-trayhead\">\r\n Not scheduled\r\n <span className=\"nf-t-count\">{unscheduled.length}</span>\r\n </div>\r\n <ul className=\"nf-t-traylist\">\r\n {unscheduled.map((it) => {\r\n const look = lookOf(it.id);\r\n return (\r\n <li key={it.id}>\r\n <button\r\n type=\"button\"\r\n className=\"nf-t-trayitem\"\r\n aria-pressed={selected === it.id}\r\n style={{ '--bar': look.color } as CSSProperties}\r\n onClick={() => select(it.id)}\r\n onDoubleClick={() => props.onOpen?.(it)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') {\r\n e.preventDefault();\r\n props.onOpen?.(it);\r\n }\r\n }}\r\n >\r\n {it.name}\r\n <small>{[it.lane || NO_LANE, it.status, it.horizon].filter(Boolean).join(' · ')}</small>\r\n </button>\r\n </li>\r\n );\r\n })}\r\n </ul>\r\n </aside>\r\n ) : null}\r\n </div>\r\n\r\n {legendEntries.length ? (\r\n <div className=\"nf-t-legend\" dir={dirAttr} aria-label=\"Legend\">\r\n {legendEntries.map(([key, look]) => (\r\n <span key={key}>\r\n <i aria-hidden=\"true\" data-style={look.style} style={{ '--sw': look.color } as CSSProperties} />\r\n {key}\r\n </span>\r\n ))}\r\n {mode === 'dates' ? (\r\n <span>\r\n <i aria-hidden=\"true\" style={{ '--sw': 'var(--nf-ink)', width: 9, height: 9, transform: 'rotate(45deg)', borderRadius: 2 } as CSSProperties} />\r\n Milestone\r\n </span>\r\n ) : null}\r\n {mode === 'dates' && todayX !== null ? (\r\n <span>\r\n <i aria-hidden=\"true\" style={{ '--sw': 'var(--nf-now)', width: 2, height: 12, borderRadius: 0 } as CSSProperties} />\r\n Today\r\n </span>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n\r\n <div id={`${uid}-help`} className=\"nf-t-sr\">\r\n Arrow keys move between items: left and right along a lane, up and down to the next lane. Enter opens.\r\n {props.onItemChange\r\n ? ' Alt plus left or right moves the selected item by a week at month zoom, or a month at quarter and half-year zoom. Alt plus Shift plus left or right changes its end. Control plus Z undoes.'\r\n : ''}\r\n </div>\r\n <Toasts prefix=\"nf-t\" items={editing.toasts} announcement={editing.announcement} onDismiss={editing.dismiss} />\r\n\r\n {license && !license.licensed && props.watermark !== false ? (\r\n <Watermark prefix=\"nf-t\" title={licenseMessage(license)} markHtml={markSvg({ size: 16 })} variant={props.watermarkStyle} onClick={props.onWatermarkClick} />\r\n ) : null}\r\n {editing.lockNote ? (\r\n <p className=\"nf-t-locknote\" role=\"status\">\r\n {editing.lockNote}\r\n </p>\r\n ) : null}\r\n {renewal ? (\r\n <a className=\"nf-t-renew\" href={renewal.href} target=\"_blank\" rel=\"noreferrer\" title={renewal.detail}>\r\n {renewal.text}\r\n </a>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n\r\n/** Now / Next / Later: lanes as rows, horizons as columns, items as cards (read-only in v1). */\r\nfunction HorizonBoard(props: {\r\n items: TimelineItem[];\r\n laneOrder?: string[];\r\n looks: ReadonlyMap<string, Appearance>;\r\n selected: string | null;\r\n onSelect: (id: string) => void;\r\n onOpen?: (item: TimelineItem) => void;\r\n dir?: 'rtl';\r\n label: string;\r\n}) {\r\n const cols = horizonColumns(props.items);\r\n const laneSet = new Set(props.items.map((i) => (i.lane ?? '').trim() || NO_LANE));\r\n const ordered = (props.laneOrder ?? []).filter((l) => laneSet.has(l));\r\n const lanes = [...ordered, ...[...laneSet].filter((l) => !ordered.includes(l) && l !== NO_LANE).sort((a, b) => a.localeCompare(b)), ...(laneSet.has(NO_LANE) ? [NO_LANE] : [])];\r\n const template = `minmax(140px, 200px) repeat(${cols.length}, minmax(160px, 1fr))`;\r\n return (\r\n <div className=\"nf-t-nnl\" role=\"region\" aria-label={props.label} dir={props.dir}>\r\n <div className=\"nf-t-nnlgrid\" style={{ gridTemplateColumns: template }}>\r\n <div className=\"nf-t-nnlhead\">Lane</div>\r\n {cols.map((c) => (\r\n <div key={c.key} className=\"nf-t-nnlhead\">\r\n {c.key}\r\n <span className=\"nf-t-lanecount\">{c.items.length}</span>\r\n </div>\r\n ))}\r\n {lanes.map((lane, li) => (\r\n <React.Fragment key={lane}>\r\n <div className=\"nf-t-nnllane\" data-alt={li % 2 ? '' : undefined}>\r\n {lane}\r\n </div>\r\n {cols.map((c) => {\r\n const cards = c.items.filter((i) => ((i.lane ?? '').trim() || NO_LANE) === lane);\r\n return (\r\n <div key={c.key} className=\"nf-t-nnlcell\" data-alt={li % 2 ? '' : undefined} role=\"group\" aria-label={`${lane}, ${c.key}`}>\r\n {cards.map((it) => {\r\n const look = props.looks.get(it.id);\r\n const p = place(it);\r\n const when = p ? (p.milestone ? dayLabel(p.start) : rangeLabel(p.start, p.end)) : 'Not scheduled';\r\n return (\r\n <button\r\n key={it.id}\r\n type=\"button\"\r\n className=\"nf-t-card\"\r\n aria-pressed={props.selected === it.id}\r\n style={{ '--bar': look?.color } as CSSProperties}\r\n onClick={() => props.onSelect(it.id)}\r\n onDoubleClick={() => props.onOpen?.(it)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') {\r\n e.preventDefault();\r\n props.onOpen?.(it);\r\n }\r\n }}\r\n >\r\n {it.name}\r\n <small>{[it.status, when].filter(Boolean).join(' · ')}</small>\r\n </button>\r\n );\r\n })}\r\n </div>\r\n );\r\n })}\r\n </React.Fragment>\r\n ))}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","/** This package's version, for Copy diagnostics. `version.test.ts` keeps it equal to package.json. */\r\nexport const VERSION = '1.0.0-rc.1';\r\n","/**\r\n * \"Copy diagnostics\": one click copies what support needs to reproduce a problem, and nothing\r\n * else. Product and control version, licence state (never the key), host, browser, locale, time\r\n * zone, the mapped property names and the row count. No data values and no personal data: the\r\n * input types only take names and counts, and every string is trimmed to one short line.\r\n *\r\n * Framework-free. The drop-in menu item is `CopyDiagnosticsItem` in `@ninthforge/core/react`.\r\n */\r\n\r\n/** Where the control runs. PCF wrappers and Power BI visuals pass it when they know. */\r\nexport type DiagnosticsHost =\r\n | 'model-driven'\r\n | 'canvas'\r\n | 'canvas (Studio)'\r\n | 'custom page'\r\n | 'Power BI'\r\n | 'code app'\r\n | 'web page';\r\n\r\n/**\r\n * The licence as diagnostics reads it: a `LicenseResult` from `@ninthforge/license` fits. Only\r\n * `status`, `payload.tier`, `payload.org` and `payload.exp` are read; the key itself never\r\n * reaches this function, and the tenant ID and key id are left out.\r\n */\r\nexport interface DiagnosticsLicence {\r\n status: string;\r\n payload?: { tier?: string; org?: string; exp?: string } | null;\r\n}\r\n\r\nexport interface DiagnosticsInput {\r\n /** The product, e.g. `Plan`, `Charts`. */\r\n product: string;\r\n /** The control, e.g. `Gantt` or `nf_NinthForge.PlanGantt`. */\r\n control: string;\r\n /** The control or package version, e.g. `0.4.4`. */\r\n version: string;\r\n licence?: DiagnosticsLicence | null;\r\n /** Overrides detection (Power BI visuals, code apps, or a PCF that knows its host). */\r\n host?: DiagnosticsHost;\r\n /**\r\n * Mapped properties: `{ taskStart: 'nf_start' }` gives `taskStart → nf_start`, and a list\r\n * gives the names alone. Names only, never values.\r\n */\r\n properties?: Readonly<Record<string, string | null | undefined>> | readonly string[];\r\n /** Rows the control received. */\r\n rows?: number;\r\n /**\r\n * A few product-specific settings, e.g. `{ 'Save mode': 'direct' }`. Settings only: never\r\n * record values, user names or e-mail addresses.\r\n */\r\n extra?: Readonly<Record<string, string | number | boolean | null | undefined>>;\r\n now?: Date;\r\n}\r\n\r\n/**\r\n * What a host adds to a component's own diagnostics (the component fills product, version,\r\n * licence and its row count): a PCF passes its control name, mapped properties and settings.\r\n */\r\nexport type DiagnosticsHostInfo = Partial<Pick<DiagnosticsInput, 'control' | 'version' | 'host' | 'properties' | 'rows' | 'extra'>>;\r\n\r\nexport interface Diagnostics {\r\n product: string;\r\n control: string;\r\n version: string;\r\n licence: { state: string; org?: string; expires?: string };\r\n host: DiagnosticsHost;\r\n browser: string;\r\n locale: string;\r\n timeZone: string;\r\n properties: string[];\r\n rows?: number;\r\n extra: [string, string][];\r\n /** When it was copied, UTC ISO. */\r\n at: string;\r\n}\r\n\r\n/** The parts of `window` diagnostics reads; tests pass a fake. */\r\nexport interface DiagnosticsEnv {\r\n navigator?: {\r\n userAgent?: string;\r\n language?: string;\r\n languages?: readonly string[];\r\n userAgentData?: { brands?: { brand: string; version: string }[]; platform?: string };\r\n };\r\n location?: { hostname?: string; ancestorOrigins?: ArrayLike<string> };\r\n Xrm?: unknown;\r\n}\r\n\r\nconst MAX = 80;\r\n\r\n/** One short line: no control characters or new lines, at most 80 characters. */\r\nfunction clean(value: unknown): string {\r\n const s = String(value ?? '')\r\n .replace(/[\\u0000-\\u001f\\u007f]+/g, ' ')\r\n .trim();\r\n return s.length > MAX ? `${s.slice(0, MAX - 1)}…` : s;\r\n}\r\n\r\nfunction envOf(win?: DiagnosticsEnv): DiagnosticsEnv {\r\n if (win) return win;\r\n return typeof window === 'undefined' ? {} : (window as unknown as DiagnosticsEnv);\r\n}\r\n\r\nfunction ancestors(env: DiagnosticsEnv): string[] {\r\n try {\r\n const list = env.location?.ancestorOrigins;\r\n return list ? Array.from(list, String) : [];\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\nfunction hostnameOf(origin: string): string {\r\n return origin.replace(/^[a-z]+:\\/\\//i, '').replace(/[:/].*$/, '').toLowerCase();\r\n}\r\n\r\nconst endsWith = (host: string, suffix: string) => host === suffix || host.endsWith(`.${suffix}`);\r\n\r\n/**\r\n * Best-effort host detection, for when the wrapper doesn't say. Model-driven pages have `Xrm`\r\n * (a custom page says `pageType: 'custom'`). A canvas player inside a Dynamics page is a custom\r\n * page, and inside Power BI it's the Power Apps visual. Studio runs on `authoring.*.powerapps.com`.\r\n */\r\nexport function detectHost(win?: DiagnosticsEnv): DiagnosticsHost {\r\n const env = envOf(win);\r\n const xrm = env.Xrm as { Utility?: { getPageContext?: () => { input?: { pageType?: string } } } } | undefined;\r\n if (xrm) {\r\n try {\r\n if (xrm.Utility?.getPageContext?.()?.input?.pageType === 'custom') return 'custom page';\r\n } catch {\r\n /* not a page context we know */\r\n }\r\n return 'model-driven';\r\n }\r\n const up = ancestors(env).map(hostnameOf);\r\n if (up.some((h) => endsWith(h, 'powerbi.com') || endsWith(h, 'fabric.microsoft.com'))) return 'Power BI';\r\n const host = (env.location?.hostname ?? '').toLowerCase();\r\n if (endsWith(host, 'powerbi.com')) return 'Power BI';\r\n if (up.some((h) => endsWith(h, 'dynamics.com'))) return 'custom page';\r\n if (host.startsWith('authoring.') && endsWith(host, 'powerapps.com')) return 'canvas (Studio)';\r\n if (endsWith(host, 'powerapps.com') || endsWith(host, 'powerplatformusercontent.com')) return 'canvas';\r\n return 'web page';\r\n}\r\n\r\nconst BRANDS: [RegExp, string][] = [\r\n [/Edg(?:e|A|iOS)?\\/(\\d+)/, 'Edge'],\r\n [/OPR\\/(\\d+)/, 'Opera'],\r\n [/Firefox\\/(\\d+)/, 'Firefox'],\r\n [/FxiOS\\/(\\d+)/, 'Firefox'],\r\n [/CriOS\\/(\\d+)/, 'Chrome'],\r\n [/Chrome\\/(\\d+)/, 'Chrome'],\r\n [/Version\\/(\\d+)[.\\d]* (?:Mobile\\/\\S+ )?Safari\\//, 'Safari']\r\n];\r\n\r\nconst PLATFORMS: [RegExp, string][] = [\r\n [/iPhone|iPad|iPod/, 'iOS'],\r\n [/Android/, 'Android'],\r\n [/Windows/, 'Windows'],\r\n [/Mac OS X|Macintosh/, 'macOS'],\r\n [/CrOS/, 'ChromeOS'],\r\n [/Linux/, 'Linux']\r\n];\r\n\r\n/** \"Edge 131 on Windows\": brand, major version and platform only, not the full user agent. */\r\nexport function describeBrowser(win?: DiagnosticsEnv): string {\r\n const nav = envOf(win).navigator;\r\n const ua = nav?.userAgent ?? '';\r\n let name = '';\r\n for (const [re, brand] of BRANDS) {\r\n const m = re.exec(ua);\r\n if (m) {\r\n name = `${brand} ${m[1]}`;\r\n break;\r\n }\r\n }\r\n if (!name) {\r\n const brands = nav?.userAgentData?.brands ?? [];\r\n const b = brands.find((x) => !/not.?a.?brand|chromium/i.test(x.brand)) ?? brands[0];\r\n name = b ? `${b.brand.replace('Microsoft ', '')} ${b.version}` : 'Unknown browser';\r\n }\r\n const platform = PLATFORMS.find(([re]) => re.test(ua))?.[1] ?? nav?.userAgentData?.platform;\r\n return clean(platform ? `${name} on ${platform}` : name);\r\n}\r\n\r\nfunction offsetLabel(minutes: number): string {\r\n const sign = minutes < 0 ? '−' : '+';\r\n const abs = Math.abs(minutes);\r\n return `UTC${sign}${String(Math.floor(abs / 60)).padStart(2, '0')}:${String(abs % 60).padStart(2, '0')}`;\r\n}\r\n\r\nfunction licenceOf(l: DiagnosticsLicence | null | undefined): Diagnostics['licence'] {\r\n if (!l) return { state: 'not checked' };\r\n const p = l.payload ?? undefined;\r\n let state = clean(l.status) || 'unknown';\r\n if ((state === 'valid' || state === 'unverifiable') && p?.tier === 'trial') state = 'trial';\r\n else if (state === 'unverifiable') state = 'valid (browser cannot verify)';\r\n const out: Diagnostics['licence'] = { state };\r\n if (p?.org) out.org = clean(p.org);\r\n if (p?.exp && /^\\d{4}-\\d{2}-\\d{2}$/.test(p.exp)) out.expires = p.exp;\r\n return out;\r\n}\r\n\r\nfunction propertiesOf(p: DiagnosticsInput['properties']): string[] {\r\n if (!p) return [];\r\n if (Array.isArray(p)) return (p as readonly string[]).map(clean).filter(Boolean);\r\n return Object.keys(p)\r\n .map((name) => {\r\n const column = (p as Record<string, string | null | undefined>)[name];\r\n return column ? `${clean(name)} → ${clean(column)}` : `${clean(name)} (not mapped)`;\r\n })\r\n .filter(Boolean);\r\n}\r\n\r\n/** Gather the diagnostics. Never throws. */\r\nexport function collectDiagnostics(input: DiagnosticsInput, win?: DiagnosticsEnv): Diagnostics {\r\n const env = envOf(win);\r\n const now = input.now ?? new Date();\r\n let locale = clean(env.navigator?.language);\r\n let zone = '';\r\n try {\r\n const opts = Intl.DateTimeFormat().resolvedOptions();\r\n if (!locale) locale = opts.locale;\r\n else if (opts.locale && opts.locale !== locale) locale = `${locale} (formats ${opts.locale})`;\r\n zone = opts.timeZone ?? '';\r\n } catch {\r\n /* Intl missing: leave blank */\r\n }\r\n // The offset itself, not a date: the one place a local-time getter is what we want.\r\n const offset = offsetLabel(-now.getTimezoneOffset());\r\n const extra: [string, string][] = Object.keys(input.extra ?? {})\r\n .filter((k) => input.extra![k] !== undefined && input.extra![k] !== null)\r\n .map((k) => [clean(k), clean(input.extra![k])]);\r\n const d: Diagnostics = {\r\n product: clean(input.product),\r\n control: clean(input.control),\r\n version: clean(input.version),\r\n licence: licenceOf(input.licence),\r\n host: input.host ?? detectHost(env),\r\n browser: describeBrowser(env),\r\n locale: locale || 'unknown',\r\n timeZone: zone ? `${clean(zone)} (${offset})` : offset,\r\n properties: propertiesOf(input.properties),\r\n extra,\r\n at: now.toISOString().replace(/\\.\\d{3}Z$/, 'Z')\r\n };\r\n if (typeof input.rows === 'number' && Number.isFinite(input.rows)) d.rows = Math.max(0, Math.round(input.rows));\r\n return d;\r\n}\r\n\r\n/** Plain text for an e-mail to support. */\r\nexport function formatDiagnostics(d: Diagnostics): string {\r\n const lic = [d.licence.state, d.licence.org, d.licence.expires && `expires ${d.licence.expires}`]\r\n .filter(Boolean)\r\n .join(' · ');\r\n const lines = [\r\n 'Ninth Forge diagnostics',\r\n `Product: ${d.product} · ${d.control} ${d.version}`,\r\n `Licence: ${lic}`,\r\n `Host: ${d.host}`,\r\n `Browser: ${d.browser}`,\r\n `Locale: ${d.locale}`,\r\n `Time zone: ${d.timeZone}`,\r\n `Properties: ${d.properties.length ? d.properties.join(', ') : 'none mapped'}`\r\n ];\r\n if (d.rows !== undefined) lines.push(`Rows: ${d.rows}`);\r\n for (const [k, v] of d.extra) lines.push(`${k}: ${v}`);\r\n lines.push(`Copied: ${d.at}`);\r\n return lines.join('\\n');\r\n}\r\n\r\n/** The parts of `window` copying needs; tests pass a fake. */\r\nexport interface ClipboardEnv {\r\n navigator?: { clipboard?: { writeText?: (text: string) => Promise<void> } };\r\n document?: Document;\r\n}\r\n\r\n/**\r\n * Copy text to the clipboard. Tries the Clipboard API, then the `execCommand('copy')` fallback\r\n * (canvas apps and Power BI run controls in iframes where the Clipboard API is often blocked).\r\n * Call it straight from a click: both need the user's gesture. Focus goes back where it was.\r\n */\r\nexport async function copyText(text: string, win?: ClipboardEnv): Promise<boolean> {\r\n const env: ClipboardEnv = win ?? (typeof window === 'undefined' ? {} : (window as unknown as ClipboardEnv));\r\n const fallback = (): boolean => {\r\n const doc = env.document;\r\n if (!doc?.body) return false;\r\n const active = doc.activeElement as HTMLElement | null;\r\n const area = doc.createElement('textarea');\r\n area.value = text;\r\n area.setAttribute('readonly', '');\r\n area.setAttribute('aria-hidden', 'true');\r\n area.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none';\r\n doc.body.appendChild(area);\r\n let ok = false;\r\n try {\r\n area.select();\r\n ok = doc.execCommand('copy');\r\n } catch {\r\n ok = false;\r\n }\r\n area.remove();\r\n active?.focus?.();\r\n return ok;\r\n };\r\n const write = env.navigator?.clipboard?.writeText;\r\n if (write) {\r\n try {\r\n // Started synchronously, inside the click.\r\n await write.call(env.navigator!.clipboard, text);\r\n return true;\r\n } catch {\r\n return fallback();\r\n }\r\n }\r\n return fallback();\r\n}\r\n\r\nexport interface CopyDiagnosticsResult {\r\n ok: boolean;\r\n /** The copied text, so a product can show it to copy by hand if the clipboard refused. */\r\n text: string;\r\n /** \"Diagnostics copied\", or what to do instead. For a toast or a live region. */\r\n message: string;\r\n}\r\n\r\nexport const DIAGNOSTICS_COPIED = 'Diagnostics copied. Paste them into your e-mail to support.';\r\nexport const DIAGNOSTICS_NOT_COPIED = \"Couldn't copy diagnostics. Your browser blocked the clipboard.\";\r\n\r\n/** Collect, format and copy. Call it straight from a click. */\r\nexport async function copyDiagnostics(\r\n input: DiagnosticsInput,\r\n win?: DiagnosticsEnv & ClipboardEnv\r\n): Promise<CopyDiagnosticsResult> {\r\n const text = formatDiagnostics(collectDiagnostics(input, win));\r\n const ok = await copyText(text, win);\r\n return { ok, text, message: ok ? DIAGNOSTICS_COPIED : DIAGNOSTICS_NOT_COPIED };\r\n}\r\n","/**\n * Dates are ISO `YYYY-MM-DD` strings, handled as whole UTC days.\n * Internally a date is a day number: days since 1970-01-01.\n * Never use local-time getters on these — they shift a day west of Greenwich.\n */\n\nconst DAY_MS = 86_400_000;\nconst ISO = /^(\\d{4})-(\\d{2})-(\\d{2})/;\n\nexport const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] as const;\nexport const MONTHS_LONG = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December'\n] as const;\nexport const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const;\n\n/** ISO date (or datetime — the time is ignored) → day number. NaN when invalid. */\nexport function toDay(iso: string | null | undefined): number {\n if (!iso) return NaN;\n const m = ISO.exec(iso);\n if (!m) return NaN;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const d = Number(m[3]);\n if (mo < 1 || mo > 12 || d < 1 || d > 31) return NaN;\n return Math.round(Date.UTC(y, mo - 1, d) / DAY_MS);\n}\n\n/** Day number → ISO date. */\nexport function fromDay(day: number): string {\n return new Date(day * DAY_MS).toISOString().slice(0, 10);\n}\n\nexport function addDays(iso: string, n: number): string {\n return fromDay(toDay(iso) + n);\n}\n\n/** Inclusive length in days of a start/end pair (a one-day task is 1). */\nexport function spanDays(startIso: string, endIso: string): number {\n return toDay(endIso) - toDay(startIso) + 1;\n}\n\n/** 0 = Monday … 6 = Sunday. 1970-01-01 was a Thursday. */\nexport function weekday(day: number): number {\n return (((day + 3) % 7) + 7) % 7;\n}\n\nexport function parts(day: number): { year: number; month: number; date: number } {\n const d = new Date(day * DAY_MS);\n return { year: d.getUTCFullYear(), month: d.getUTCMonth(), date: d.getUTCDate() };\n}\n\nexport function dayOf(year: number, month: number, date = 1): number {\n return Math.round(Date.UTC(year, month, date) / DAY_MS);\n}\n\nexport function startOfWeek(day: number): number {\n return day - weekday(day);\n}\n\nexport function startOfMonth(day: number): number {\n const p = parts(day);\n return dayOf(p.year, p.month, 1);\n}\n\nexport function addMonths(day: number, n: number): number {\n const p = parts(day);\n return dayOf(p.year, p.month + n, 1);\n}\n\nexport function startOfQuarter(day: number): number {\n const p = parts(day);\n return dayOf(p.year, Math.floor(p.month / 3) * 3, 1);\n}\n\n/** The viewer's local \"today\" as a day number — the one place local time is right. */\nexport function today(now: Date = new Date()): number {\n return dayOf(now.getFullYear(), now.getMonth(), now.getDate());\n}\n\n/** \"Oct 12\" */\nexport function formatShort(day: number): string {\n const p = parts(day);\n return `${MONTHS[p.month]} ${p.date}`;\n}\n\n/** \"12 Oct 2026\" */\nexport function formatLong(day: number): string {\n const p = parts(day);\n return `${p.date} ${MONTHS[p.month]} ${p.year}`;\n}\n\n/* ---------- Working days ---------- */\n\n/** Which days count as working days. `mon-fri` skips Saturdays and Sundays; `all` counts every day. */\nexport type WorkingWeek = 'mon-fri' | 'all';\n\nexport function isWorkingDay(day: number, week: WorkingWeek = 'mon-fri'): boolean {\n return week === 'all' || weekday(day) < 5;\n}\n\n/** Working days from `start` to `end`, both inclusive. 0 when the span is all weekend or reversed. */\nexport function workingDaysBetween(start: number, end: number, week: WorkingWeek = 'mon-fri'): number {\n if (Number.isNaN(start) || Number.isNaN(end) || end < start) return 0;\n const total = end - start + 1;\n if (week === 'all') return total;\n const full = Math.floor(total / 7);\n let count = full * 5;\n for (let d = start + full * 7; d <= end; d++) if (isWorkingDay(d, week)) count++;\n return count;\n}\n\n/** `day` if it's a working day, else the nearest working day forward (dir ≥ 0) or backward (dir < 0). */\nexport function snapToWorkingDay(day: number, dir: number, week: WorkingWeek = 'mon-fri'): number {\n if (Number.isNaN(day)) return day;\n const step = dir < 0 ? -1 : 1;\n let d = day;\n for (let i = 0; i < 7 && !isWorkingDay(d, week); i++) d += step;\n return d;\n}\n\n/** The day `n` working days after `day` (n ≥ 0), counting from the first working day at or after `day`. */\nexport function addWorkingDays(day: number, n: number, week: WorkingWeek = 'mon-fri'): number {\n let d = snapToWorkingDay(day, 1, week);\n if (week === 'all') return d + n;\n d += Math.floor(n / 5) * 7;\n for (let left = n % 5; left > 0; ) {\n d++;\n if (isWorkingDay(d, week)) left--;\n }\n return d;\n}\n","import {\n addMonths,\n MONTHS,\n MONTHS_LONG,\n parts,\n startOfMonth,\n startOfQuarter,\n startOfWeek,\n weekday\n} from './date';\n\nexport type Zoom = 'week' | 'month' | 'quarter';\n\nexport const ZOOMS: readonly Zoom[] = ['week', 'month', 'quarter'];\n\n/** Pixels per day at each zoom level. */\nexport const DAY_WIDTH: Record<Zoom, number> = { week: 36, month: 9, quarter: 3 };\n\nexport interface Tick {\n day: number;\n x: number;\n width: number;\n label: string;\n}\n\nexport interface TimeScale {\n zoom: Zoom;\n startDay: number;\n /** Exclusive. */\n endDay: number;\n dayWidth: number;\n width: number;\n x(day: number): number;\n /** Day at a pixel offset (for drag, later). */\n dayAt(x: number): number;\n major: Tick[];\n minor: Tick[];\n /** Weekend bands, only at week zoom. */\n weekends: { x: number; width: number }[];\n}\n\nexport interface ScaleInput {\n /** First and last day that must be visible (inclusive). */\n from: number;\n to: number;\n zoom: Zoom;\n /** Extra breathing room after `to`, in days. */\n padDays?: number;\n /** Extend the end until the scale is at least this wide (fills the viewport). */\n minWidth?: number;\n}\n\nfunction snapStart(day: number, zoom: Zoom): number {\n if (zoom === 'week') return startOfWeek(day);\n if (zoom === 'month') return startOfMonth(day);\n return startOfQuarter(day);\n}\n\nfunction snapEnd(day: number, zoom: Zoom): number {\n // Exclusive end on the next boundary after `day`.\n if (zoom === 'week') return startOfWeek(day) + 7;\n if (zoom === 'month') return addMonths(day, 1);\n return addMonths(startOfQuarter(day), 3);\n}\n\nexport function createTimeScale({ from, to, zoom, padDays, minWidth = 0 }: ScaleInput): TimeScale {\n const pad = padDays ?? (zoom === 'week' ? 3 : zoom === 'month' ? 10 : 30);\n const startDay = snapStart(from, zoom);\n const dayWidth = DAY_WIDTH[zoom];\n let endDay = snapEnd(Math.max(to, from) + pad, zoom);\n for (let guard = 0; (endDay - startDay) * dayWidth < minWidth && guard < 120; guard++) {\n endDay = snapEnd(endDay, zoom);\n }\n const x = (day: number) => (day - startDay) * dayWidth;\n\n const major: Tick[] = [];\n const minor: Tick[] = [];\n const weekends: { x: number; width: number }[] = [];\n\n if (zoom === 'quarter') {\n for (let d = startDay; d < endDay; d = addMonths(d, 3)) {\n const p = parts(d);\n const next = addMonths(d, 3);\n major.push({ day: d, x: x(d), width: (next - d) * dayWidth, label: `Q${Math.floor(p.month / 3) + 1} ${p.year}` });\n }\n for (let d = startDay; d < endDay; d = addMonths(d, 1)) {\n const next = addMonths(d, 1);\n minor.push({ day: d, x: x(d), width: (next - d) * dayWidth, label: MONTHS[parts(d).month] ?? '' });\n }\n } else {\n let first = true;\n for (let d = startOfMonth(startDay); d < endDay; d = addMonths(d, 1)) {\n const next = addMonths(d, 1);\n const left = Math.max(d, startDay);\n const p = parts(d);\n const label = first || p.month === 0 ? `${MONTHS_LONG[p.month]} ${p.year}` : `${MONTHS_LONG[p.month]}`;\n major.push({ day: left, x: x(left), width: (Math.min(next, endDay) - left) * dayWidth, label });\n first = false;\n }\n if (zoom === 'week') {\n for (let d = startDay; d < endDay; d++) {\n minor.push({ day: d, x: x(d), width: dayWidth, label: String(parts(d).date) });\n if (weekday(d) === 5) weekends.push({ x: x(d), width: dayWidth * 2 });\n }\n } else {\n for (let d = startOfWeek(startDay); d < endDay; d += 7) {\n if (d < startDay) continue;\n minor.push({ day: d, x: x(d), width: 7 * dayWidth, label: String(parts(d).date) });\n }\n }\n }\n\n return {\n zoom,\n startDay,\n endDay,\n dayWidth,\n width: (endDay - startDay) * dayWidth,\n x,\n dayAt: (px: number) => startDay + Math.floor(px / dayWidth),\n major,\n minor,\n weekends\n };\n}\n","/**\n * Time zones for instant (UTC millisecond) maths.\n *\n * A `Zone` answers one question: at this instant, how many minutes **east of UTC** is the\n * wall clock? Perth is +480, New York −300 (winter) / −240 (summer). Everything that turns\n * instants into dates, hours or pixels goes through a Zone — never through the machine's\n * local getters, which are wrong whenever the user's Dataverse zone differs from the browser's.\n */\nexport type Zone = (utcMs: number) => number;\n\nconst MINUTE = 60_000;\n\n/** No offset, ever. Also the right zone for time-zone-independent (\"floating\") values. */\nexport const UTC: Zone = () => 0;\n\nexport function fixedZone(minutesEast: number): Zone {\n return () => minutesEast;\n}\n\n/** The browser's own zone. Only for demos; in Power Apps use `pcfZone`. */\nexport function browserZone(): Zone {\n return (ms) => -new Date(ms).getTimezoneOffset() || 0;\n}\n\nconst formatters = new Map<string, Intl.DateTimeFormat>();\n\n/** An IANA zone such as `Europe/London`, via `Intl`. Used by the demo and the tests. */\nexport function ianaZone(name: string): Zone {\n let fmt = formatters.get(name);\n if (!fmt) {\n fmt = new Intl.DateTimeFormat('en-US', {\n timeZone: name,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric'\n });\n formatters.set(name, fmt);\n }\n const f = fmt;\n return (ms) => {\n const p: Record<string, number> = {};\n for (const part of f.formatToParts(new Date(ms))) if (part.type !== 'literal') p[part.type] = Number(part.value);\n const wall = Date.UTC(p.year!, p.month! - 1, p.day!, p.hour === 24 ? 0 : p.hour!, p.minute!, p.second!);\n return Math.round((wall - Math.floor(ms / 1000) * 1000) / MINUTE);\n };\n}\n\n/**\n * The Power Apps user's zone, from `context.userSettings.getTimeZoneOffsetMinutes(date)`.\n *\n * Measured in Dev (DECISIONS 2026-10-25): it returns minutes **east** of UTC (Perth +480,\n * Pacific −480 in January, −420 in July), so `sign` defaults to +1. It must always be given a\n * date: without one the PCF API returned the standard offset during daylight time.\n */\nexport function pcfZone(getOffset: (date: Date) => number, sign: 1 | -1 = 1): Zone {\n return (ms) => sign * getOffset(new Date(ms)) || 0;\n}\n","import { MONTHS, WEEKDAYS, weekday } from './date';\nimport type { Zone } from './zone';\n\n/*\n * Instant maths for the Scheduler. An instant is UTC milliseconds. Wall-clock values (a date\n * and time as the user reads it) only exist relative to a Zone.\n *\n * The Gantt's day numbers (date.ts) count calendar days; they meet instants only through\n * `dayToInstant` and `instantToDay`, never by multiplying by 86,400,000 — DST days are 23 or\n * 25 hours long.\n */\n\nconst MINUTE = 60_000;\nconst HOUR = 3_600_000;\nconst DAY = 86_400_000;\n\n/** A wall-clock time. `m` is 1–12. */\nexport interface Wall {\n y: number;\n m: number;\n d: number;\n h?: number;\n min?: number;\n s?: number;\n}\n\n/** The wall clock in `zone` at an instant. */\nexport function instantToWall(ms: number, zone: Zone): Required<Wall> {\n const t = new Date(ms + zone(ms) * MINUTE);\n return { y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: t.getUTCHours(), min: t.getUTCMinutes(), s: t.getUTCSeconds() };\n}\n\n/**\n * The instant a wall-clock time happens in `zone`.\n *\n * - **Gap** (spring forward: 02:30 doesn't exist in New York on 8 Mar 2026): moves forward by\n * the length of the gap, so 02:30 → 03:30 daylight time.\n * - **Overlap** (fall back: 01:30 happens twice on 1 Nov 2026): the first occurrence (daylight).\n *\n * Assumes at most one offset change within a day either side, which holds for every real zone.\n */\nexport function wallToInstant(w: Wall, zone: Zone): number {\n const local = Date.UTC(w.y, w.m - 1, w.d, w.h ?? 0, w.min ?? 0, w.s ?? 0);\n const before = zone(local - DAY);\n const after = zone(local + DAY);\n const tBefore = local - before * MINUTE;\n if (before === after) return tBefore;\n const tAfter = local - after * MINUTE;\n const okBefore = zone(tBefore) === before;\n const okAfter = zone(tAfter) === after;\n if (okBefore && okAfter) return Math.min(tBefore, tAfter); // overlap → first occurrence\n if (okBefore) return tBefore;\n if (okAfter) return tAfter;\n return tBefore; // gap → forward by the gap's length\n}\n\n/** Calendar day number (as in date.ts) of the wall date at an instant. */\nexport function instantToDay(ms: number, zone: Zone): number {\n const w = instantToWall(ms, zone);\n return Math.round(Date.UTC(w.y, w.m - 1, w.d) / DAY);\n}\n\n/** The instant a calendar day starts in `zone` (local midnight, or the first instant after a midnight gap). */\nexport function dayToInstant(day: number, zone: Zone): number {\n const t = new Date(day * DAY);\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate() }, zone);\n}\n\n/** Start of the wall day containing `ms`. */\nexport function startOfDayIn(ms: number, zone: Zone): number {\n return dayToInstant(instantToDay(ms, zone), zone);\n}\n\n/** Same wall time `n` calendar days later (or earlier). */\nexport function addDaysIn(ms: number, n: number, zone: Zone): number {\n const w = instantToWall(ms, zone);\n const t = new Date(Date.UTC(w.y, w.m - 1, w.d + n));\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: w.h, min: w.min, s: w.s }, zone);\n}\n\n/** Calendar days from `a` to `b` in `zone` (whole days, however long each one is). */\nexport function daysBetween(a: number, b: number, zone: Zone): number {\n return instantToDay(b, zone) - instantToDay(a, zone);\n}\n\n/** Real length of a calendar day in ms: 24 h, or 23 / 25 h on DST changes. */\nexport function dayLength(day: number, zone: Zone): number {\n return dayToInstant(day + 1, zone) - dayToInstant(day, zone);\n}\n\n/** A wall time `hours` after the day's midnight, e.g. 9.5 → 09:30. */\nexport function atHour(day: number, hours: number, zone: Zone): number {\n const t = new Date(day * DAY);\n const whole = Math.floor(hours);\n const min = Math.round((hours - whole) * 60);\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: whole, min }, zone);\n}\n\n/* ---------- Snapping ---------- */\n\nexport type SnapDirection = 'nearest' | 'floor' | 'ceil';\n\n/**\n * Snap to a multiple of `minutes` on the **wall clock** (15-minute marks are :00/:15/:30/:45\n * local, including in +5:30 or +9:30 zones). Across a DST gap the result follows `wallToInstant`.\n */\nexport function snap(ms: number, minutes: number, zone: Zone, dir: SnapDirection = 'nearest'): number {\n if (minutes <= 0) return ms;\n const w = instantToWall(ms, zone);\n const ofDay = w.h * 60 + w.min + w.s / 60 + (ms % 1000) / 60_000;\n const steps = ofDay / minutes;\n const n = dir === 'floor' ? Math.floor(steps) : dir === 'ceil' ? Math.ceil(steps) : Math.round(steps);\n const target = n * minutes;\n const t = new Date(Date.UTC(w.y, w.m - 1, w.d, 0, target));\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: t.getUTCHours(), min: t.getUTCMinutes() }, zone);\n}\n\n/* ---------- Labels ---------- */\n\n/** \"09:30\" */\nexport function timeLabel(ms: number, zone: Zone): string {\n const w = instantToWall(ms, zone);\n return `${String(w.h).padStart(2, '0')}:${String(w.min).padStart(2, '0')}`;\n}\n\n/** \"Mon 26 Oct\" */\nexport function dayLabelIn(ms: number, zone: Zone): string {\n const day = instantToDay(ms, zone);\n const w = instantToWall(ms, zone);\n return `${WEEKDAYS[weekday(day)]} ${w.d} ${MONTHS[w.m - 1]}`;\n}\n\n/* ---------- Instant scale ---------- */\n\nexport interface InstantTick {\n ms: number;\n x: number;\n width: number;\n label: string;\n}\n\nexport interface InstantDayTick extends InstantTick {\n day: number;\n /** Real hours in this day (23, 24 or 25). */\n hours: number;\n working: boolean;\n}\n\nexport interface InstantScale {\n zone: Zone;\n /** First visible instant (start of the first day). */\n start: number;\n /** Exclusive end (start of the day after the last). */\n end: number;\n startDay: number;\n /** Exclusive. */\n endDay: number;\n pxPerHour: number;\n width: number;\n /** Instant → px. Instants in hidden (non-working) time map to the next visible edge. */\n x(ms: number): number;\n /** px → instant. */\n msAt(px: number): number;\n days: InstantDayTick[];\n hours: InstantTick[];\n /** Non-working time to tint (before/after hours, and whole non-working days). Empty when hidden. */\n nonWorking: { x: number; width: number }[];\n}\n\nexport interface InstantScaleInput {\n /** Any instants inside the first and last day to show. */\n from: number;\n to: number;\n zone: Zone;\n pxPerHour: number;\n /** Working hours as decimal wall hours, e.g. 9 and 17.5. */\n workStart?: number;\n workEnd?: number;\n /** Mon–Fri (default) or every day. */\n workingWeek?: 'mon-fri' | 'all';\n /** Show only working hours (and only working days with `mon-fri`). */\n hideNonWorking?: boolean;\n /** Hour tick spacing (default 1). */\n hourStep?: number;\n}\n\ninterface Segment {\n start: number;\n end: number;\n x0: number;\n}\n\nexport function createInstantScale(input: InstantScaleInput): InstantScale {\n const { zone, pxPerHour, workStart = 9, workEnd = 17, workingWeek = 'mon-fri', hideNonWorking = false, hourStep = 1 } = input;\n const startDay = instantToDay(Math.min(input.from, input.to), zone);\n const endDay = instantToDay(Math.max(input.from, input.to), zone) + 1;\n const pxPerMs = pxPerHour / HOUR;\n const isWorkingDay = (day: number) => workingWeek === 'all' || weekday(day) < 5;\n\n // Visible stretches of time, laid end to end.\n const segments: Segment[] = [];\n let x0 = 0;\n const days: InstantDayTick[] = [];\n const nonWorking: { x: number; width: number }[] = [];\n for (let day = startDay; day < endDay; day++) {\n const dayStart = dayToInstant(day, zone);\n const dayEnd = dayToInstant(day + 1, zone);\n const working = isWorkingDay(day);\n const ws = Math.max(dayStart, Math.min(dayEnd, atHour(day, workStart, zone)));\n const we = Math.max(ws, Math.min(dayEnd, atHour(day, workEnd, zone)));\n let segStart = dayStart;\n let segEnd = dayEnd;\n if (hideNonWorking) {\n if (!working) continue;\n segStart = ws;\n segEnd = we;\n }\n const width = (segEnd - segStart) * pxPerMs;\n segments.push({ start: segStart, end: segEnd, x0 });\n days.push({ day, ms: segStart, x: x0, width, label: dayLabelIn(dayStart, zone), hours: (dayEnd - dayStart) / HOUR, working });\n if (!hideNonWorking) {\n if (!working) nonWorking.push({ x: x0, width });\n else {\n if (ws > dayStart) nonWorking.push({ x: x0, width: (ws - dayStart) * pxPerMs });\n if (we < dayEnd) nonWorking.push({ x: x0 + (we - dayStart) * pxPerMs, width: (dayEnd - we) * pxPerMs });\n }\n }\n x0 += width;\n }\n const width = x0;\n const start = segments[0]?.start ?? dayToInstant(startDay, zone);\n const end = segments[segments.length - 1]?.end ?? dayToInstant(endDay, zone);\n\n const x = (ms: number): number => {\n if (segments.length === 0) return 0;\n let lo = 0;\n let hi = segments.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (segments[mid]!.start <= ms) lo = mid;\n else hi = mid - 1;\n }\n const s = segments[lo]!;\n if (ms <= s.start) return s.x0;\n if (ms >= s.end) return s.x0 + (s.end - s.start) * pxPerMs; // hidden gap → next visible edge\n return s.x0 + (ms - s.start) * pxPerMs;\n };\n\n const msAt = (px: number): number => {\n if (segments.length === 0) return start;\n let lo = 0;\n let hi = segments.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (segments[mid]!.x0 <= px) lo = mid;\n else hi = mid - 1;\n }\n const s = segments[lo]!;\n return Math.min(s.end, Math.max(s.start, s.start + (px - s.x0) / pxPerMs));\n };\n\n // Hour ticks walk real instants, so a 25-hour day shows 01:00 twice and a 23-hour day skips 02:00.\n const hours: InstantTick[] = [];\n for (const s of segments) {\n let t = snap(s.start, hourStep * 60, zone, 'ceil');\n while (t < s.end) {\n const next = Math.min(s.end, t + hourStep * HOUR);\n hours.push({ ms: t, x: x(t), width: (next - t) * pxPerMs, label: timeLabel(t, zone) });\n t += hourStep * HOUR;\n }\n }\n\n return { zone, start, end, startDay, endDay, pxPerHour, width, x, msAt, days, hours, nonWorking };\n}\n","import { atHour, dayToInstant } from './instant';\nimport type { Zone } from './zone';\n\n/* Working hours and capacity for the Scheduler. Hours are decimal wall hours (9.5 = 09:30). */\n\nconst HOUR = 3_600_000;\n\n/** \"09:30\" or 9.5 → 9.5. Invalid input → NaN. */\nexport function parseHour(value: string | number): number {\n if (typeof value === 'number') return value >= 0 && value <= 24 ? value : NaN;\n const m = /^\\s*(\\d{1,2})(?::(\\d{2}))?\\s*$/.exec(value);\n if (!m) return NaN;\n const h = Number(m[1]);\n const min = m[2] ? Number(m[2]) : 0;\n return h <= 24 && min < 60 && (h < 24 || min === 0) ? h + min / 60 : NaN;\n}\n\n/** The working window of a calendar day in `zone`, as instants. End never precedes start. */\nexport function workingWindow(day: number, start: string | number, end: string | number, zone: Zone): { start: number; end: number } {\n const s = atHour(day, parseHour(start), zone);\n const e = atHour(day, parseHour(end), zone);\n return { start: s, end: Math.max(s, e) };\n}\n\nexport interface TimedItem {\n start: number;\n end: number;\n /** Unavailable blocks (leave, maintenance) count against capacity like bookings. */\n kind?: 'booking' | 'unavailable';\n}\n\n/**\n * Hours booked on a calendar day: each item clipped to the day's real start and end (so an\n * overnight booking splits across days, and a DST night counts its real 23 or 25 hours).\n * Overlapping items both count — that is what over-capacity means.\n */\nexport function bookedHours(items: readonly TimedItem[], day: number, zone: Zone): number {\n const dayStart = dayToInstant(day, zone);\n const dayEnd = dayToInstant(day + 1, zone);\n let ms = 0;\n for (const it of items) {\n const s = Math.max(it.start, dayStart);\n const e = Math.min(it.end, dayEnd);\n if (e > s) ms += e - s;\n }\n return ms / HOUR;\n}\n\nexport type UtilisationLevel = 'none' | 'under' | 'near' | 'over';\n\n/** Booked ÷ capacity, with a level for colouring: under < 85% ≤ near ≤ 100% < over. */\nexport function utilisation(booked: number, capacity: number): { ratio: number; level: UtilisationLevel } {\n if (!(capacity > 0)) return { ratio: booked > 0 ? Infinity : 0, level: booked > 0 ? 'over' : 'none' };\n const ratio = booked / capacity;\n const level: UtilisationLevel = booked <= 0 ? 'none' : ratio > 1 + 1e-9 ? 'over' : ratio >= 0.85 ? 'near' : 'under';\n return { ratio, level };\n}\n","/**\r\n * Number formatting on Intl.NumberFormat, so every locale and currency is right.\r\n * Compact notation follows the locale: en \"4.2M\", de \"4,2 Mio.\", ja \"420万\".\r\n * Shared by Charts, Scheduler and Driver Tree (moved from @ninthforge/charts).\r\n */\r\n\r\nexport type NumberStyle = 'auto' | 'number' | 'currency' | 'percent' | 'compact';\r\n\r\nexport interface NumberFormatOptions {\r\n style?: NumberStyle;\r\n /** ISO 4217 code, e.g. AUD. Used by `currency`, and by `compact`/`auto` when set. */\r\n currency?: string;\r\n /** Fixed decimal places. Leave out to choose automatically. */\r\n decimals?: number;\r\n /** BCP-47 tag. Leave out for the runtime default. */\r\n locale?: string;\r\n /**\r\n * When to show a sign: `auto` (negatives only, the default), `always` (+5, 0 shows +0),\r\n * `exceptZero` (+5, 0, −5: for changes and variances) or `never`.\r\n */\r\n signDisplay?: SignDisplay;\r\n /**\r\n * A unit for `number` and `compact` (and `auto` without a currency): an Intl unit\r\n * identifier such as `hour`, `day`, `kilometer`, `kilogram` or `kilometer-per-hour`.\r\n * Shown short (\"8 hr\", \"1.2K km\") in the locale's own words. Ignored for currency and\r\n * percent; an identifier Intl doesn't know is dropped rather than thrown.\r\n */\r\n unit?: string;\r\n}\r\n\r\nexport type SignDisplay = 'auto' | 'always' | 'exceptZero' | 'never';\r\n\r\n/** Decimal places a step needs (0.25 → 2, 5 → 0). */\r\nexport function stepDecimals(step: number): number {\r\n if (!(step > 0) || !Number.isFinite(step)) return 0;\r\n let d = 0;\r\n while (d < 12 && Math.abs(Math.round(step * 10 ** d) - step * 10 ** d) > 1e-9 * 10 ** d) d++;\r\n return d;\r\n}\r\n\r\n/** Values at or above this (absolute) use compact notation in `auto`. */\r\nexport const AUTO_COMPACT_FROM = 10_000;\r\n\r\nconst cache = new Map<string, Intl.NumberFormat>();\r\n\r\nfunction nf(locale: string | undefined, o: Intl.NumberFormatOptions): Intl.NumberFormat {\r\n const key = (locale ?? '') + JSON.stringify(o);\r\n let f = cache.get(key);\r\n if (!f) {\r\n try {\r\n f = new Intl.NumberFormat(locale, o);\r\n } catch {\r\n // Unknown locale or currency code: fall back rather than throw inside a render.\r\n const safe: Intl.NumberFormatOptions = { ...o };\r\n if (safe.style === 'currency' || safe.style === 'unit') {\r\n delete safe.style;\r\n delete safe.currency;\r\n delete safe.unit;\r\n delete safe.unitDisplay;\r\n }\r\n try {\r\n f = new Intl.NumberFormat(locale, safe);\r\n } catch {\r\n f = new Intl.NumberFormat(undefined, safe);\r\n }\r\n }\r\n cache.set(key, f);\r\n }\r\n return f;\r\n}\r\n\r\ntype Style = Exclude<NumberStyle, 'auto'>;\r\n\r\nfunction intlOptions(style: Style, opts: NumberFormatOptions, min: number | undefined, max: number | undefined): Intl.NumberFormatOptions {\r\n const { currency, unit, signDisplay } = opts;\r\n const o: Intl.NumberFormatOptions = {};\r\n if (currency && (style === 'currency' || style === 'compact')) {\r\n o.style = 'currency';\r\n o.currency = currency.toUpperCase();\r\n } else if (unit && (style === 'number' || style === 'compact')) {\r\n o.style = 'unit';\r\n o.unit = unit;\r\n o.unitDisplay = 'short';\r\n }\r\n if (signDisplay && signDisplay !== 'auto') o.signDisplay = signDisplay;\r\n if (style === 'percent') o.style = 'percent';\r\n if (style === 'compact') o.notation = 'compact';\r\n if (max !== undefined) o.maximumFractionDigits = max;\r\n if (min !== undefined) o.minimumFractionDigits = Math.min(min, max ?? min);\r\n // Compact currency would otherwise inherit the currency's minimum digits on some ICU versions\r\n // (Node 22 printed \"$450.0K\" where Node 24 prints \"$450K\").\r\n else if (style === 'compact') o.minimumFractionDigits = 0;\r\n return o;\r\n}\r\n\r\n/** Resolves `auto` from the largest magnitude on show: compact from 10,000, else number or currency. */\r\nexport function resolveStyle(style: NumberStyle | undefined, magnitude: number, currency?: string): Style {\r\n if (style && style !== 'auto') return style;\r\n if (Math.abs(magnitude) >= AUTO_COMPACT_FROM) return 'compact';\r\n return currency ? 'currency' : 'number';\r\n}\r\n\r\n/**\r\n * Formats one value for a tooltip, label or table. Non-finite values give \"\".\r\n * Percent takes a fraction: 0.125 → \"12.5%\".\r\n */\r\nexport function formatNumber(v: number, o: NumberFormatOptions = {}): string {\r\n if (!Number.isFinite(v)) return '';\r\n const style = resolveStyle(o.style, v, o.currency);\r\n let max = o.decimals;\r\n const min = o.decimals;\r\n if (max === undefined) {\r\n if (style === 'compact' || style === 'percent') max = 1;\r\n else if (style === 'number') max = Math.abs(v) >= 100 ? 0 : 2;\r\n // currency: leave undefined so the currency's own digits apply (2 for AUD, 0 for JPY).\r\n }\r\n return nf(o.locale, intlOptions(style, o, min, max)).format(v);\r\n}\r\n\r\n/**\r\n * A formatter for axis ticks. Decimals come from the tick step, and it adds\r\n * decimals until no two ticks share a label (so an axis never says 1K, 1K, 2K).\r\n */\r\nexport function tickFormatter(ticks: number[], o: NumberFormatOptions = {}): (v: number) => string {\r\n const finite = ticks.filter(Number.isFinite);\r\n const magnitude = finite.reduce((m, v) => Math.max(m, Math.abs(v)), 0);\r\n const style = resolveStyle(o.style, magnitude, o.currency);\r\n const step = finite.length > 1 ? Math.abs((finite[1] as number) - (finite[0] as number)) : 0;\r\n const wrap = (f: Intl.NumberFormat) => (v: number) => (Number.isFinite(v) ? f.format(v) : '');\r\n // Others keep a fixed width (0.0, 0.5, 1.0).\r\n const make = (d: number): ((v: number) => string) => wrap(nf(o.locale, intlOptions(style, o, d, d)));\r\n if (o.decimals !== undefined) return make(o.decimals);\r\n const unique = (fmt: (v: number) => string): boolean => new Set(finite.map(fmt)).size === finite.length;\r\n if (style === 'compact') {\r\n // Compact units differ by locale (K, Mio., 万), so precision is in significant\r\n // digits: enough to show every tick exactly (2.5K, 10.2K, 1.25万), no trailing zeros.\r\n const digits = step > 0 && magnitude > 0 ? Math.floor(Math.log10(magnitude)) - Math.floor(Math.log10(step)) + 1 : 1;\r\n for (let sig = Math.max(1, Math.min(21, digits)); sig <= 21; sig++) {\r\n const fmt = wrap(nf(o.locale, { ...intlOptions(style, o, undefined, undefined), maximumSignificantDigits: sig }));\r\n if (unique(fmt)) return fmt;\r\n }\r\n }\r\n const start = style === 'percent' ? Math.max(0, stepDecimals(step) - 2) : stepDecimals(step);\r\n for (let d = start; d <= 6; d++) {\r\n const fmt = make(d);\r\n if (unique(fmt)) return fmt;\r\n }\r\n return make(6);\r\n}\r\n","/* Print and image export helpers shared by the components. No network: printing happens in a\n same-origin about:blank iframe and images download through a local blob URL. */\n\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c]!);\n}\n\n/** \"Capital works program\" + \"2026-10-21\" → \"capital-works-program-2026-10-21.png\" */\nexport function exportFileName(title: string, isoDay: string, ext = 'png', fallback = 'plan'): string {\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || fallback;\n return `${slug}-${isoDay}.${ext}`;\n}\n\n/** Browsers cap canvases around 16k px a side (and ~268M px in area); exports stay under both. */\nexport const MAX_CANVAS_SIDE = 16_000;\nexport const MAX_CANVAS_AREA = 120_000_000;\n\n/** Device-pixel ratio for a `width` × `height` export: `preferred`, lowered to fit the caps; null below 0.5. */\nexport function exportRatio(width: number, height: number, preferred = 2): number | null {\n const ratio = Math.min(preferred, MAX_CANVAS_SIDE / width, MAX_CANVAS_SIDE / height, Math.sqrt(MAX_CANVAS_AREA / (width * height)));\n return ratio >= 0.5 ? ratio : null;\n}\n\n/** Any CSS colour → a canvas-safe string (resolves names, rgb() and color-mix() where supported). */\nexport function canvasColor(ctx: CanvasRenderingContext2D, color: string | undefined, fallback: string): string {\n ctx.fillStyle = fallback;\n if (color) ctx.fillStyle = color;\n return String(ctx.fillStyle);\n}\n\n/** Save a canvas as a PNG through a local blob URL. */\nexport function downloadCanvas(canvas: HTMLCanvasElement, fileName: string, doc: Document = document): void {\n canvas.toBlob((blob) => {\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const a = doc.createElement('a');\n a.href = url;\n a.download = fileName;\n a.style.display = 'none';\n doc.body.appendChild(a);\n a.click();\n a.remove();\n setTimeout(() => URL.revokeObjectURL(url), 10_000);\n }, 'image/png');\n}\n\n/**\n * Print `html` from a hidden same-origin iframe, so the host page (the whole Power Apps shell)\n * is never printed or re-laid out. The iframe is removed after the print dialog closes, or after\n * a minute for browsers that never fire afterprint.\n */\nexport function printDocument(html: string, doc: Document = document): void {\n const frame = doc.createElement('iframe');\n frame.setAttribute('aria-hidden', 'true');\n frame.setAttribute('tabindex', '-1');\n frame.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden';\n doc.body.appendChild(frame);\n const win = frame.contentWindow;\n const fdoc = frame.contentDocument;\n if (!win || !fdoc) {\n frame.remove();\n return;\n }\n fdoc.open();\n fdoc.write(html);\n fdoc.close();\n let done = false;\n const cleanup = () => {\n if (done) return;\n done = true;\n frame.remove();\n };\n win.addEventListener('afterprint', () => setTimeout(cleanup, 0));\n setTimeout(cleanup, 60_000);\n // Let the iframe lay out before the dialog snapshots it.\n setTimeout(() => {\n win.focus();\n win.print();\n }, 50);\n}\n","import { dayOf, MONTHS, parts } from './date';\nimport type { Tick } from './scale';\n\n/*\n * The Timeline's axis: months, quarters or half-years, with fiscal-year labels. Separate from\n * createTimeScale (the Gantt's week/month/quarter), so the Gantt's zooms are untouched.\n * Day numbers in UTC, like the rest of the date maths.\n */\n\nexport type RoadmapZoom = 'month' | 'quarter' | 'half';\n\nexport const ROADMAP_ZOOMS: readonly RoadmapZoom[] = ['month', 'quarter', 'half'];\n\n/** Pixels per day: about 120 px a month, 140 px a quarter and 150 px a half-year. */\nexport const ROADMAP_DAY_WIDTH: Record<RoadmapZoom, number> = { month: 4, quarter: 1.5, half: 0.8 };\n\nexport interface RoadmapScaleInput {\n /** First and last day that must be visible (inclusive). */\n from: number;\n to: number;\n zoom: RoadmapZoom;\n /** First month of the fiscal year, 1–12 (default 1: calendar years). */\n fiscalYearStart?: number;\n /** Extend the end until the scale is at least this wide. */\n minWidth?: number;\n}\n\nexport interface RoadmapScale {\n zoom: RoadmapZoom;\n fiscalYearStart: number;\n startDay: number;\n /** Exclusive. */\n endDay: number;\n dayWidth: number;\n width: number;\n x(day: number): number;\n dayAt(px: number): number;\n /** Years (or fiscal years) on top. */\n major: Tick[];\n /** Months, quarters or halves below. */\n minor: Tick[];\n /** Quarter zoom only: the months under each quarter (empty at other zooms). */\n months: Tick[];\n}\n\nconst clampMonth = (m: number | undefined) => (Number.isInteger(m) && m! >= 1 && m! <= 12 ? m! : 1);\n\n/** Months since the fiscal year began (0–11) for a 0-based calendar month. */\nconst fyMonth = (month: number, fyStart: number) => (((month - (fyStart - 1)) % 12) + 12) % 12;\n\n/**\n * The fiscal year a day falls in, named by the year it ends in (a July 2026 start is FY27).\n * With a January start it is the calendar year.\n */\nexport function fiscalYear(day: number, fiscalYearStart = 1): number {\n const fs = clampMonth(fiscalYearStart);\n const p = parts(day);\n return fs === 1 || p.month < fs - 1 ? p.year : p.year + 1;\n}\n\n/** First day of the period (`months` long: 1, 3 or 6) that `day` is in, aligned to the fiscal year. */\nexport function periodStart(day: number, months: 1 | 3 | 6, fiscalYearStart = 1): number {\n const fs = clampMonth(fiscalYearStart);\n const p = parts(day);\n const into = fyMonth(p.month, fs) % months;\n return dayOf(p.year, p.month - into, 1);\n}\n\n/** The first day `n` months after the start of `day`'s month. */\nfunction addMonthsFrom(day: number, n: number): number {\n const p = parts(day);\n return dayOf(p.year, p.month + n, 1);\n}\n\n/** \"Q1 FY27\" / \"Q1 2027\", \"H2 FY27\" / \"H2 2027\", \"FY27\" / \"2027\". */\nexport function periodLabel(day: number, kind: 'quarter' | 'half' | 'year', fiscalYearStart = 1): string {\n const fs = clampMonth(fiscalYearStart);\n const fy = fiscalYear(day, fs);\n const year = fs === 1 ? String(fy) : `FY${String(fy % 100).padStart(2, '0')}`;\n if (kind === 'year') return year;\n const m = fyMonth(parts(day).month, fs);\n return kind === 'quarter' ? `Q${Math.floor(m / 3) + 1} ${year}` : `H${m < 6 ? 1 : 2} ${year}`;\n}\n\nexport function createRoadmapScale({ from, to, zoom, fiscalYearStart, minWidth = 0 }: RoadmapScaleInput): RoadmapScale {\n const fs = clampMonth(fiscalYearStart);\n const step = zoom === 'month' ? 1 : zoom === 'quarter' ? 3 : 6;\n const startDay = periodStart(Math.min(from, to), step, fs);\n let endDay = addMonthsFrom(periodStart(Math.max(from, to), step, fs), step);\n const dayWidth = ROADMAP_DAY_WIDTH[zoom];\n for (let guard = 0; (endDay - startDay) * dayWidth < minWidth && guard < 240; guard++) endDay = addMonthsFrom(endDay, step);\n const x = (day: number) => Math.round((day - startDay) * dayWidth * 10) / 10;\n\n const minor: Tick[] = [];\n for (let d = startDay; d < endDay; d = addMonthsFrom(d, step)) {\n const next = addMonthsFrom(d, step);\n const label = zoom === 'month' ? (MONTHS[parts(d).month] ?? '') : periodLabel(d, zoom, fs);\n minor.push({ day: d, x: x(d), width: x(next) - x(d), label });\n }\n const months: Tick[] = [];\n if (zoom === 'quarter') {\n for (let d = startDay; d < endDay; d = addMonthsFrom(d, 1)) {\n const next = addMonthsFrom(d, 1);\n months.push({ day: d, x: x(d), width: x(next) - x(d), label: MONTHS[parts(d).month] ?? '' });\n }\n }\n const major: Tick[] = [];\n for (let yStart = yearStart(startDay, fs); yStart < endDay; ) {\n const yEnd = addMonthsFrom(yStart, 12);\n const left = Math.max(yStart, startDay);\n const right = Math.min(yEnd, endDay);\n major.push({ day: left, x: x(left), width: x(right) - x(left), label: periodLabel(yStart, 'year', fs) });\n yStart = yEnd;\n }\n\n return {\n zoom,\n fiscalYearStart: fs,\n startDay,\n endDay,\n dayWidth,\n width: x(endDay),\n x,\n dayAt: (px: number) => startDay + Math.floor(px / dayWidth),\n major,\n minor,\n months\n };\n}\n\n/** First day of the (fiscal) year `day` is in. */\nexport function yearStart(day: number, fiscalYearStart = 1): number {\n const fs = clampMonth(fiscalYearStart);\n const p = parts(day);\n const y = p.month >= fs - 1 ? p.year : p.year - 1;\n return dayOf(y, fs - 1, 1);\n}\n","/**\n * Host hooks shared by the Plan components (Gantt, Timeline, capacity heatmap) when a host such\n * as a Power BI visual wraps them. Same shapes as `@ninthforge/charts`' hooks. All optional:\n * Power Apps passes none of them.\n */\n\n/** Passed with a selection: `multi` is true when Ctrl or Cmd was held (add to the selection). */\nexport interface SelectInfo {\n multi: boolean;\n}\n\n/** Where the pointer (or, from the keyboard, the item's anchor) is, in client coordinates. */\nexport interface PointerInfo extends SelectInfo {\n clientX: number;\n clientY: number;\n}\n\n/** A host formatter, e.g. one built from a Power BI format string. `C` says what is formatted. */\nexport type HostFormat<C> = (value: number, ctx: C) => string;\n\n/** Separates the parts of a composite key (category and series, resource and week). */\nexport const KEY_SEPARATOR = '\\u001f';\n\n/** True when the event asks to add to the selection (Ctrl on Windows, Cmd on a Mac). */\nexport function isMultiSelect(e: { ctrlKey?: boolean; metaKey?: boolean }): boolean {\n return !!(e.ctrlKey || e.metaKey);\n}\n\n/** Opacity for items outside `highlighted` (the Charts' DIM). */\nexport const HOST_DIM = 0.3;\n\n/**\n * Whether an item stays at full strength. With no highlight (undefined or empty), every item\n * does; otherwise only the listed ids.\n */\nexport function isHighlighted(id: string, highlighted?: readonly string[] | ReadonlySet<string>): boolean {\n if (!highlighted) return true;\n if (isSet(highlighted)) return highlighted.size === 0 || highlighted.has(id);\n return highlighted.length === 0 || highlighted.indexOf(id) !== -1;\n}\n\nfunction isSet(v: readonly string[] | ReadonlySet<string>): v is ReadonlySet<string> {\n return typeof (v as ReadonlySet<string>).has === 'function' && !Array.isArray(v);\n}\n\n/** The keyboard way to open a context menu: the Menu key or Shift+F10. */\nexport function isContextMenuKey(e: { key: string; shiftKey?: boolean }): boolean {\n return e.key === 'ContextMenu' || (e.key === 'F10' && !!e.shiftKey);\n}\n","/**\r\n * Pointer maths that ignores CSS scaling around a component.\r\n *\r\n * Layout maths work in the component's own CSS pixels (`offsetWidth`, `clientWidth`). Pointer\r\n * events and `getBoundingClientRect()` report screen pixels, which differ when something around\r\n * the component scales it: Power BI's focus mode and report zoom, a canvas app's scale-to-fit,\r\n * a wrapper's `transform: scale()`. Browser zoom scales both alike, so it needs nothing. Divide\r\n * pointer movement by `cssScale()`, and turn client points into local ones with `localPoint()`.\r\n */\r\n\r\n/** The parts of an element these helpers read; tests pass a fake. */\r\nexport interface ScalableElement {\r\n offsetWidth: number;\r\n offsetHeight: number;\r\n getBoundingClientRect(): { left: number; top: number; width: number; height: number };\r\n}\r\n\r\nexport interface CssScale {\r\n x: number;\r\n y: number;\r\n}\r\n\r\nconst ratio = (drawn: number, layout: number): number =>\r\n layout > 0 && drawn > 0 && Number.isFinite(drawn / layout) ? drawn / layout : 1;\r\n\r\n/**\r\n * How much the element is drawn bigger (>1) or smaller (<1) than its layout size, from\r\n * transforms around it. 1 when unknown (no element, not laid out, or server render).\r\n */\r\nexport function cssScale(el: ScalableElement | null | undefined): CssScale {\r\n if (!el) return { x: 1, y: 1 };\r\n const r = el.getBoundingClientRect();\r\n return { x: ratio(r.width, el.offsetWidth), y: ratio(r.height, el.offsetHeight) };\r\n}\r\n\r\n/**\r\n * A client point (a pointer event's `clientX`/`clientY`) as an offset from the element's\r\n * top-left corner, in the element's own CSS pixels. Add `scrollLeft`/`scrollTop` yourself for\r\n * content coordinates.\r\n */\r\nexport function localPoint(el: ScalableElement | null | undefined, clientX: number, clientY: number): { x: number; y: number } {\r\n if (!el) return { x: 0, y: 0 };\r\n const r = el.getBoundingClientRect();\r\n return { x: (clientX - r.left) / ratio(r.width, el.offsetWidth), y: (clientY - r.top) / ratio(r.height, el.offsetHeight) };\r\n}\r\n","/**\n * Optimistic editing for Ninth Forge components — pure functions, no React.\n *\n * A change is `{ id, …fields, previous, source }`: the new values of some fields of one item,\n * the old values of the same fields, and what caused it. Components define which fields are\n * editable (`keys`), when two values count as the same (`equal`), and what \"no value\" is for\n * a field when undoing (`blank`). Shared by the Gantt and the Scheduler.\n */\n\n/** One edit to one item. `F` is the editable field set; `S` the sources a component uses. */\nexport type Change<F extends object, S extends string = string> = Partial<F> & {\n id: string;\n /** The same fields before the edit — for undo, and for hosts that diff. */\n previous: Partial<F>;\n source: S;\n};\n\nexport interface ChangeSpec<F extends object> {\n /** The editable fields, in a stable order. */\n keys: readonly (keyof F & string)[];\n /** Whether two values of a field are the same. Default: empty values (undefined/null/'') match, else `===`. */\n equal?: (key: keyof F & string, a: unknown, b: unknown) => boolean;\n /** The value that means \"none\" for a field, used when undoing a change whose old value was unset. */\n blank?: (key: keyof F & string) => unknown;\n}\n\nconst isEmpty = (v: unknown) => v === undefined || v === null || v === '';\n\nexport function defaultEqual(_key: string, a: unknown, b: unknown): boolean {\n if (isEmpty(a) && isEmpty(b)) return true;\n return a === b;\n}\n\n/** Set-like equality for string arrays (order doesn't matter; missing = empty). */\nexport function sameStringSet(a: unknown, b: unknown): boolean {\n const x = Array.isArray(a) ? [...(a as string[])].sort() : [];\n const y = Array.isArray(b) ? [...(b as string[])].sort() : [];\n return x.length === y.length && x.every((v, i) => v === y[i]);\n}\n\n/** The forward values of a change (everything except id/previous/source). */\nexport function fieldsOf<F extends object>(change: Change<F>, spec: ChangeSpec<F>): Partial<F> {\n const out: Partial<F> = {};\n for (const k of spec.keys) if (k in change) (out as Record<string, unknown>)[k] = (change as Record<string, unknown>)[k];\n return out;\n}\n\n/** The current values of exactly the fields `next` sets. */\nexport function previousOf<T extends object, F extends object>(item: T, next: Partial<F>, spec: ChangeSpec<F>): Partial<F> {\n const prev: Partial<F> = {};\n for (const k of spec.keys) if (k in next) (prev as Record<string, unknown>)[k] = (item as unknown as Record<string, unknown>)[k];\n return prev;\n}\n\n/** True when the change would not alter the item (e.g. a drag that snapped back to where it started). */\nexport function isNoop<T extends object, F extends object>(item: T, change: Change<F>, spec: ChangeSpec<F>): boolean {\n const eq = spec.equal ?? defaultEqual;\n const f = fieldsOf(change, spec) as Record<string, unknown>;\n return Object.keys(f).every((k) => eq(k as keyof F & string, (item as unknown as Record<string, unknown>)[k], f[k]));\n}\n\n/* ---------- Optimistic overrides ---------- */\n\nexport interface Override<F extends object> {\n patch: Partial<F>;\n /** Increments per commit; a stale save result can't touch a newer edit. */\n seq: number;\n}\n\nexport type Overrides<F extends object> = ReadonlyMap<string, Override<F>>;\n\n/** Items as the user sees them: host data with pending edits laid over it. */\nexport function applyOverrides<T extends { id: string }, F extends object>(items: T[], overrides: Overrides<F>): T[] {\n if (overrides.size === 0) return items;\n return items.map((t) => {\n const o = overrides.get(t.id);\n return o ? { ...t, ...o.patch } : t;\n });\n}\n\n/** Drop overrides that the host data now matches (the save landed and the data refreshed), or whose item is gone. */\nexport function reconcileOverrides<T extends { id: string }, F extends object>(\n items: T[],\n overrides: Overrides<F>,\n spec: Pick<ChangeSpec<F>, 'equal'> = {}\n): Map<string, Override<F>> {\n const eq = spec.equal ?? defaultEqual;\n const byId = new Map(items.map((t) => [t.id, t] as const));\n const next = new Map(overrides);\n for (const [id, o] of overrides) {\n const t = byId.get(id);\n if (!t) {\n next.delete(id);\n continue;\n }\n const matches = Object.keys(o.patch).every((k) =>\n eq(k as keyof F & string, (t as unknown as Record<string, unknown>)[k], (o.patch as Record<string, unknown>)[k])\n );\n if (matches) next.delete(id);\n }\n return next;\n}\n\n/* ---------- Undo / redo ---------- */\n\nexport const HISTORY_LIMIT = 20;\n\nexport interface History<C> {\n undo: C[];\n redo: C[];\n}\n\nexport function emptyHistory<C>(): History<C> {\n return { undo: [], redo: [] };\n}\n\n/** A new edit: push it, cap at 20, and clear redo. */\nexport function pushHistory<C>(h: History<C>, change: C): History<C> {\n return { undo: [...h.undo, change].slice(-HISTORY_LIMIT), redo: [] };\n}\n\n/** Remove a change from both stacks (its save was rejected). */\nexport function dropFromHistory<C>(h: History<C>, change: C): History<C> {\n return { undo: h.undo.filter((c) => c !== change), redo: h.redo.filter((c) => c !== change) };\n}\n\n/** The change that reverses `change`, ready to commit like any other edit. */\nexport function invertChange<F extends object, S extends string>(change: Change<F, S>, source: S, spec: ChangeSpec<F>): Change<F, S> {\n const forward = fieldsOf(change, spec);\n const back: Record<string, unknown> = {};\n for (const k of Object.keys(forward) as (keyof F & string)[]) {\n const v = (change.previous as Record<string, unknown>)[k];\n back[k] = v !== undefined ? v : spec.blank ? spec.blank(k) : undefined;\n }\n return { id: change.id, ...(back as Partial<F>), previous: forward, source } as Change<F, S>;\n}\n\nexport interface HistoryStep<C> {\n commit: C;\n entry: C;\n history: History<C>;\n}\n\n/** Undo: the change to commit, and the history with the entry moved to redo. */\nexport function undoStep<F extends object, S extends string>(\n h: History<Change<F, S>>,\n undoSource: S,\n spec: ChangeSpec<F>\n): HistoryStep<Change<F, S>> | null {\n const entry = h.undo[h.undo.length - 1];\n if (!entry) return null;\n return { commit: invertChange(entry, undoSource, spec), entry, history: { undo: h.undo.slice(0, -1), redo: [...h.redo, entry] } };\n}\n\n/** Redo: re-apply the most recently undone change. */\nexport function redoStep<F extends object, S extends string>(h: History<Change<F, S>>, redoSource: S): HistoryStep<Change<F, S>> | null {\n const entry = h.redo[h.redo.length - 1];\n if (!entry) return null;\n const commit = { ...entry, source: redoSource } as Change<F, S>;\n return { commit, entry, history: { undo: [...h.undo, entry].slice(-HISTORY_LIMIT), redo: h.redo.slice(0, -1) } };\n}\n\n/** A readable message from anything a save might throw or reject with. */\nexport function errorText(err: unknown, fallback = 'The change was not saved.'): string {\n if (err instanceof Error && err.message) return err.message;\n if (typeof err === 'string' && err) return err;\n const m = (err as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : fallback;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\r\nimport {\r\n applyOverrides,\r\n dropFromHistory,\r\n emptyHistory,\r\n errorText,\r\n fieldsOf,\r\n pushHistory,\r\n reconcileOverrides,\r\n redoStep,\r\n undoStep,\r\n type Change,\r\n type ChangeSpec,\r\n type History,\r\n type Override\r\n} from './changes';\r\nimport type { ToastItem } from './Toasts';\r\n\r\nconst INFO_MS = 5000;\r\nconst ERROR_MS = 9000;\r\n\r\nexport interface EditingSpec<T extends { id: string }, F extends object> extends ChangeSpec<F> {\r\n /** Screen-reader and toast text for a committed change (\"'Pour slab' moved to …\"). */\r\n describe: (change: Change<F>, nameOf: (id: string) => string) => string;\r\n /** The item's display name, for messages. Default: its `name`, else \"item\". */\r\n name?: (item: T) => string;\r\n /** What to call an item in messages when it can't be found (\"task\", \"booking\"). */\r\n noun?: string;\r\n /**\r\n * View-only: no key that unlocks Pro actions (`unlocksPro` in @ninthforge/license). A locked\r\n * commit is never applied, saved or raised, so the item snaps back. Leave `lockedMessage` empty\r\n * while the licence check runs, so nothing about it shows yet.\r\n */\r\n locked?: boolean;\r\n /** Said when a locked action is tried (`lockedMessage('Plan')`). Toasted once per page, then announced. */\r\n lockedMessage?: string;\r\n /** Where the locked toast's \"Start a free trial\" button goes (`trialUrl('gantt')`). No button without it. */\r\n lockedTrialUrl?: string;\r\n /** That button's text. Default \"Start a free trial\". */\r\n lockedTrialLabel?: string;\r\n}\r\n\r\n/** The free trial page, tagged with where the visitor came from (`trialUrl('gantt')`). */\r\nexport function trialUrl(ref: string): string {\r\n return `https://ninthforge.com/trial?ref=${encodeURIComponent(ref)}`;\r\n}\r\n\r\n/** The trial message for a locked action (Matt, 2026-11-08). */\r\nexport function lockedMessage(product: string): string {\r\n return `Saving changes needs a Ninth Forge ${product} key. Start a free 30-day trial at ninthforge.com/trial.`;\r\n}\r\n\r\n/**\r\n * The editing lock for a licence check. `license` is the result (`null` while checking) and\r\n * `unlocked` is `unlocksPro(license)` from @ninthforge/license. Locked unless the key unlocks Pro\r\n * actions; while the check runs it's locked with no message, so nothing about it shows yet.\r\n */\r\nexport function lockFor(\r\n license: object | null | undefined,\r\n unlocked: boolean,\r\n product: string,\r\n /** Adds the toast's \"Start a free trial\" button, to `trialUrl(trialRef)` (e.g. 'gantt'). */\r\n trialRef?: string\r\n): { locked: boolean; lockedMessage?: string; lockedTrialUrl?: string } {\r\n if (unlocked) return { locked: false };\r\n if (!license) return { locked: true };\r\n return trialRef ? { locked: true, lockedMessage: lockedMessage(product), lockedTrialUrl: trialUrl(trialRef) } : { locked: true, lockedMessage: lockedMessage(product) };\r\n}\r\n\r\n/** Locked messages already toasted on this page: one toast per session, then only the status line. */\r\nconst toastedLocks = new Set<string>();\r\n/** For tests. */\r\nexport function resetLockedToasts(): void {\r\n toastedLocks.clear();\r\n}\r\n\r\n/**\r\n * True the first time a locked message is shown on this page, and marks it shown: one toast per\r\n * page, shared by every Ninth Forge component (`useEditing`'s `refuse`, and components with their\r\n * own editing, such as Kanban Pro). Later refusals only announce and show the status line.\r\n */\r\nexport function firstLockToast(message: string): boolean {\r\n if (toastedLocks.has(message)) return false;\r\n toastedLocks.add(message);\r\n return true;\r\n}\r\n\r\n/** Opens the trial page in a new tab (an ordinary link the user chose; the component makes no request). */\r\nexport function openTrial(url: string): void {\r\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener');\r\n}\r\n\r\n/**\r\n * Optimistic edits.\r\n *\r\n * - `overrides` hold each committed edit until the host's items show it (or the save rejects).\r\n * - Undo/redo keep the last 20 changes; undo commits the previous values as a new change,\r\n * so hosts save it like any other edit.\r\n * - A rejected save reverts its override, restores history, and shows an error toast.\r\n */\r\nexport function useEditing<T extends { id: string }, F extends object, C extends Change<F> = Change<F>>(\r\n items: T[],\r\n onChange: ((c: C) => void | Promise<void>) | undefined,\r\n spec: EditingSpec<T, F>\r\n) {\r\n const [overrides, setOverrides] = useState<Map<string, Override<F>>>(() => new Map());\r\n const historyRef = useRef<History<C>>(emptyHistory<C>());\r\n const [, setHistoryVersion] = useState(0);\r\n const setHistory = (fn: (h: History<C>) => History<C>) => {\r\n historyRef.current = fn(historyRef.current);\r\n setHistoryVersion((v) => v + 1);\r\n };\r\n const seqRef = useRef(0);\r\n const [toasts, setToasts] = useState<ToastItem[]>([]);\r\n const [announcement, setAnnouncement] = useState({ text: '', n: 0 });\r\n const timers = useRef(new Map<number, ReturnType<typeof setTimeout>>());\r\n\r\n const itemsRef = useRef(items);\r\n itemsRef.current = items;\r\n const handlerRef = useRef(onChange);\r\n handlerRef.current = onChange;\r\n const specRef = useRef(spec);\r\n specRef.current = spec;\r\n\r\n // Host data caught up → drop the matching overrides.\r\n useEffect(() => {\r\n setOverrides((o) => (o.size ? reconcileOverrides(items, o, specRef.current) : o));\r\n }, [items]);\r\n\r\n useEffect(() => {\r\n const map = timers.current;\r\n return () => map.forEach((t) => clearTimeout(t));\r\n }, []);\r\n\r\n const dismiss = useCallback((id: number) => {\r\n const t = timers.current.get(id);\r\n if (t) clearTimeout(t);\r\n timers.current.delete(id);\r\n setToasts((list) => list.filter((x) => x.id !== id));\r\n }, []);\r\n\r\n const toast = useCallback(\r\n (item: Omit<ToastItem, 'id'>) => {\r\n const id = ++seqRef.current;\r\n // Keep every error, but only the latest info toast.\r\n setToasts((list) => [...list.filter((x) => x.tone === 'error'), { ...item, id }].slice(-4));\r\n timers.current.set(\r\n id,\r\n setTimeout(() => dismiss(id), item.tone === 'error' ? ERROR_MS : INFO_MS)\r\n );\r\n },\r\n [dismiss]\r\n );\r\n\r\n const announce = useCallback((text: string) => setAnnouncement((a) => ({ text, n: a.n + 1 })), []);\r\n\r\n // View-only: say why the action didn't happen (a toast once per page, then the status line).\r\n const [lockNote, setLockNote] = useState<string | null>(null);\r\n const refuse = useCallback(() => {\r\n const msg = specRef.current.lockedMessage;\r\n if (!msg) return; // still checking the key: stay quiet\r\n announce(msg);\r\n setLockNote(msg);\r\n if (!firstLockToast(msg)) return;\r\n const url = specRef.current.lockedTrialUrl;\r\n toast({ text: msg, tone: 'info', action: url ? { label: specRef.current.lockedTrialLabel ?? 'Start a free trial', run: () => openTrial(url) } : undefined });\r\n }, [announce, toast]);\r\n\r\n const nameOf = (id: string) => {\r\n const item = itemsRef.current.find((t) => t.id === id);\r\n const s = specRef.current;\r\n if (!item) return s.noun ?? 'item';\r\n return s.name ? s.name(item) : ((item as unknown as { name?: string }).name ?? s.noun ?? 'item');\r\n };\r\n\r\n // `undo`/`redo` and `commit` refer to each other through refs so toasts can call them later.\r\n const undoRef = useRef<() => void>(() => undefined);\r\n const redoRef = useRef<() => void>(() => undefined);\r\n\r\n const commit = (change: C, mode: 'new' | 'undo' | 'redo', entry?: C) => {\r\n if (specRef.current.locked) return refuse();\r\n const seq = ++seqRef.current;\r\n setOverrides((m) => {\r\n const next = new Map(m);\r\n next.set(change.id, { patch: { ...m.get(change.id)?.patch, ...fieldsOf(change, specRef.current) }, seq });\r\n return next;\r\n });\r\n if (mode === 'new') setHistory((h) => pushHistory(h, change));\r\n\r\n const text = specRef.current.describe(change, nameOf);\r\n announce(text);\r\n toast({\r\n text,\r\n tone: 'info',\r\n action: mode === 'undo' ? { label: 'Redo', run: () => redoRef.current() } : { label: 'Undo', run: () => undoRef.current() }\r\n });\r\n\r\n let result: void | Promise<void>;\r\n try {\r\n result = handlerRef.current?.(change);\r\n } catch (err) {\r\n result = Promise.reject(err);\r\n }\r\n if (result && typeof (result as Promise<void>).then === 'function') {\r\n (result as Promise<void>).then(undefined, (err: unknown) => {\r\n setOverrides((m) => {\r\n const o = m.get(change.id);\r\n if (!o || o.seq !== seq) return m; // a newer edit owns this item now\r\n const next = new Map(m);\r\n next.delete(change.id);\r\n return next;\r\n });\r\n setHistory((h) => {\r\n if (mode === 'new') return dropFromHistory(h, change);\r\n if (!entry) return h;\r\n // Put the entry back where it was before the failed undo/redo.\r\n return mode === 'undo'\r\n ? { undo: [...h.undo, entry], redo: h.redo.filter((c) => c !== entry) }\r\n : { undo: h.undo.filter((c) => c !== entry), redo: [...h.redo, entry] };\r\n });\r\n const msg = errorText(err);\r\n toast({ text: `Couldn't save '${nameOf(change.id)}', so it was put back. ${msg}`, tone: 'error' });\r\n announce(`Not saved, put back. ${msg}`);\r\n });\r\n }\r\n };\r\n\r\n const undo = () => {\r\n const step = undoStep(historyRef.current as History<Change<F, string>>, 'undo', specRef.current);\r\n if (!step) return announce('Nothing to undo');\r\n setHistory(() => step.history as History<C>);\r\n commit(step.commit as C, 'undo', step.entry as C);\r\n };\r\n const redo = () => {\r\n const step = redoStep(historyRef.current as History<Change<F, string>>, 'redo');\r\n if (!step) return announce('Nothing to redo');\r\n setHistory(() => step.history as History<C>);\r\n commit(step.commit as C, 'redo', step.entry as C);\r\n };\r\n undoRef.current = undo;\r\n redoRef.current = redo;\r\n\r\n const shown = useMemo(() => applyOverrides(items, overrides), [items, overrides]);\r\n\r\n return {\r\n overrides,\r\n shown,\r\n commit: (change: C) => commit(change, 'new'),\r\n undo,\r\n redo,\r\n canUndo: historyRef.current.undo.length > 0,\r\n canRedo: historyRef.current.redo.length > 0,\r\n toasts,\r\n toast,\r\n dismiss,\r\n announcement,\r\n announce,\r\n /** View-only: Pro actions are refused (see `EditingSpec.locked`). */\r\n locked: !!spec.locked,\r\n /** Call instead of an action that bypasses `commit` (create, add) while `locked`. */\r\n refuse,\r\n /** The locked message once an action was refused, for a quiet status line. */\r\n lockNote: spec.locked ? lockNote : null\r\n };\r\n}\r\n","import * as React from 'react';\n\nexport interface ToastItem {\n id: number;\n text: string;\n tone: 'info' | 'error';\n action?: { label: string; run: () => void };\n}\n\n/**\n * Toasts plus a visually hidden live region. Error toasts use role=\"alert\";\n * every edit is also announced politely so screen-reader users hear the result.\n * Class names are `<prefix>-sr`, `<prefix>-toasts`, `<prefix>-toast…`; each component\n * styles them (the Gantt uses the `nf-g` prefix).\n */\nexport function Toasts({\n items,\n announcement,\n onDismiss,\n prefix = 'nf-g'\n}: {\n items: ToastItem[];\n announcement: { text: string; n: number };\n onDismiss: (id: number) => void;\n prefix?: string;\n}) {\n return (\n <>\n <div className={`${prefix}-sr`} aria-live=\"polite\" aria-atomic=\"true\">\n {/* The key forces a re-announcement when the same text repeats. */}\n <span key={announcement.n}>{announcement.text}</span>\n </div>\n {items.length ? (\n <div className={`${prefix}-toasts`}>\n {items.map((t) => (\n <div key={t.id} className={`${prefix}-toast`} data-tone={t.tone} role={t.tone === 'error' ? 'alert' : 'status'}>\n <span className={`${prefix}-toast-text`}>{t.text}</span>\n {t.action ? (\n <button\n type=\"button\"\n className={`${prefix}-toast-btn`}\n onClick={() => {\n t.action!.run();\n onDismiss(t.id);\n }}\n >\n {t.action.label}\n </button>\n ) : null}\n <button type=\"button\" className={`${prefix}-toast-x`} aria-label=\"Dismiss\" onClick={() => onDismiss(t.id)}>\n ×\n </button>\n </div>\n ))}\n </div>\n ) : null}\n </>\n );\n}\n","import * as React from 'react';\n\n/**\n * One action offered in a component's empty state, e.g. \"Load sample data\" on a Ninth Forge\n * sample table. The host decides when to offer it and runs it; the component only shows it.\n */\nexport interface EmptyAction {\n label: string;\n onClick: () => void;\n /** Running: the button gives way to `status` (\"Loading sample data… 120 of 380\"). */\n busy?: boolean;\n status?: string;\n /** Shown under the button, e.g. what the action does. */\n hint?: string;\n /** The last attempt failed (the button stays, so the user can retry). */\n error?: string;\n}\n\n/**\n * The action's button, progress and error. Class names are `<prefix>-emptyaction` and the\n * component's own button class, so each component styles it like its toolbar.\n */\nexport function EmptyActionView({ action, prefix, buttonClass }: { action: EmptyAction; prefix: string; buttonClass: string }) {\n return (\n <div className={`${prefix}-emptyaction`}>\n {action.busy ? (\n <span role=\"status\">{action.status ?? `${action.label}…`}</span>\n ) : (\n <button type=\"button\" className={buttonClass} onClick={action.onClick}>\n {action.label}\n </button>\n )}\n {action.hint && !action.busy ? <small>{action.hint}</small> : null}\n {action.error ? <span role=\"alert\">{action.error}</span> : null}\n </div>\n );\n}\n","import * as React from 'react';\r\n\r\n/**\r\n * A licence key field offered beside \"Load sample data\" on a Ninth Forge sample table. The host\r\n * decides when to offer it and saves the key; the component only shows it.\r\n */\r\nexport interface LicenceKeyField {\r\n onSave: (key: string) => void;\r\n busy?: boolean;\r\n /** Saved: what happens now. */\r\n message?: string;\r\n /** The key was refused or not saved (the field keeps the text, so the user can fix it). */\r\n error?: string;\r\n}\r\n\r\nlet nextId = 0;\r\n\r\n/**\r\n * The field, its Save button and the result. Class names are `<prefix>-keyfield` and the\r\n * component's own button class, so each component styles it like its toolbar.\r\n */\r\nexport function LicenceKeyFieldView({ field, prefix, buttonClass }: { field: LicenceKeyField; prefix: string; buttonClass: string }) {\r\n const [text, setText] = React.useState('');\r\n const id = React.useRef(`${prefix}-key-${++nextId}`).current;\r\n const save = () => field.onSave(text);\r\n return (\r\n <div className={`${prefix}-keyfield`}>\r\n <label htmlFor={id}>Licence key</label>\r\n <span>\r\n <input\r\n id={id}\r\n type=\"text\"\r\n value={text}\r\n placeholder=\"Paste your trial key\"\r\n autoComplete=\"off\"\r\n spellCheck={false}\r\n disabled={field.busy}\r\n aria-describedby={field.error || field.message ? `${id}-result` : undefined}\r\n onChange={(e) => setText(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') save();\r\n }}\r\n />\r\n <button type=\"button\" className={buttonClass} onClick={save} disabled={field.busy}>\r\n {field.busy ? 'Saving…' : 'Save'}\r\n </button>\r\n </span>\r\n {field.error ? (\r\n <span id={`${id}-result`} role=\"alert\">\r\n {field.error}\r\n </span>\r\n ) : field.message ? (\r\n <span id={`${id}-result`} role=\"status\">\r\n {field.message}\r\n </span>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n","import * as React from 'react';\r\n\r\n/** Where the Plan watermark links when the host doesn't handle the click. */\r\nexport const PLAN_WATERMARK_URL = 'https://ninthforge.com/plan?ref=watermark';\r\n\r\nexport interface WatermarkProps {\r\n /** Class prefix, e.g. `nf-g`: the element gets `${prefix}-mark`. */\r\n prefix: string;\r\n /** The licence message for makers (`licenseMessage(result)`), shown as the title. */\r\n title: string;\r\n /** The brand mark's SVG (`markSvg({ size: 16 })`). Not drawn with `variant: 'text'`. */\r\n markHtml: string;\r\n /** `text`: words only, no coloured mark (Power BI certification). Default `mark`. */\r\n variant?: 'mark' | 'text';\r\n /**\r\n * The host handles the click (Power BI opens links through its own API): a button instead\r\n * of a link.\r\n */\r\n onClick?: () => void;\r\n href?: string;\r\n}\r\n\r\n/** \"Ninth Forge · Unlicensed\", for Plan components without a licence. */\r\nexport function Watermark({ prefix, title, markHtml, variant = 'mark', onClick, href = PLAN_WATERMARK_URL }: WatermarkProps) {\r\n const mark =\r\n variant === 'text' ? null : <span aria-hidden=\"true\" style={{ display: 'inline-flex' }} dangerouslySetInnerHTML={{ __html: markHtml }} />;\r\n return onClick ? (\r\n <button type=\"button\" className={`${prefix}-mark`} title={title} onClick={onClick}>\r\n {mark}\r\n Ninth Forge · Unlicensed\r\n </button>\r\n ) : (\r\n <a className={`${prefix}-mark`} href={href} target=\"_blank\" rel=\"noreferrer\" title={title}>\r\n {mark}\r\n Ninth Forge · Unlicensed\r\n </a>\r\n );\r\n}\r\n\r\n/** The host props every Plan component takes for its watermark. */\r\nexport interface WatermarkHostProps {\r\n /**\r\n * `false` draws no watermark at all (no link, no space). The licence is still checked; the\r\n * host decides when, e.g. Power BI Desktop for authoring (DECISIONS 2026-10-31). Default true.\r\n */\r\n watermark?: boolean;\r\n /** `text`: words only, no coloured mark (Power BI certification). Default `mark`. */\r\n watermarkStyle?: 'mark' | 'text';\r\n /** The host opens the link itself (Power BI's `launchUrl`): the watermark becomes a button. */\r\n onWatermarkClick?: () => void;\r\n}\r\n\r\nlet next = 0;\r\n\r\n/**\r\n * A page-unique id prefix, for React 16 (no `useId`). A counter, not `Math.random`: Power BI\r\n * certification rejects any `Math.random`.\r\n */\r\nexport function nextUid(prefix: string): string {\r\n next += 1;\r\n return `${prefix}${next.toString(36)}`;\r\n}\r\n","export const PREFIX = 'NF1';\n\nexport function utf8(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\nexport function b64urlEncode(bytes: Uint8Array): string {\n let bin = '';\n bytes.forEach((b) => (bin += String.fromCharCode(b)));\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport function b64urlDecode(s: string): Uint8Array {\n if (!/^[A-Za-z0-9_-]*$/.test(s)) throw new Error('Invalid base64url');\n const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));\n const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/') + pad);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n if (!/^[0-9a-f]*$/i.test(hex) || hex.length % 2 !== 0) throw new Error('Invalid hex');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** WebCrypto, available in every modern browser, Node 20+, and Cloudflare Workers. */\nexport function subtle(): SubtleCrypto | undefined {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n return c && c.subtle ? c.subtle : undefined;\n}\n\nexport const ED25519 = { name: 'Ed25519' } as const;\n","/** Lowercase a host name and drop any trailing dots (`WWW.Example.com.` → `www.example.com`). */\nexport function normaliseHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.+$/, '');\n}\n\n/**\n * The page's host name, normalised, or `undefined` outside a browser (SSR, tests, workers\n * without `location`). Components pass it as `host` to `verifyLicense`, so host-bound demo\n * keys work only on the hosts they name. Every component uses this one helper; none reads\n * `location` itself.\n */\nexport function browserHost(): string | undefined {\n const loc = (globalThis as { location?: { hostname?: unknown } }).location;\n const name = loc && typeof loc.hostname === 'string' ? normaliseHost(loc.hostname) : '';\n return name || undefined;\n}\n\n/**\n * Does `host` match a key's `hosts` entry? An exact entry matches only itself. `*.name`\n * matches any subdomain of `name` (`a.name`, `a.b.name`), but not `name` itself and never\n * `evilname`: the dot is part of the suffix.\n */\nexport function hostMatches(host: string, pattern: string): boolean {\n const h = normaliseHost(host);\n const p = normaliseHost(pattern);\n if (!h || !p) return false;\n if (p.startsWith('*.')) {\n const suffix = p.slice(1); // \".name\"\n return suffix.length > 1 && h.length > suffix.length && h.endsWith(suffix);\n }\n return h === p;\n}\n","import { b64urlDecode, ED25519, hexToBytes, PREFIX, subtle, utf8 } from './codec';\r\nimport { hostMatches, normaliseHost } from './browser';\r\nimport type { LicensePayload, LicenseResult, Product, Tier } from './types';\r\n\r\nexport type { LicensePayload, LicenseResult, LicenseStatus, Product, Tier } from './types';\r\nexport { browserHost, hostMatches, normaliseHost } from './browser';\r\n\r\n/** Days a key keeps working after `exp`, with a renewal notice to makers. */\r\nexport const GRACE_DAYS = 14;\r\n\r\n/**\r\n * Products a `suite` key unlocks: the Suite is Plan + Charts (licensing.md step 3, 2026-10-25).\r\n * It does not unlock Driver Tree or any future product; change this deliberately, with tests.\r\n */\r\nexport const SUITE_PRODUCTS: readonly Exclude<Product, 'suite'>[] = ['plan', 'charts'];\r\n\r\n/**\r\n * Production public keys by key id (hex, 32 bytes).\r\n * Generate with `pnpm --filter @ninthforge/license keygen <kid>` and paste the\r\n * PUBLIC key here. Private keys live only in the licence worker's secrets.\r\n */\r\nexport const PUBLIC_KEYS: Record<string, string> = {\r\n 'nf-2027-1': 'ad97c33249018681b3b315a303551442552d917d337690cac9b4783e931f88d1'\r\n};\r\n\r\nexport interface VerifyOptions {\r\n /** The product this component belongs to. */\r\n product: Exclude<Product, 'suite'>;\r\n /**\r\n * Host tenant ID; omit when unknown (code apps in local dev). `null` counts as unknown: the Power\r\n * Apps visual in Power BI passes `orgSettings.tenantId` as null (probe 2, 2026-11-02).\r\n */\r\n tenantId?: string | null;\r\n /**\r\n * The web host the component runs on (use `browserHost()`). Only host-bound demo keys\r\n * (v2) read it; v1 keys ignore it.\r\n */\r\n host?: string;\r\n now?: Date;\r\n /** Override trusted keys (tests, staging). Defaults to PUBLIC_KEYS. */\r\n publicKeys?: Record<string, string>;\r\n}\r\n\r\nconst DAY = 86_400_000;\r\n\r\nfunction isoToUtcMs(iso: string): number {\r\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\r\n if (!m) return NaN;\r\n return Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\r\n}\r\n\r\nfunction parsePayload(segment: string): LicensePayload | undefined {\r\n try {\r\n const json = JSON.parse(new TextDecoder().decode(b64urlDecode(segment)));\r\n if (!json || typeof json.kid !== 'string' || typeof json.exp !== 'string') return undefined;\r\n if (json.v === 1) return json as LicensePayload;\r\n // v2: host-bound demo key; must name at least one host.\r\n if (\r\n json.v === 2 &&\r\n Array.isArray(json.hosts) &&\r\n json.hosts.length > 0 &&\r\n json.hosts.every((h: unknown) => typeof h === 'string' && h.trim() !== '')\r\n ) {\r\n return json as LicensePayload;\r\n }\r\n } catch {\r\n /* fall through */\r\n }\r\n return undefined;\r\n}\r\n\r\nasync function verifySignature(pubHex: string, message: Uint8Array, signature: Uint8Array): Promise<boolean | 'unsupported'> {\r\n const s = subtle();\r\n if (!s) return 'unsupported';\r\n let key: CryptoKey;\r\n try {\r\n key = await s.importKey('raw', hexToBytes(pubHex) as BufferSource, ED25519, false, ['verify']);\r\n } catch (err) {\r\n // NotSupportedError → the browser lacks Ed25519; anything else is a bad key.\r\n return (err as { name?: string })?.name === 'NotSupportedError' ? 'unsupported' : false;\r\n }\r\n try {\r\n return await s.verify(ED25519, key, signature as BufferSource, message as BufferSource);\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Verify a licence key offline. Never throws; never makes a network call.\r\n * Uses the platform's WebCrypto Ed25519, so it adds no bytes to the bundle.\r\n */\r\nexport async function verifyLicense(key: string | null | undefined, opts: VerifyOptions): Promise<LicenseResult> {\r\n const trimmed = (key ?? '').trim();\r\n if (!trimmed) return { status: 'missing', licensed: false };\r\n\r\n const parts = trimmed.split('.');\r\n if (parts.length !== 3 || parts[0] !== PREFIX) return { status: 'invalid', licensed: false };\r\n const [, payloadSeg, sigSeg] = parts as [string, string, string];\r\n\r\n const payload = parsePayload(payloadSeg);\r\n const keys = opts.publicKeys ?? PUBLIC_KEYS;\r\n const pubHex = payload ? keys[payload.kid] : undefined;\r\n if (!payload || !pubHex) return { status: 'invalid', licensed: false };\r\n\r\n let sig: Uint8Array;\r\n try {\r\n sig = b64urlDecode(sigSeg);\r\n } catch {\r\n return { status: 'invalid', licensed: false };\r\n }\r\n\r\n const ok = await verifySignature(pubHex, utf8(payloadSeg), sig);\r\n if (ok === false) return { status: 'invalid', licensed: false };\r\n\r\n // Host-bound demo keys (v2): only on the named hosts, and never where a tenant is known.\r\n // PCF wrappers always pass the Dataverse tenant, so a demo key can't unlock Power Apps.\r\n // Checked before the fail-open below, so an old browser doesn't widen a demo key either.\r\n if (payload.v === 2) {\r\n const host = opts.host === undefined ? '' : normaliseHost(opts.host);\r\n const onHost = host !== '' && (payload.hosts ?? []).some((p) => hostMatches(host, p));\r\n if ((opts.tenantId !== undefined && opts.tenantId !== null) || !onHost) return { status: 'wrong-host', licensed: false, payload };\r\n }\r\n\r\n if (ok === 'unsupported') return { status: 'unverifiable', licensed: true, payload };\r\n\r\n const suiteCovers = payload.pid === 'suite' && SUITE_PRODUCTS.includes(opts.product);\r\n if (payload.pid !== opts.product && !suiteCovers) {\r\n return { status: 'wrong-product', licensed: false, payload };\r\n }\r\n\r\n const tenant = typeof opts.tenantId === 'string' && opts.tenantId.trim() ? opts.tenantId : undefined;\r\n if (payload.v === 1 && payload.tid !== '*' && tenant !== undefined && payload.tid.toLowerCase() !== tenant.toLowerCase()) {\r\n return { status: 'wrong-tenant', licensed: false, payload };\r\n }\r\n\r\n const now = (opts.now ?? new Date()).getTime();\r\n const expMs = isoToUtcMs(payload.exp) + DAY; // inclusive expiry day\r\n if (Number.isNaN(expMs)) return { status: 'invalid', licensed: false };\r\n const daysLeft = Math.floor((expMs - now) / DAY);\r\n\r\n if (now > expMs + GRACE_DAYS * DAY) return { status: 'expired', licensed: false, payload, daysLeft };\r\n if (now > expMs) return { status: 'grace', licensed: true, payload, daysLeft };\r\n return { status: 'valid', licensed: true, payload, daysLeft };\r\n}\r\n\r\n/** How useful a failed result is to the maker: the key meant for this product says most. */\r\nconst FAILURE_RANK: Partial<Record<LicenseResult['status'], number>> = {\r\n expired: 0,\r\n 'wrong-tenant': 1,\r\n 'wrong-host': 2,\r\n 'wrong-product': 3,\r\n invalid: 4,\r\n missing: 5\r\n};\r\n\r\n/**\r\n * Verify one or more keys (separated by spaces, new lines, commas or semicolons), as an\r\n * environment variable holding a tenant's Plan and Charts keys does. Returns the first key\r\n * that licenses this product; otherwise the most telling failure (an expired key for this\r\n * product beats a key for another product). One key behaves exactly like `verifyLicense`.\r\n */\r\nexport async function verifyLicenses(keys: string | null | undefined, opts: VerifyOptions): Promise<LicenseResult> {\r\n const list = (keys ?? '').split(/[\\s,;]+/).filter(Boolean);\r\n if (list.length <= 1) return verifyLicense(list[0], opts);\r\n let best: LicenseResult | undefined;\r\n for (const key of list) {\r\n const r = await verifyLicense(key, opts);\r\n if (r.licensed) return r;\r\n if (!best || (FAILURE_RANK[r.status] ?? 9) < (FAILURE_RANK[best.status] ?? 9)) best = r;\r\n }\r\n return best ?? { status: 'missing', licensed: false };\r\n}\r\n\r\n/** Where a grace-period notice sends makers: the \"find my keys\" page, which renews too. */\r\nexport const RENEW_URL = 'https://ninthforge.com/licence?ref=renewal';\r\n\r\nexport interface RenewalNotice {\r\n /** Short text for a small notice, e.g. \"Licence expired · renew within 9 days\". */\r\n text: string;\r\n /** Full sentence for the notice's title and screen readers. */\r\n detail: string;\r\n href: string;\r\n /** Whole days until the watermark returns (1–14). */\r\n daysLeft: number;\r\n}\r\n\r\n/**\r\n * The renewal notice a component shows **to makers only** while a licence is in grace\r\n * (`docs/specs/licensing.md` step 5), or `null` in every other state. The licence check stays\r\n * offline: this only formats the result. Each component decides who counts as a maker (the PCF\r\n * wrappers pass it in; code apps choose with a prop).\r\n */\r\nexport function renewalNotice(result: LicenseResult | null | undefined, now: Date = new Date()): RenewalNotice | null {\r\n if (!result || result.status !== 'grace' || !result.payload) return null;\r\n const expMs = isoToUtcMs(result.payload.exp) + DAY;\r\n if (Number.isNaN(expMs)) return null;\r\n const daysLeft = Math.min(GRACE_DAYS, Math.max(1, Math.ceil((expMs + GRACE_DAYS * DAY - now.getTime()) / DAY)));\r\n const days = daysLeft === 1 ? '1 day' : `${daysLeft} days`;\r\n return {\r\n text: `Licence expired · renew within ${days}`,\r\n detail: `Your Ninth Forge licence expired on ${result.payload.exp}. Renew within ${days} to keep the watermark off. Only makers see this.`,\r\n href: RENEW_URL,\r\n daysLeft\r\n };\r\n}\r\n\r\n/** Short, human message for makers (never shown to end users). */\r\nexport function licenseMessage(result: LicenseResult): string {\r\n switch (result.status) {\r\n case 'valid':\r\n case 'unverifiable':\r\n return `Licensed to ${result.payload?.org ?? 'your organisation'}`;\r\n case 'grace':\r\n return 'Licence expired — renew within 14 days to keep the watermark off';\r\n case 'expired':\r\n return 'Licence expired';\r\n case 'missing':\r\n return 'Unlicensed — add a licence key in the control properties';\r\n case 'wrong-product':\r\n return 'This key is for a different Ninth Forge product';\r\n case 'wrong-tenant':\r\n return 'This key belongs to a different Microsoft tenant';\r\n case 'wrong-host':\r\n return `This demo key only works on ${result.payload?.hosts?.[0] ?? 'the Ninth Forge website'}`;\r\n default:\r\n return 'Licence key not recognised';\r\n }\r\n}\r\n\r\n/** Paid tiers keep Pro actions after they expire (\"nothing ever breaks\" for customers who lapse). */\r\nconst PAID_TIERS: readonly Tier[] = ['team', 'business', 'enterprise'];\r\n\r\n/**\r\n * True when Pro actions (editing, saving, what-if…) are allowed (Matt, 2026-11-08: \"see\r\n * everything free; a key unlocks the work\"). Valid, grace and unverifiable keys unlock. A paid\r\n * key that has expired still unlocks, with the watermark. Missing, invalid, wrong-product,\r\n * wrong-tenant and wrong-host keys don't, and neither do expired trial, developer or demo keys.\r\n * `null` (still checking) is locked: draw nothing about it until the result is in.\r\n * The watermark is unchanged: `licensed` still decides it.\r\n */\r\nexport function unlocksPro(r: LicenseResult | null | undefined): boolean {\r\n if (!r) return false;\r\n if (r.licensed) return true;\r\n return r.status === 'expired' && !!r.payload && PAID_TIERS.includes(r.payload.tier);\r\n}\r\n","/**\r\n * Text size: type roles and one scale for every component (the same roles in Power Apps and Power BI).\r\n *\r\n * Components size text by role, never by a pixel number, so one setting (\"Text size\", 75–200 %)\r\n * makes every label, value and title bigger or smaller together. The same numbers reach CSS\r\n * (`--nf-text-<role>`) and layout maths (`textSize(role, scale)`), so label fitting and row\r\n * heights match what the browser draws.\r\n */\r\n\r\n/** What the text is for, not how big it is. */\r\nexport type TextRole = 'caption' | 'body' | 'label' | 'value' | 'callout' | 'header' | 'title' | 'hero';\r\n\r\n/** Every role, in the spec's order. */\r\nexport const TEXT_ROLES: readonly TextRole[] = ['caption', 'body', 'label', 'value', 'callout', 'header', 'title', 'hero'];\r\n\r\n/**\r\n * Sizes at 100 %, in CSS pixels.\r\n * - `caption`: legends, footnotes, axis titles, \"+N\"\r\n * - `body`: grid and table cells, task and card text, node and person names (dense UI text)\r\n * - `label`: chart axis ticks and category labels\r\n * - `value`: data labels, values in cards and cells\r\n * - `callout`: Δ callouts, annotations, badges\r\n * - `header`: table, column, lane and group headers (semibold)\r\n * - `title`: in-component titles and section headings\r\n * - `hero`: the big number on KPI cards and gauges\r\n */\r\nexport const TEXT_SIZES: Readonly<Record<TextRole, number>> = {\r\n caption: 11,\r\n body: 13,\r\n label: 12,\r\n value: 12,\r\n callout: 12,\r\n header: 13,\r\n title: 16,\r\n hero: 28\r\n};\r\n\r\n/** Per-role sizes at 100 %, in CSS pixels, that replace the defaults. The scale still applies. */\r\nexport type TextSizes = Partial<Record<TextRole, number>>;\r\n\r\n/** The smallest and largest text scale (75 % and 200 %). */\r\nexport const TEXT_SCALE_MIN = 0.75;\r\nexport const TEXT_SCALE_MAX = 2;\r\n\r\n/** A usable scale: 0.75–2, and 1 for anything that isn't a positive number. */\r\nexport function clampTextScale(scale: number | null | undefined): number {\r\n if (typeof scale !== 'number' || !Number.isFinite(scale) || scale <= 0) return 1;\r\n return Math.min(TEXT_SCALE_MAX, Math.max(TEXT_SCALE_MIN, scale));\r\n}\r\n\r\n/**\r\n * A scale from a percent, as the PCF \"Text size\" input and the Power BI format pane give it\r\n * (75–200, default 100). Empty, zero or garbage gives 1.\r\n */\r\nexport function textScaleFromPercent(percent: number | string | null | undefined): number {\r\n const n = typeof percent === 'string' ? Number(percent.trim().replace(/%$/, '')) : percent;\r\n if (typeof n !== 'number' || !Number.isFinite(n) || n <= 0) return 1;\r\n return clampTextScale(n / 100);\r\n}\r\n\r\n/**\r\n * Per-role sizes from a maker's JSON (the PCF's optional override input), e.g.\r\n * `{\"label\": 13, \"title\": 18}`. Unknown roles and sizes outside 6–96 px are ignored, and bad\r\n * JSON gives `{}`: a typo never breaks the control.\r\n */\r\nexport function parseTextSizes(json: string | null | undefined): TextSizes {\r\n if (!json || !json.trim()) return {};\r\n let raw: unknown;\r\n try {\r\n raw = JSON.parse(json);\r\n } catch {\r\n return {};\r\n }\r\n const out: TextSizes = {};\r\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return out;\r\n for (const role of TEXT_ROLES) {\r\n const v = (raw as Record<string, unknown>)[role];\r\n if (typeof v === 'number' && Number.isFinite(v) && v >= 6 && v <= 96) out[role] = v;\r\n }\r\n return out;\r\n}\r\n\r\n/** Rounds to the nearest half pixel, which browsers draw crisply. */\r\nconst half = (px: number): number => Math.round(px * 2) / 2;\r\n\r\n/** A role's size in CSS pixels at a scale (1 = 100 %), for layout maths and canvas text. */\r\nexport function textSize(role: TextRole, scale = 1, sizes?: TextSizes): number {\r\n const base = sizes?.[role] ?? TEXT_SIZES[role];\r\n return half(base * clampTextScale(scale));\r\n}\r\n\r\n/** The `--nf-text-<role>` CSS variables for a scale, e.g. `{ '--nf-text-label': '12px' }`. */\r\nexport function textVars(scale = 1, sizes?: TextSizes): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n for (const role of TEXT_ROLES) out[`--nf-text-${role}`] = `${textSize(role, scale, sizes)}px`;\r\n return out;\r\n}\r\n\r\n/** `var(--nf-text-<role>)`, for inline styles and CSS strings, with the 100 % size as fallback. */\r\nexport function textVar(role: TextRole): string {\r\n return `var(--nf-text-${role},${TEXT_SIZES[role]}px)`;\r\n}\r\n\r\n/**\r\n * A text-dependent length (row height, card height, padding) at a scale. At 100 % it is the\r\n * design value unchanged; at any other scale it grows or shrinks with the text and rounds up\r\n * to the 8 px grid, so rows stay aligned and text never clips.\r\n */\r\nexport function scaleWithText(px: number, scale = 1): number {\r\n const s = clampTextScale(scale);\r\n if (s === 1) return px;\r\n return Math.ceil((px * s) / 8) * 8;\r\n}\r\n","/**\r\n * Ninth Forge design tokens.\r\n * Brand colours carry the website and marketing; component themes are what\r\n * customers see inside their apps and can override with CSS variables.\r\n */\r\n\r\nexport const brand = {\r\n ink: '#0E1116',\r\n ember: '#D9480F',\r\n paper: '#F6F6F3',\r\n line: '#E4E5E9',\r\n muted: '#5D6472',\r\n cobalt: '#2E54FF'\r\n} as const;\r\n\r\nexport const semantic = {\r\n milestone: '#E8930C',\r\n positive: '#138A52',\r\n negative: '#D93F2B'\r\n} as const;\r\n\r\nexport const font = {\r\n sans: \"'Geist', 'Segoe UI', system-ui, -apple-system, sans-serif\",\r\n mono: \"'Geist Mono', 'Cascadia Mono', ui-monospace, monospace\",\r\n /** Components inherit the host app's font unless a theme says otherwise. */\r\n inherit: 'inherit'\r\n} as const;\r\n\r\nexport const radius = { bar: 6, control: 9, card: 16 } as const;\r\nexport const space = (n: number): number => n * 8;\r\n\r\nexport const motion = {\r\n fast: '150ms',\r\n base: '220ms',\r\n ease: 'cubic-bezier(0.2, 0.8, 0.2, 1)'\r\n} as const;\r\n\r\n/** A component theme. Every key becomes a `--nf-<kebab-key>` CSS variable. */\r\nexport interface ComponentTheme {\r\n accent: string;\r\n accentTint: string;\r\n accentText: string;\r\n surface: string;\r\n surfaceAlt: string;\r\n ink: string;\r\n muted: string;\r\n line: string;\r\n lineSoft: string;\r\n phase: string;\r\n milestone: string;\r\n baseline: string;\r\n selected: string;\r\n focus: string;\r\n positive: string;\r\n negative: string;\r\n tooltipBg: string;\r\n tooltipInk: string;\r\n font: string;\r\n monoFont: string;\r\n}\r\n\r\nexport const lightTheme: ComponentTheme = {\r\n accent: brand.cobalt,\r\n accentTint: '#DCE3FF',\r\n accentText: '#FFFFFF',\r\n surface: '#FFFFFF',\r\n surfaceAlt: '#F6F6F3',\r\n ink: brand.ink,\r\n muted: brand.muted,\r\n line: brand.line,\r\n lineSoft: '#F2F3F5',\r\n phase: brand.ink,\r\n milestone: semantic.milestone,\r\n baseline: '#B9BDC7',\r\n selected: '#F4F6FF',\r\n focus: brand.cobalt,\r\n positive: semantic.positive,\r\n negative: semantic.negative,\r\n tooltipBg: brand.ink,\r\n tooltipInk: '#FFFFFF',\r\n font: font.inherit,\r\n monoFont: font.mono\r\n};\r\n\r\nexport const darkTheme: ComponentTheme = {\r\n accent: '#6F8BFF',\r\n accentTint: '#28305A',\r\n accentText: '#0E1116',\r\n surface: '#14171D',\r\n surfaceAlt: '#0E1116',\r\n ink: '#F2F3F5',\r\n muted: '#A3A9B6',\r\n line: '#2A2F3A',\r\n lineSoft: '#1D2129',\r\n phase: '#E4E5E9',\r\n milestone: '#F2A53A',\r\n baseline: '#5D6472',\r\n selected: '#1C2238',\r\n focus: '#8FA6FF',\r\n positive: '#3FBF7F',\r\n negative: '#F06A58',\r\n tooltipBg: '#F2F3F5',\r\n tooltipInk: '#0E1116',\r\n font: font.inherit,\r\n monoFont: font.mono\r\n};\r\n\r\nconst kebab = (s: string): string => s.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());\r\n\r\n/** Theme → `{ '--nf-accent': '#2E54FF', ... }`, ready for a React `style` prop. */\r\nexport function themeVars(theme: Partial<ComponentTheme> = {}, base: ComponentTheme = lightTheme): Record<string, string> {\r\n const merged = { ...base, ...theme } as ComponentTheme;\r\n const out: Record<string, string> = {};\r\n (Object.keys(merged) as (keyof ComponentTheme)[]).forEach((k) => {\r\n out[`--nf-${kebab(k)}`] = merged[k];\r\n });\r\n return out;\r\n}\r\n\r\n/** The IX mark as an SVG string. `ember` colours the glowing stroke tip. */\r\nexport interface MarkOptions {\r\n size?: number;\r\n tile?: boolean;\r\n ink?: string;\r\n ember?: string;\r\n}\r\n\r\nexport function markSvg({ size = 40, tile = true, ink = brand.ink as string, ember = brand.ember as string }: MarkOptions = {}): string {\r\n const stroke = tile ? '#FFFFFF' : ink;\r\n return (\r\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 40 40\" role=\"img\" aria-label=\"Ninth Forge\">` +\r\n (tile ? `<rect width=\"40\" height=\"40\" rx=\"10\" fill=\"${ink}\"/>` : '') +\r\n `<rect x=\"8.5\" y=\"10\" width=\"4.6\" height=\"20\" fill=\"${stroke}\"/>` +\r\n `<path d=\"M17 10 L31.5 30\" stroke=\"${stroke}\" stroke-width=\"4.6\"/>` +\r\n `<path d=\"M31.5 10 L17 30\" stroke=\"${stroke}\" stroke-width=\"4.6\"/>` +\r\n `<path d=\"M31.5 10 L27.6 15.4\" stroke=\"${ember}\" stroke-width=\"4.6\"/>` +\r\n `</svg>`\r\n );\r\n}\r\n\r\n/* ---------- Contrast ---------- */\r\n\r\nfunction luminance(hex: string): number | null {\r\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());\r\n if (!m) return null;\r\n const h = m[1]!.length === 3 ? m[1]!.replace(/./g, (c) => c + c) : m[1]!;\r\n const ch = [0, 2, 4].map((i) => {\r\n const v = parseInt(h.slice(i, i + 2), 16) / 255;\r\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\r\n });\r\n return 0.2126 * ch[0]! + 0.7152 * ch[1]! + 0.0722 * ch[2]!;\r\n}\r\n\r\n/** WCAG contrast ratio between two hex colours (1–21), or null if either isn't a hex colour. */\r\nexport function contrastRatio(a: string, b: string): number | null {\r\n const la = luminance(a);\r\n const lb = luminance(b);\r\n if (la === null || lb === null) return null;\r\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\r\n return (hi + 0.05) / (lo + 0.05);\r\n}\r\n\r\n/**\r\n * Text colour for use on `background`: white or brand ink, whichever contrasts more.\r\n * Use it for text on a customer's accent (badges, tags, primary buttons). Non-hex input\r\n * returns white.\r\n */\r\nexport function readableTextOn(background: string, light = '#FFFFFF', dark: string = brand.ink): string {\r\n const onLight = contrastRatio(background, light);\r\n const onDark = contrastRatio(background, dark);\r\n if (onLight === null || onDark === null) return light;\r\n return onLight >= onDark ? light : dark;\r\n}\r\n\r\nfunction mixHex(a: string, b: string, t: number): string {\r\n const p = (h: string) => {\r\n const s = h.replace('#', '');\r\n const f = s.length === 3 ? s.replace(/./g, (c) => c + c) : s;\r\n return [0, 2, 4].map((i) => parseInt(f.slice(i, i + 2), 16));\r\n };\r\n const [x, y] = [p(a), p(b)];\r\n return '#' + x.map((v, i) => Math.round(v + (y[i]! - v) * t).toString(16).padStart(2, '0')).join('').toUpperCase();\r\n}\r\n\r\n/**\r\n * A background and text colour for small text on an accent (tags, badges, primary buttons)\r\n * that meets 4.5:1. Keeps the accent when it already passes; otherwise shades it — darker\r\n * under white text, lighter under ink — by the smallest step that passes. Non-hex accents\r\n * are returned unchanged with white text.\r\n */\r\nexport function accentForText(accent: string, light = '#FFFFFF', dark: string = brand.ink): { background: string; text: string } {\r\n const text = readableTextOn(accent, light, dark);\r\n if ((contrastRatio(accent, text) ?? 21) >= 4.5) return { background: accent, text };\r\n const toward = text === light ? '#000000' : '#FFFFFF';\r\n for (let t = 0.05; t <= 1.0001; t += 0.05) {\r\n const bg = mixHex(accent, toward, t);\r\n if ((contrastRatio(bg, text) ?? 0) >= 4.5) return { background: bg, text };\r\n }\r\n return { background: toward === '#000000' ? '#000000' : '#FFFFFF', text };\r\n}\r\n\r\n/**\r\n * A colour for a mark (a stripe, a dot, a bar) that must stay visible on `background`: `color`\r\n * itself when it reaches `ratio` (default 3:1, WCAG non-text), otherwise mixed toward black (or\r\n * white on a dark background) in 10% steps until it does. For data colours makers choose, such as\r\n * a Dataverse choice's colour. Non-hex input is returned unchanged.\r\n */\r\nexport function visibleOn(color: string, background: string, ratio = 3): string {\r\n const own = contrastRatio(color, background);\r\n if (own === null || own >= ratio) return color;\r\n const dark = (luminance(background) ?? 1) < 0.18;\r\n const toward = dark ? '#FFFFFF' : '#000000';\r\n for (let t = 0.1; t <= 1.0001; t += 0.1) {\r\n const mixed = mixHex(color, toward, t);\r\n if ((contrastRatio(mixed, background) ?? 0) >= ratio) return mixed;\r\n }\r\n return toward;\r\n}\r\n\r\nexport * from './text';\r\n","import { addMonths, dayOf, parts, toDay, type RoadmapScale, type RoadmapZoom } from '@ninthforge/core';\r\nimport { accentForText, semantic, scaleWithText } from '@ninthforge/tokens';\r\nimport type { TimelineItem } from './types';\r\n\r\n/* Pure layout for the Timeline: dates → day numbers, lanes, row packing, colours and the\r\n Now / Next / Later columns. No DOM, no React: everything here is unit-tested. */\r\n\r\nexport const ROW_H = 30;\r\nexport const LANE_PAD = 8;\r\nexport const LANE_MIN_H = 48;\r\nexport const LANE_HEAD_W = 200;\r\nexport const NO_LANE = 'No lane';\r\n\r\nconst TEXT = ['name', 'start', 'end', 'lane', 'status', 'horizon', 'color'] as const;\r\nconst asText = (v: unknown): string | undefined => (v === null || v === undefined ? undefined : typeof v === 'string' ? v : String(v));\r\n\r\n/**\r\n * Host data made safe: text fields as text, `dependsOn` as a list of ids, unique ids (first wins).\r\n * Clean items are returned as the same objects, so hosts get their own item back.\r\n */\r\nexport function cleanItems(items: readonly TimelineItem[]): TimelineItem[] {\r\n const seen = new Set<string>();\r\n const out: TimelineItem[] = [];\r\n for (const it of items) {\r\n if (!it || typeof it !== 'object') continue;\r\n const raw = it as unknown as Record<string, unknown>;\r\n const id = asText(raw.id);\r\n if (!id || seen.has(id)) continue;\r\n seen.add(id);\r\n const deps = raw.dependsOn;\r\n const clean =\r\n typeof raw.id === 'string' &&\r\n typeof raw.name === 'string' &&\r\n TEXT.every((k) => raw[k] === undefined || typeof raw[k] === 'string') &&\r\n (deps === undefined || (Array.isArray(deps) && deps.every((d) => typeof d === 'string'))) &&\r\n (raw.milestone === undefined || typeof raw.milestone === 'boolean');\r\n if (clean) {\r\n out.push(it);\r\n continue;\r\n }\r\n const fixed: Record<string, unknown> = { ...raw, id };\r\n for (const k of TEXT) fixed[k] = asText(raw[k]);\r\n fixed.name = asText(raw.name) ?? '';\r\n fixed.dependsOn = Array.isArray(deps) ? deps.map(asText).filter((d): d is string => !!d) : undefined;\r\n fixed.milestone = typeof raw.milestone === 'boolean' ? raw.milestone : undefined;\r\n out.push(fixed as unknown as TimelineItem);\r\n }\r\n return out;\r\n}\r\n\r\n/** An item with its dates resolved to UTC day numbers. */\r\nexport interface Placed {\r\n item: TimelineItem;\r\n id: string;\r\n /** Day number (inclusive). */\r\n start: number;\r\n /** Day number (inclusive). Equal to start for milestones. */\r\n end: number;\r\n milestone: boolean;\r\n}\r\n\r\n/** `YYYY-MM-DD…` → day number, or NaN. Only the leading date counts (no local-time getters). */\r\nexport function dayOfIso(v: unknown): number {\r\n if (typeof v !== 'string') return NaN;\r\n const m = /^(\\d{4}-\\d{2}-\\d{2})/.exec(v);\r\n if (!m) return NaN;\r\n const d = toDay(m[1]!);\r\n return Number.isFinite(d) ? d : NaN;\r\n}\r\n\r\n/**\r\n * Where an item goes. Rules (Matt, 2026-10-26):\r\n * - `milestone` set → it decides (true: a diamond on the start; false: a bar, even one day long)\r\n * - otherwise an empty end, or an end equal to the start, is a milestone\r\n * - only an end: a milestone on that date\r\n * - no dates at all: unscheduled (the \"Not scheduled\" tray)\r\n * - an end before the start is swapped, so nothing disappears\r\n */\r\nexport function place(item: TimelineItem): Placed | null {\r\n let start = dayOfIso(item.start);\r\n let end = dayOfIso(item.end);\r\n if (Number.isNaN(start) && Number.isNaN(end)) return null;\r\n if (Number.isNaN(start)) start = end;\r\n if (Number.isNaN(end)) end = start;\r\n if (end < start) [start, end] = [end, start];\r\n const milestone = item.milestone === undefined ? end === start : item.milestone;\r\n return { item, id: item.id, start, end: milestone ? start : end, milestone };\r\n}\r\n\r\n/**\r\n * Pack spans into as few rows as possible without overlaps: sort by start, then longest,\r\n * then id (deterministic), and put each span in the first row that has ended before it starts.\r\n * Ends are inclusive day numbers.\r\n */\r\nexport function packRows(spans: readonly { id: string; start: number; end: number }[]): { row: Map<string, number>; rows: number } {\r\n const sorted = [...spans].sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start) || a.id.localeCompare(b.id));\r\n const ends: number[] = [];\r\n const row = new Map<string, number>();\r\n for (const s of sorted) {\r\n let i = ends.findIndex((e) => e < s.start);\r\n if (i < 0) {\r\n i = ends.length;\r\n ends.push(s.end);\r\n } else ends[i] = s.end;\r\n row.set(s.id, i);\r\n }\r\n return { row, rows: Math.max(1, ends.length) };\r\n}\r\n\r\n/** Approximate label width at 12 px × the text scale (packing reserves room for labels beside short bars). */\r\nexport function labelWidth(text: string, textScale = 1): number {\r\n return Math.ceil(text.length * 6.6 * textScale) + 12;\r\n}\r\n\r\n/** Bars narrower than this put their label beside them. */\r\nexport const LABEL_INSIDE_MIN = 64;\r\n\r\n/** The days an item really takes on screen: the bar, plus its label when the label sits beside it. */\r\nexport function footprint(p: Placed, scale: Pick<RoadmapScale, 'dayWidth'>, textScale = 1): { id: string; start: number; end: number } {\r\n const barPx = p.milestone ? 14 : (p.end - p.start + 1) * scale.dayWidth;\r\n const outside = p.milestone || barPx < LABEL_INSIDE_MIN * textScale;\r\n if (!outside) return { id: p.id, start: p.start, end: p.end };\r\n const px = (p.milestone ? 14 : barPx) + 6 + labelWidth(p.item.name, textScale);\r\n return { id: p.id, start: p.start, end: Math.max(p.end, p.start + Math.ceil(px / scale.dayWidth)) };\r\n}\r\n\r\nexport interface Lane {\r\n key: string;\r\n label: string;\r\n items: Placed[];\r\n /** Packed row per item id. */\r\n row: Map<string, number>;\r\n rows: number;\r\n collapsed: boolean;\r\n top: number;\r\n height: number;\r\n /** Position among lanes (zebra tint, keyboard). */\r\n index: number;\r\n}\r\n\r\n/** A lane's height; rows and the collapsed strip grow with the text scale (8 px grid). */\r\nexport function laneHeight(rows: number, collapsed: boolean, textScale = 1): number {\r\n const rowH = scaleWithText(ROW_H, textScale);\r\n return collapsed ? scaleWithText(36, textScale) : Math.max(scaleWithText(LANE_MIN_H, textScale), LANE_PAD * 2 + rows * rowH);\r\n}\r\n\r\nconst norm = (s: string) => s.trim().toLowerCase();\r\n\r\n/** Does an item match the filter box (name, lane or status)? */\r\nexport function matches(item: TimelineItem, filter: string): boolean {\r\n const q = norm(filter);\r\n if (!q) return true;\r\n return [item.name, item.lane, item.status].some((v) => (v ?? '').toLowerCase().includes(q));\r\n}\r\n\r\n/** Lane order: `laneOrder` first (as given), then the rest alphabetically, \"No lane\" last. */\r\nexport function laneKeys(items: readonly TimelineItem[], laneOrder: readonly string[] = []): string[] {\r\n const present = new Set(items.map((i) => (i.lane ?? '').trim() || NO_LANE));\r\n const ordered = laneOrder.filter((l) => present.has(l));\r\n const rest = [...present].filter((l) => !ordered.includes(l) && l !== NO_LANE).sort((a, b) => a.localeCompare(b));\r\n return [...ordered, ...rest, ...(present.has(NO_LANE) ? [NO_LANE] : [])];\r\n}\r\n\r\n/** Lanes with packed rows and vertical positions, plus the items without dates. */\r\nexport function buildLanes(\r\n items: readonly TimelineItem[],\r\n scale: Pick<RoadmapScale, 'dayWidth'>,\r\n opts: { laneOrder?: readonly string[]; collapsed?: ReadonlySet<string>; filter?: string; textScale?: number } = {}\r\n): { lanes: Lane[]; unscheduled: TimelineItem[] } {\r\n const shown = items.filter((i) => matches(i, opts.filter ?? ''));\r\n const unscheduled: TimelineItem[] = [];\r\n const byLane = new Map<string, Placed[]>();\r\n for (const it of shown) {\r\n const p = place(it);\r\n if (!p) {\r\n unscheduled.push(it);\r\n continue;\r\n }\r\n const key = (it.lane ?? '').trim() || NO_LANE;\r\n const list = byLane.get(key) ?? [];\r\n list.push(p);\r\n byLane.set(key, list);\r\n }\r\n const lanes: Lane[] = [];\r\n let top = 0;\r\n for (const key of laneKeys(shown, opts.laneOrder)) {\r\n const placed = (byLane.get(key) ?? []).sort((a, b) => a.start - b.start || a.id.localeCompare(b.id));\r\n const { row, rows } = packRows(placed.map((p) => footprint(p, scale, opts.textScale)));\r\n const collapsed = opts.collapsed?.has(key) ?? false;\r\n const height = laneHeight(rows, collapsed, opts.textScale);\r\n lanes.push({ key, label: key, items: placed, row, rows, collapsed, top, height, index: lanes.length });\r\n top += height;\r\n }\r\n return { lanes, unscheduled: unscheduled.sort((a, b) => a.name.localeCompare(b.name)) };\r\n}\r\n\r\n/* ---------- Colour ---------- */\r\n\r\n/** Validated palette for values without a colour (distinct in lightness as well as hue). */\r\nexport const PALETTE = ['#2E54FF', '#0E8C7F', '#6B4EFF', '#B54708', '#0B6E99', '#9E1F63'];\r\n\r\nexport type BarStyle = 'solid' | 'outline' | 'done';\r\n\r\n/** Status words → meaning. Matched as whole phrases, case-insensitive. Custom names fall through to the palette. */\r\nconst SEMANTIC: [RegExp, 'positive' | 'warning' | 'negative' | 'done' | 'planned'][] = [\r\n [/^(on track|green|healthy|in progress|active)$/i, 'positive'],\r\n [/^(at risk|amber|yellow|watch)$/i, 'warning'],\r\n [/^(off track|red|blocked|late|delayed)$/i, 'negative'],\r\n [/^(done|complete|completed|shipped|delivered|closed)$/i, 'done'],\r\n [/^(not started|planned|backlog|proposed|idea)$/i, 'planned']\r\n];\r\n/** Brand semantic tokens: Positive, Amber (dark text on it reaches 4.5:1) and Negative. */\r\nconst SEMANTIC_COLOR = { positive: semantic.positive, warning: semantic.milestone, negative: semantic.negative };\r\n/** Done is de-emphasised: a neutral grey, drawn as a light fill with ink text. */\r\nexport const DONE_COLOR = '#8A90A0';\r\n\r\nexport interface Appearance {\r\n /** Fill (or outline) colour. */\r\n color: string;\r\n /** Text on the fill: always 4.5:1. */\r\n text: string;\r\n style: BarStyle;\r\n}\r\n\r\n/**\r\n * Colour for an item. Order: the item's own colour → the host's map for the value (e.g.\r\n * Dataverse choice colours) → the meaning of the status word → a palette colour per value.\r\n * Text on bars is always readable (`accentForText` shades the fill just enough).\r\n */\r\nexport function appearance(\r\n item: TimelineItem,\r\n o: { colorBy: 'status' | 'lane'; accent: string; statusColors?: Readonly<Record<string, string>>; laneColors?: Readonly<Record<string, string>>; seen: Map<string, string> }\r\n): Appearance {\r\n const status = (item.status ?? '').trim();\r\n const meaning = SEMANTIC.find(([re]) => re.test(status))?.[1];\r\n const style: BarStyle = meaning === 'done' ? 'done' : meaning === 'planned' ? 'outline' : 'solid';\r\n const value = o.colorBy === 'lane' ? (item.lane ?? '').trim() || NO_LANE : status;\r\n const map = o.colorBy === 'lane' ? o.laneColors : o.statusColors;\r\n let color = item.color || map?.[value];\r\n if (!color && o.colorBy === 'status' && meaning && meaning !== 'done' && meaning !== 'planned') color = SEMANTIC_COLOR[meaning];\r\n if (!color && o.colorBy === 'status' && meaning === 'done') color = DONE_COLOR;\r\n if (!color && o.colorBy === 'status' && (!status || meaning)) color = o.accent;\r\n if (!color) {\r\n color = o.seen.get(value);\r\n if (!color) {\r\n color = PALETTE[o.seen.size % PALETTE.length]!;\r\n o.seen.set(value, color);\r\n }\r\n }\r\n const pair = accentForText(color);\r\n return { color: pair.background, text: pair.text, style };\r\n}\r\n\r\n/* ---------- Now / Next / Later ---------- */\r\n\r\nexport const HORIZONS = ['Now', 'Next', 'Later'] as const;\r\n\r\n/** Items per horizon column (case-insensitive), plus any without a recognised horizon. */\r\nexport function horizonColumns(items: readonly TimelineItem[]): { key: string; items: TimelineItem[] }[] {\r\n const cols = HORIZONS.map((h) => ({ key: h as string, items: [] as TimelineItem[] }));\r\n const other: TimelineItem[] = [];\r\n for (const it of items) {\r\n const i = HORIZONS.findIndex((h) => h.toLowerCase() === (it.horizon ?? '').trim().toLowerCase());\r\n (i >= 0 ? cols[i]!.items : other).push(it);\r\n }\r\n return other.length ? [...cols, { key: 'No horizon', items: other }] : cols;\r\n}\r\n\r\n/* ---------- Editing ---------- */\r\n\r\n/** One keyboard step at a zoom: a week at Month zoom, a month at Quarter and Half-year. */\r\nexport function stepDay(day: number, zoom: RoadmapZoom, dir: 1 | -1): number {\r\n if (zoom === 'month') return day + dir * 7;\r\n // Same day of the month, clamped to the month's length (31 Jan → 28 Feb).\r\n const p = parts(day);\r\n const first = addMonths(day, dir);\r\n const last = addMonths(first, 1) - 1;\r\n return Math.min(dayOf(parts(first).year, parts(first).month, p.date), last);\r\n}\r\n\r\n/** Day number → `YYYY-MM-DD`. */\r\nexport function isoOfDay(day: number): string {\r\n return new Date(day * 86_400_000).toISOString().slice(0, 10);\r\n}\r\n\r\n/* ---------- Dependency connectors ---------- */\r\n\r\n/**\r\n * The connector path from a predecessor's end (fx, fy) to an item's start (tx, ty). With room\r\n * between them it steps across once; when the item starts too soon after (or before) the\r\n * predecessor ends, it turns back through the gap between rows, so no leg runs through a bar.\r\n */\r\nexport function connectorPath(fx: number, fy: number, tx: number, ty: number, rowH = ROW_H): string {\r\n const GAP = 12;\r\n if (tx - fx >= GAP || fy === ty) {\r\n const mid = fy === ty ? tx - 1 : fx + Math.min(GAP, (tx - fx) / 2);\r\n return fy === ty ? `M${fx},${fy}H${tx - 1}` : `M${fx},${fy}H${mid}V${ty}H${tx - 1}`;\r\n }\r\n const between = ty > fy ? ty - rowH / 2 : ty + rowH / 2;\r\n return `M${fx},${fy}H${fx + 6}V${between}H${tx - 8}V${ty}H${tx - 1}`;\r\n}\r\n\r\n/** The longest axis the Timeline draws: 15 years. */\r\nexport const MAX_SPAN_DAYS = Math.round(15 * 365.25);\r\n\r\n/**\r\n * The days the axis must show: every dated item and today, with a margin. One stray date (a\r\n * typo'd year, Dataverse's 9999-12-31) mustn't stretch it over centuries, so a span over\r\n * 15 years keeps a window around the middle of the items (and today, when it fits).\r\n */\r\nexport function timelineRange(placed: readonly (Pick<Placed, 'start' | 'end'> | null)[], todayDay: number): { from: number; to: number } {\r\n const days: number[] = [];\r\n for (const p of placed) {\r\n if (!p) continue;\r\n if (Number.isFinite(p.start)) days.push(p.start);\r\n if (Number.isFinite(p.end)) days.push(p.end);\r\n }\r\n if (!days.length) return { from: todayDay - 60, to: todayDay + 300 };\r\n days.push(todayDay);\r\n days.sort((a, b) => a - b);\r\n let lo = days[0]!;\r\n let hi = days[days.length - 1]!;\r\n if (hi - lo > MAX_SPAN_DAYS) {\r\n const mid = days[Math.floor(days.length / 2)]!;\r\n lo = Math.max(lo, Math.min(mid - MAX_SPAN_DAYS / 2, hi - MAX_SPAN_DAYS));\r\n hi = lo + MAX_SPAN_DAYS;\r\n }\r\n return { from: lo - 14, to: hi + 30 };\r\n}\r\n","import { MONTHS, parts, type RoadmapZoom } from '@ninthforge/core';\r\nimport type { ChangeSpec } from '@ninthforge/editing';\r\nimport { isoOfDay, stepDay, type Placed } from './layout';\r\nimport type { TimelineChange, TimelineEditable, TimelineFields, TimelineItem } from './types';\r\n\r\n/** Dates are ISO `YYYY-MM-DD`; only the leading date counts when comparing. */\r\nexport const TIMELINE_FIELDS: ChangeSpec<TimelineFields> = {\r\n keys: ['start', 'end'],\r\n equal: (_key, a, b) => (typeof a === 'string' ? a.slice(0, 10) : a ?? '') === (typeof b === 'string' ? b.slice(0, 10) : b ?? '')\r\n};\r\n\r\n/** \"5 Oct 2026\". */\r\nexport function dayLabel(day: number): string {\r\n const p = parts(day);\r\n return `${p.date} ${MONTHS[p.month]} ${p.year}`;\r\n}\r\n\r\n/** \"5 Oct – 26 Mar 2027\" (the year once when both days share it). */\r\nexport function rangeLabel(start: number, end: number): string {\r\n if (start === end) return dayLabel(start);\r\n const a = parts(start);\r\n const b = parts(end);\r\n const left = a.year === b.year ? `${a.date} ${MONTHS[a.month]}` : dayLabel(start);\r\n return `${left} – ${dayLabel(end)}`;\r\n}\r\n\r\n/** The full sentence for screen readers. */\r\nexport function itemWords(p: Placed, extra: { lane: string; status?: string; dependsOn?: string[] }): string {\r\n // A one-day bar reads \"on 20 Nov 2026\" (still not a milestone): QA-2026-11-01-15.\r\n const when = p.milestone ? `milestone on ${dayLabel(p.start)}` : p.start === p.end ? `on ${dayLabel(p.start)}` : `${dayLabel(p.start)} to ${dayLabel(p.end)}`;\r\n const deps = extra.dependsOn?.length ? `depends on ${extra.dependsOn.join(', ')}` : '';\r\n return [p.item.name, extra.lane, when, extra.status ?? '', deps].filter(Boolean).join(', ');\r\n}\r\n\r\nexport type EditAction = 'move' | 'resize';\r\n\r\n/** Whether an item can be moved or resized: needs a handler, the editable setting, and no lock. */\r\nexport function canEdit(editable: TimelineEditable | undefined, hasHandler: boolean, action: EditAction, item?: TimelineItem): boolean {\r\n if (!hasHandler || editable === false || item?.readOnly) return false;\r\n if (editable && typeof editable === 'object' && editable[action] === false) return false;\r\n return true;\r\n}\r\n\r\nexport type DragMode = 'move' | 'start' | 'end';\r\n\r\n/** A span after dragging by `days`: moving keeps the length; resizing never goes past the other end. */\r\nexport function dragSpan(mode: DragMode, start: number, end: number, days: number): { start: number; end: number } {\r\n if (mode === 'move') return { start: start + days, end: end + days };\r\n if (mode === 'start') return { start: Math.min(start + days, end), end };\r\n return { start, end: Math.max(end + days, start) };\r\n}\r\n\r\n/** The keyboard edit for a key press, if any: Alt+←/→ moves, Alt+Shift+←/→ changes the end. */\r\nexport function keyEdit(e: { key: string; altKey: boolean; shiftKey: boolean; ctrlKey?: boolean; metaKey?: boolean }): { mode: DragMode; dir: 1 | -1 } | null {\r\n if (!e.altKey || e.ctrlKey || e.metaKey) return null;\r\n if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return null;\r\n return { mode: e.shiftKey ? 'end' : 'move', dir: e.key === 'ArrowRight' ? 1 : -1 };\r\n}\r\n\r\n/** A keyboard step at the zoom: whole weeks at Month zoom, months at Quarter and Half-year. */\r\nexport function keySpan(mode: DragMode, start: number, end: number, zoom: RoadmapZoom, dir: 1 | -1): { start: number; end: number } {\r\n if (mode === 'end') return { start, end: Math.max(stepDay(end, zoom, dir), start) };\r\n const s = stepDay(start, zoom, dir);\r\n return { start: s, end: s + (end - start) };\r\n}\r\n\r\n/**\r\n * The change that puts `p` at `span`, or null when nothing moves. A milestone keeps an end\r\n * equal to its start when it had one (so it stays a milestone); a missing end stays missing.\r\n */\r\nexport function spanChange(p: Placed, span: { start: number; end: number }, source: TimelineChange['source']): TimelineChange | null {\r\n const item = p.item;\r\n const next: Partial<TimelineFields> = {};\r\n const previous: Partial<TimelineFields> = {};\r\n if (span.start !== p.start) {\r\n next.start = isoOfDay(span.start);\r\n previous.start = item.start;\r\n }\r\n const wantEnd = p.milestone ? (item.end ? span.start : undefined) : span.end;\r\n if (wantEnd !== undefined && wantEnd !== p.end) {\r\n next.end = isoOfDay(wantEnd);\r\n previous.end = item.end;\r\n }\r\n if (next.start === undefined && next.end === undefined) return null;\r\n return { id: item.id, ...next, previous, source } as TimelineChange;\r\n}\r\n\r\n/** Toast and announcement text: \"'Portal' moved to 5 Oct – 26 Mar 2027\". */\r\nexport function describeChange(change: TimelineChange, name: string, before: Placed | undefined): string {\r\n const start = change.start ?? (before ? isoOfDay(before.start) : undefined);\r\n const end = change.end ?? (before ? isoOfDay(before.end) : undefined);\r\n const s = start ? Date.parse(`${start.slice(0, 10)}T00:00:00Z`) / 86_400_000 : NaN;\r\n const e = end ? Date.parse(`${end.slice(0, 10)}T00:00:00Z`) / 86_400_000 : s;\r\n // \"Undone:\" / \"Redone:\" like the Gantt and Scheduler, so an undo never sounds like a new edit.\r\n const prefix = change.source === 'undo' ? 'Undone: ' : change.source === 'redo' ? 'Redone: ' : '';\r\n if (Number.isNaN(s)) return `${prefix}'${name}' changed`;\r\n if (before?.milestone) return `${prefix}'${name}' moved to ${dayLabel(s)}`;\r\n const verb = change.start !== undefined ? 'moved' : 'now ends';\r\n return prefix + (verb === 'moved' ? `'${name}' moved to ${rangeLabel(s, e)}` : `'${name}' now ends ${dayLabel(e)}`);\r\n}\r\n","import { HOST_DIM } from '@ninthforge/core';\r\nimport { brand, motion } from '@ninthforge/tokens';\r\nimport { LANE_HEAD_W, ROW_H } from './layout';\r\n\r\nexport const HEAD_H = 52;\r\n/** The month row under the quarters (Quarter zoom). */\r\nexport const MONTH_ROW_H = 20;\r\n\r\n/** Scoped styles. Every colour comes from a --nf-* variable so customers can theme it. */\r\nexport const css = `\r\n.nf-tl{--nf-lanehead:${LANE_HEAD_W}px;--nf-row:${ROW_H}px;--nf-now:${brand.ember};position:relative;isolation:isolate;display:flex;flex-direction:column;width:100%;box-sizing:border-box;min-height:0;height:100%;box-sizing:border-box;background:var(--nf-surface);color:var(--nf-ink);font-family:var(--nf-font);font-size:var(--nf-text-body);line-height:1.3;border:1px solid var(--nf-line);border-radius:16px;overflow:hidden;-webkit-font-smoothing:antialiased}\r\n.nf-tl *,.nf-tl *::before,.nf-tl *::after{box-sizing:border-box}\r\n.nf-t-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--nf-line)}\r\n.nf-t-title{font-weight:600;font-size:var(--nf-text-title);letter-spacing:-0.01em;margin-inline-end:6px}\r\n.nf-t-seg{display:inline-flex;gap:2px;padding:3px;border-radius:9px;background:var(--nf-line-soft);max-width:100%;overflow-x:auto}\r\n.nf-t-btn{font:inherit;font-size:var(--nf-text-body);border:1px solid var(--nf-line);background:var(--nf-surface);color:var(--nf-ink);padding:7px 12px;border-radius:9px;cursor:pointer;min-height:32px;white-space:nowrap;transition:background ${motion.fast} ${motion.ease}}\r\n.nf-t-btn:hover{background:var(--nf-line-soft)}\r\n.nf-t-seg .nf-t-btn{flex:none;border:0;background:transparent;color:var(--nf-muted);padding:6px 12px;border-radius:7px}\r\n.nf-t-seg .nf-t-btn[aria-pressed=\"true\"]{background:var(--nf-surface);color:var(--nf-ink);font-weight:500;box-shadow:0 1px 2px rgba(14,17,22,.12)}\r\n.nf-t-spacer{flex:1}\r\n.nf-t-filter{font:inherit;font-size:var(--nf-text-body);min-height:32px;width:calc(var(--nf-text-body) * 170 / 13);max-width:40vw;padding:6px 10px;border-radius:9px;border:1px solid var(--nf-line);background:var(--nf-surface);color:var(--nf-ink)}\r\n.nf-t-count{display:inline-block;min-width:20px;padding:1px 6px;margin-inline-start:6px;border-radius:9px;background:var(--nf-accent-solid,var(--nf-accent));color:var(--nf-accent-text);font-size:var(--nf-text-caption);font-weight:600;text-align:center;direction:ltr;unicode-bidi:isolate}\r\n.nf-t-btn:focus-visible,.nf-t-filter:focus-visible,.nf-t-scroll:focus-visible,.nf-t-lanebtn:focus-visible,.nf-t-card:focus-visible,.nf-t-trayitem:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-t-main{position:relative;flex:1;min-height:0;display:flex}\r\n.nf-t-scroll{position:relative;flex:1;min-width:0;overflow:auto;outline:none}\r\n.nf-t-head{position:sticky;top:0;z-index:4;display:flex;height:var(--nf-head,${HEAD_H}px);background:var(--nf-surface);border-bottom:1px solid var(--nf-line)}\r\n.nf-t-corner{position:sticky;left:0;z-index:5;flex:none;width:var(--nf-lanehead);display:flex;align-items:flex-end;padding:0 16px 8px;background:var(--nf-surface);border-right:1px solid var(--nf-line);font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);letter-spacing:.06em;color:var(--nf-muted);text-transform:uppercase}\r\n.nf-t-axis{position:relative;flex:none}\r\n.nf-t-major,.nf-t-minor{position:absolute;padding:0 8px;white-space:nowrap;overflow:hidden;border-left:1px solid var(--nf-line)}\r\n.nf-t-major{top:0;height:calc(var(--nf-text-label) * 2);padding-top:6px;font-size:var(--nf-text-label);font-weight:600}\r\n.nf-t-minor{top:calc(var(--nf-text-label) * 26 / 12);height:calc(var(--nf-text-label) * 26 / 12);padding-top:6px;font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);color:var(--nf-muted)}\r\n.nf-tl[data-months] .nf-t-minor{font-family:var(--nf-font);font-size:var(--nf-text-label);font-weight:600;color:var(--nf-ink)}\r\n.nf-t-month{position:absolute;top:var(--nf-head,52px);height:calc(var(--nf-text-caption) * 20 / 11);padding:3px 0 0 8px;white-space:nowrap;overflow:hidden;border-left:1px solid var(--nf-line-soft);font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);color:var(--nf-muted)}\r\n.nf-t-body{position:relative}\r\n.nf-t-layer{position:absolute;top:0;bottom:0;pointer-events:none}\r\n.nf-t-vline{position:absolute;top:0;bottom:0;width:1px;background:var(--nf-line-soft)}\r\n.nf-t-vline[data-major]{background:var(--nf-line)}\r\n.nf-t-today{position:absolute;top:0;bottom:0;width:2px;margin-left:-1px;background:var(--nf-now);z-index:2}\r\n.nf-t-lane{position:absolute;left:0;display:flex}\r\n.nf-t-lane[data-alt]{background:var(--nf-surface-alt)}\r\n.nf-t-lanehead{position:sticky;left:0;z-index:3;flex:none;width:var(--nf-lanehead);display:flex;align-items:flex-start;padding:10px 12px 0 8px;background:inherit;background-color:var(--nf-surface);border-right:1px solid var(--nf-line);border-bottom:1px solid var(--nf-line-soft)}\r\n.nf-t-lane[data-alt] .nf-t-lanehead{background-color:var(--nf-surface-alt)}\r\n.nf-t-lanebtn{display:inline-flex;align-items:center;gap:6px;max-width:100%;font:inherit;font-size:var(--nf-text-header);font-weight:600;color:var(--nf-ink);background:none;border:0;padding:4px;border-radius:6px;cursor:pointer;text-align:start}\r\n.nf-t-lanebtn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\r\n.nf-t-lanebtn svg{flex:none;transition:transform ${motion.fast} ${motion.ease}}\r\n.nf-t-lanebtn[aria-expanded=\"false\"] svg{transform:rotate(-90deg)}\r\n.nf-tl[data-rtl] .nf-t-lanebtn[aria-expanded=\"false\"] svg{transform:rotate(90deg)}\r\n.nf-t-lanecount{font-weight:400;color:var(--nf-muted)}\r\n.nf-t-track{position:relative;flex:none;border-bottom:1px solid var(--nf-line-soft)}\r\n.nf-t-bar{position:absolute;z-index:1;height:calc(var(--nf-row) - 8px);border-radius:6px;padding:0 8px;display:flex;align-items:center;font-size:var(--nf-text-value);font-weight:500;white-space:nowrap;overflow:hidden;cursor:pointer;background:var(--bar);color:var(--bar-text);border:1px solid var(--bar);user-select:none;touch-action:none}\r\n.nf-t-barname{min-width:0;overflow:hidden;text-overflow:ellipsis}\r\n.nf-t-bar[data-style=\"outline\"]{background:var(--nf-surface);color:var(--nf-ink);border:1.5px dashed var(--bar)}\r\n.nf-t-bar[data-style=\"done\"]{background:color-mix(in srgb,var(--bar) 22%,var(--nf-surface));color:var(--nf-ink);border-color:color-mix(in srgb,var(--bar) 45%,var(--nf-surface))}\r\n.nf-t-bar[data-edit]{cursor:grab}\r\n.nf-tl.nf-t-dragging .nf-t-bar[data-edit]{cursor:grabbing}\r\n.nf-t-bar[aria-selected=\"true\"]{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-focus)}\r\n.nf-t-bar[data-preview]{opacity:.9;z-index:2}\r\n.nf-t-h{position:absolute;top:0;bottom:0;width:8px;cursor:ew-resize}\r\n.nf-t-h[data-edge=\"start\"]{left:-1px}\r\n.nf-t-h[data-edge=\"end\"]{right:-1px}\r\n.nf-t-ms{position:absolute;z-index:1;width:14px;height:14px;margin-top:2px;transform:rotate(45deg);border-radius:3px;background:var(--bar);border:1px solid var(--bar);cursor:pointer}\r\n.nf-t-ms[data-style=\"outline\"]{background:var(--nf-surface);border:2px solid var(--bar)}\r\n.nf-t-ms[aria-selected=\"true\"]{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-focus)}\r\n.nf-t-label{position:absolute;z-index:1;font-size:var(--nf-text-value);font-weight:500;white-space:nowrap;color:var(--nf-ink);pointer-events:none;background:var(--nf-surface);padding:0 3px;margin-left:-3px;border-radius:3px}\r\n/* Connectors run under bars, diamonds and labels. */\r\n.nf-t-depwrap{position:absolute;top:0;z-index:0;pointer-events:none}\r\n.nf-t-deps{position:absolute;top:0;left:0;pointer-events:none;overflow:visible}\r\n.nf-t-deps path{fill:none;stroke:var(--nf-muted);stroke-width:1.25}\r\n.nf-t-draglabel{position:absolute;z-index:6;padding:4px 8px;border-radius:6px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);font-size:var(--nf-text-value);white-space:nowrap;pointer-events:none;direction:ltr}\r\n.nf-t-empty{position:absolute;left:var(--nf-lanehead);right:0;top:40px;text-align:center;color:var(--nf-muted);font-size:var(--nf-text-body)}.nf-t-emptyaction{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:12px;color:var(--nf-muted);font-size:var(--nf-text-body)}.nf-t-emptyaction small{font-size:var(--nf-text-callout);max-width:360px}.nf-t-emptyaction [role=\"alert\"]{color:var(--nf-negative,#D93F2B)}\r\n.nf-t-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\r\n.nf-t-legend{display:flex;flex-wrap:wrap;gap:12px;padding:8px 16px;border-top:1px solid var(--nf-line);font-size:var(--nf-text-callout);color:var(--nf-muted)}\r\n.nf-t-legend span{display:inline-flex;align-items:center;gap:6px}\r\n.nf-t-legend i{display:inline-block;width:14px;height:10px;border-radius:3px;background:var(--sw);border:1px solid var(--sw)}\r\n.nf-t-legend i[data-style=\"outline\"]{background:transparent;border-style:dashed}\r\n.nf-t-legend i[data-style=\"done\"]{background:color-mix(in srgb,var(--sw) 22%,var(--nf-surface))}\r\n.nf-t-tray{flex:none;width:260px;display:flex;flex-direction:column;border-left:1px solid var(--nf-line);background:var(--nf-surface)}\r\n.nf-tl[data-narrow] .nf-t-tray{position:absolute;inset:0;z-index:6;width:auto;border-left:0}\r\n.nf-t-trayhead{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--nf-line);font-weight:600}\r\n.nf-t-traylist{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:6px;margin:0;list-style:none}\r\n.nf-t-trayitem{display:block;width:100%;text-align:start;font:inherit;font-size:var(--nf-text-body);padding:8px 10px;border-radius:9px;border:1px solid var(--nf-line);border-inline-start:3px solid var(--bar);background:var(--nf-surface);color:var(--nf-ink);cursor:pointer}\r\n.nf-t-trayitem small{display:block;margin-top:2px;font-size:var(--nf-text-callout);color:var(--nf-muted)}\r\n.nf-t-trayitem[aria-pressed=\"true\"]{border-color:var(--nf-focus)}\r\n.nf-t-nnl{flex:1;min-width:0;overflow:auto}\r\n.nf-t-nnlgrid{display:grid;min-width:640px}\r\n.nf-t-nnlhead{position:sticky;top:0;z-index:2;padding:12px 12px 8px;background:var(--nf-surface);border-bottom:1px solid var(--nf-line);font-weight:600;font-size:var(--nf-text-header)}\r\n.nf-t-nnlhead .nf-t-lanecount{margin-inline-start:6px}\r\n.nf-t-nnllane{padding:10px 12px;font-size:var(--nf-text-header);font-weight:600;border-bottom:1px solid var(--nf-line-soft);background:var(--nf-surface)}\r\n.nf-t-nnlcell{padding:8px;display:flex;flex-direction:column;gap:6px;border-bottom:1px solid var(--nf-line-soft);border-inline-start:1px solid var(--nf-line-soft)}\r\n.nf-t-nnlgrid>[data-alt]{background:var(--nf-surface-alt)}\r\n.nf-t-card{display:block;width:100%;text-align:start;font:inherit;font-size:var(--nf-text-body);padding:8px 10px;border-radius:8px;border:1px solid var(--nf-line);border-inline-start:4px solid var(--bar);background:var(--nf-surface);color:var(--nf-ink);cursor:pointer}\r\n.nf-t-card small{display:block;margin-top:2px;font-size:var(--nf-text-callout);color:var(--nf-muted)}\r\n.nf-t-card[aria-pressed=\"true\"]{border-color:var(--nf-focus)}\r\n.nf-t-menuwrap{position:relative;display:inline-flex}\r\n.nf-t-menu{position:absolute;top:calc(100% + 6px);right:0;z-index:9;min-width:180px;display:flex;flex-direction:column;padding:4px;border-radius:10px;border:1px solid var(--nf-line);background:var(--nf-surface);box-shadow:0 8px 24px rgba(14,17,22,.14)}\r\n.nf-t-menu [role=\"menuitem\"]{font:inherit;font-size:var(--nf-text-body);text-align:start;color:var(--nf-ink);background:none;border:0;border-radius:7px;padding:8px 10px;cursor:pointer;unicode-bidi:plaintext}\r\n.nf-t-menu [role=\"menuitem\"]:hover,.nf-t-menu [role=\"menuitem\"]:focus-visible{background:var(--nf-line-soft);outline:none}\r\n.nf-t-menu [role=\"menuitem\"]:focus-visible{box-shadow:inset 0 0 0 2px var(--nf-focus)}\r\n.nf-t-mark{position:absolute;right:12px;bottom:12px;z-index:7;display:inline-flex;align-items:center;gap:6px;padding:5px 9px 5px 6px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);box-shadow:0 2px 8px rgba(14,17,22,.08);font-size:var(--nf-text-caption);color:var(--nf-muted);text-decoration:none;font-family:inherit;cursor:pointer}\r\n.nf-t-mark:hover{color:var(--nf-ink)}\r\n.nf-t-locknote{position:absolute;left:12px;bottom:12px;z-index:7;margin:0;max-width:calc(100% - 220px);padding:5px 9px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);font-size:var(--nf-text-caption);color:var(--nf-muted)}\r\n.nf-t-renew{position:absolute;right:12px;bottom:12px;z-index:7;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);box-shadow:0 2px 8px rgba(14,17,22,.08);font-size:var(--nf-text-caption);color:var(--nf-muted);text-decoration:none}\r\n.nf-t-renew:hover{color:var(--nf-ink)}\r\n.nf-t-renew:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-t-renew::before{content:\"\";width:6px;height:6px;border-radius:3px;background:var(--nf-milestone)}\r\n.nf-t-mark:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-t-toasts{position:absolute;left:50%;bottom:16px;z-index:8;transform:translateX(-50%);display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none}\r\n.nf-t-toast{pointer-events:auto;display:flex;align-items:center;gap:10px;padding:8px 8px 8px 12px;border-radius:10px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);box-shadow:0 8px 24px rgba(14,17,22,.24);font-size:var(--nf-text-body);animation:nf-t-in ${motion.fast} ${motion.ease} both}\r\n.nf-t-toast[data-tone=\"error\"]{background:var(--nf-negative);color:#fff}\r\n.nf-t-toast-btn{font:inherit;font-size:var(--nf-text-body);font-weight:600;color:inherit;background:rgba(255,255,255,.14);border:0;padding:5px 10px;border-radius:7px;cursor:pointer}\r\n.nf-t-toast-x{font:inherit;font-size:var(--nf-text-title);line-height:1;color:inherit;opacity:.7;background:none;border:0;padding:4px 6px;border-radius:6px;cursor:pointer}\r\n.nf-t-toast button:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-tl.nf-t-settled .nf-t-bar,.nf-tl.nf-t-settled .nf-t-ms{animation:nf-t-in ${motion.base} ${motion.ease} both}\r\n@keyframes nf-t-in{from{opacity:0;transform:scaleX(.96)}to{opacity:1;transform:none}}\r\n@media (prefers-reduced-motion: reduce){.nf-tl *{animation:none!important;transition:none!important}}\r\n.nf-tl[data-static] *{animation:none!important;transition:none!important}\r\n.nf-t-bar[data-dim],.nf-t-ms[data-dim],.nf-t-label[data-dim]{opacity:${HOST_DIM}}\r\n@media (forced-colors: active){\r\n.nf-tl{border-color:CanvasText}\r\n.nf-t-bar,.nf-t-ms,.nf-t-today,.nf-t-count,.nf-t-legend i{forced-color-adjust:none}\r\n.nf-t-bar{background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-t-bar[data-style=\"outline\"]{border-style:dashed}\r\n.nf-t-bar[data-style=\"done\"]{border-style:dotted}\r\n.nf-t-ms{background:CanvasText;border-color:CanvasText}\r\n.nf-t-bar[aria-selected=\"true\"],.nf-t-ms[aria-selected=\"true\"]{box-shadow:none;outline:2px solid Highlight;outline-offset:2px}\r\n.nf-t-today{background:Highlight}\r\n.nf-t-count{background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-t-legend i{background:Canvas;border-color:CanvasText}\r\n.nf-t-seg .nf-t-btn[aria-pressed=\"true\"]{outline:2px solid Highlight;outline-offset:-2px}\r\n.nf-t-card,.nf-t-trayitem,.nf-t-menu{border-color:CanvasText}\r\n.nf-t-card[aria-pressed=\"true\"],.nf-t-trayitem[aria-pressed=\"true\"]{outline:2px solid Highlight}\r\n.nf-t-toast{forced-color-adjust:none;background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-tl :focus-visible{outline:2px solid Highlight}\r\n}\r\n`;\r\n\r\n","import { canvasColor, downloadCanvas, escapeHtml, exportRatio, type RoadmapScale } from '@ninthforge/core';\nimport { brand, type ComponentTheme } from '@ninthforge/tokens';\nimport { dayLabel, rangeLabel } from './edit';\nimport { footprint, horizonColumns, labelWidth, LABEL_INSIDE_MIN, LANE_HEAD_W, LANE_PAD, NO_LANE, packRows, place, ROW_H, type Appearance, type Lane, type Placed } from './layout';\nimport { HEAD_H } from './styles';\nimport type { TimelineItem, TimelineMode } from './types';\n\n/* Print and PNG for the Timeline: no DOM screenshots, no dependencies, no network. Print is a\n self-contained HTML document printed from an about:blank iframe; PNG is a canvas redraw. */\n\nexport interface TimelineExport {\n title: string;\n lanes: Lane[];\n unscheduled: TimelineItem[];\n /** The screen's scale: print refits it to the page, PNG uses it as is. */\n scale: RoadmapScale;\n looks: ReadonlyMap<string, Appearance>;\n theme: ComponentTheme;\n today: number;\n /** \"26 Oct 2026\". */\n printedOn: string;\n showDependencies: boolean;\n mode: TimelineMode;\n fiscalYearStart?: number;\n}\n\n/** A landscape page leaves about 1,040 px: 180 for lane names, the rest for time. */\nexport const PRINT_TIMELINE_W = 840;\nconst PRINT_LANE_W = 180;\nconst PRINT_ROW = 22;\n\nconst e = escapeHtml;\n\ninterface Fitted {\n x: (day: number) => number;\n dayWidth: number;\n width: number;\n major: { x: number; width: number; label: string }[];\n minor: { x: number; width: number; label: string }[];\n startDay: number;\n endDay: number;\n}\n\n/** The screen scale squeezed (or stretched) to `width` px. */\nexport function fitScale(s: RoadmapScale, width: number): Fitted {\n const k = width / s.width;\n return {\n x: (day) => Math.round(s.x(day) * k * 10) / 10,\n dayWidth: s.dayWidth * k,\n width,\n major: s.major.map((t) => ({ x: t.x * k, width: t.width * k, label: t.label })),\n minor: s.minor.map((t) => ({ x: t.x * k, width: t.width * k, label: t.label })),\n startDay: s.startDay,\n endDay: s.endDay\n };\n}\n\nconst fallback = (t: ComponentTheme): Appearance => ({ color: t.accent, text: t.accentText, style: 'solid' });\n\nfunction laneSvg(lane: Lane, f: Fitted, o: TimelineExport): string {\n const t = o.theme;\n const { row, rows } = packRows(lane.items.map((p) => footprint(p, f)));\n const h = Math.max(28, rows * PRINT_ROW + 8);\n const out: string[] = [];\n for (const m of f.minor) out.push(`<line x1=\"${m.x}\" x2=\"${m.x}\" y1=\"0\" y2=\"${h}\" stroke=\"${t.lineSoft}\"/>`);\n if (o.today >= f.startDay && o.today < f.endDay) {\n const tx = f.x(o.today) + f.dayWidth / 2;\n out.push(`<line x1=\"${tx}\" x2=\"${tx}\" y1=\"0\" y2=\"${h}\" stroke=\"${brand.ember}\" stroke-width=\"1.5\"/>`);\n }\n for (const p of lane.items) {\n const look = o.looks.get(p.id) ?? fallback(t);\n const y = 4 + (row.get(p.id) ?? 0) * PRINT_ROW;\n if (p.milestone) {\n const cx = f.x(p.start) + f.dayWidth / 2;\n out.push(`<rect x=\"${cx - 5}\" y=\"${y + 4}\" width=\"10\" height=\"10\" rx=\"2\" fill=\"${look.style === 'outline' ? '#fff' : look.color}\" stroke=\"${look.color}\" stroke-width=\"1.5\" transform=\"rotate(45 ${cx} ${y + 9})\"/>`);\n out.push(`<text x=\"${cx + 9}\" y=\"${y + 12.5}\" fill=\"${t.ink}\">${e(p.item.name)}</text>`);\n continue;\n }\n const x0 = f.x(p.start);\n const w = Math.max(3, f.x(p.end + 1) - x0);\n const attrs =\n look.style === 'outline'\n ? `fill=\"#fff\" stroke=\"${look.color}\" stroke-dasharray=\"3 2\"`\n : look.style === 'done'\n ? `fill=\"${look.color}\" fill-opacity=\".25\" stroke=\"${look.color}\" stroke-opacity=\".5\"`\n : `fill=\"${look.color}\"`;\n out.push(`<rect x=\"${x0}\" y=\"${y}\" width=\"${w}\" height=\"17\" rx=\"4\" ${attrs}/>`);\n const inside = w >= LABEL_INSIDE_MIN;\n const ink = look.style === 'solid' ? look.text : t.ink;\n const maxChars = Math.floor((w - 8) / 5.4);\n const name = inside ? (p.item.name.length <= maxChars ? p.item.name : `${p.item.name.slice(0, Math.max(1, maxChars - 1))}…`) : p.item.name;\n out.push(`<text x=\"${inside ? x0 + 5 : x0 + w + 5}\" y=\"${y + 12}\" fill=\"${inside ? ink : t.ink}\">${e(name)}</text>`);\n }\n return `<svg width=\"${f.width}\" height=\"${h}\" viewBox=\"0 0 ${f.width} ${h}\" aria-hidden=\"true\">${out.join('')}</svg>`;\n}\n\nfunction legendHtml(o: TimelineExport): string {\n const seen = new Map<string, Appearance>();\n for (const l of o.lanes) for (const p of l.items) {\n const key = (p.item.status ?? '').trim();\n if (key && !seen.has(key)) seen.set(key, o.looks.get(p.id) ?? fallback(o.theme));\n }\n const sw = (a: Appearance) =>\n a.style === 'outline' ? `border:1.5px dashed ${a.color};background:#fff` : a.style === 'done' ? `background:${a.color};opacity:.35` : `background:${a.color}`;\n return [\n ...[...seen.entries()].map(([k, a]) => `<span><i style=\"${sw(a)}\"></i>${e(k)}</span>`),\n `<span><i style=\"background:${o.theme.ink};width:8px;height:8px;transform:rotate(45deg)\"></i>Milestone</span>`,\n `<span><i style=\"background:${brand.ember};width:2px;height:11px\"></i>Today</span>`\n ].join('');\n}\n\nfunction shell(title: string, meta: string, legend: string, t: ComponentTheme, body: string): string {\n return `<!doctype html><html><head><meta charset=\"utf-8\"><title>${e(title)}</title><style>\n@page{size:landscape;margin:10mm}\n*{box-sizing:border-box}\nhtml,body{margin:0;background:#fff;color:${t.ink};font-family:Geist,system-ui,-apple-system,\"Segoe UI\",sans-serif;font-size:10px;-webkit-print-color-adjust:exact;print-color-adjust:exact}\nheader{display:flex;align-items:baseline;flex-wrap:wrap;gap:12px;margin-bottom:8px}\nh1{font-size:16px;margin:0;letter-spacing:-0.01em}\nh2{font-size:12px;margin:14px 0 6px}\n.meta{color:${t.muted}}\n.legend{display:flex;flex-wrap:wrap;gap:12px;margin-left:auto;color:${t.muted}}\n.legend span{display:inline-flex;align-items:center;gap:5px}\n.legend i{display:inline-block;width:16px;height:9px;border-radius:2px}\ntable{width:100%;border-collapse:collapse;table-layout:fixed}\nthead{display:table-header-group}\ntr{page-break-inside:avoid;break-inside:avoid}\nth{font-weight:600;text-align:left;padding:0 6px 4px;border-bottom:1px solid ${t.line};vertical-align:bottom}\nth.tl{padding:0}\ntd{padding:4px 6px;border-bottom:1px solid ${t.lineSoft};vertical-align:top}\ntd.tl{padding:0}\ntr.alt td{background:${t.surfaceAlt}}\ntd.lane{font-weight:600}\nsvg text{font-family:inherit;font-size:9px}\n.muted{color:${t.muted}}\n.card{border:1px solid ${t.line};border-left:3px solid var(--c);border-radius:4px;padding:3px 6px;margin-bottom:4px}\n.card small{display:block;color:${t.muted}}\n</style></head><body>\n<header><h1>${e(title)}</h1><span class=\"meta\">${meta}</span><div class=\"legend\">${legend}</div></header>\n${body}\n</body></html>`;\n}\n\nfunction trayTable(o: TimelineExport): string {\n if (!o.unscheduled.length) return '';\n return `<h2>Not scheduled (${o.unscheduled.length})</h2><table><colgroup><col><col style=\"width:160px\"><col style=\"width:120px\"><col style=\"width:90px\"></colgroup><thead><tr><th>Item</th><th>Lane</th><th>Status</th><th>Horizon</th></tr></thead><tbody>${o.unscheduled\n .map((i) => `<tr><td>${e(i.name)}</td><td>${e(i.lane || NO_LANE)}</td><td class=\"muted\">${e(i.status ?? '')}</td><td class=\"muted\">${e(i.horizon ?? '')}</td></tr>`)\n .join('')}</tbody></table>`;\n}\n\n/** The print document. Pure: same inputs, same HTML. */\nexport function timelinePrintHtml(o: TimelineExport): string {\n const t = o.theme;\n const count = o.lanes.reduce((n, l) => n + l.items.length, 0) + o.unscheduled.length;\n if (o.mode === 'horizon') {\n const all = [...o.lanes.flatMap((l) => l.items.map((p) => p.item)), ...o.unscheduled];\n const cols = horizonColumns(all);\n const lanes = [...new Set(all.map((i) => (i.lane ?? '').trim() || NO_LANE))];\n const body = `<table><thead><tr><th style=\"width:150px\">Lane</th>${cols.map((c) => `<th>${e(c.key)} <span class=\"muted\">${c.items.length}</span></th>`).join('')}</tr></thead><tbody>${lanes\n .map(\n (lane, i) =>\n `<tr${i % 2 ? ' class=\"alt\"' : ''}><td class=\"lane\">${e(lane)}</td>${cols\n .map(\n (c) =>\n `<td>${c.items\n .filter((it) => ((it.lane ?? '').trim() || NO_LANE) === lane)\n .map((it) => {\n const p = place(it);\n const when = p ? (p.milestone ? dayLabel(p.start) : rangeLabel(p.start, p.end)) : 'Not scheduled';\n return `<div class=\"card\" style=\"--c:${(o.looks.get(it.id) ?? fallback(t)).color}\">${e(it.name)}<small>${e([it.status, when].filter(Boolean).join(' · '))}</small></div>`;\n })\n .join('')}</td>`\n )\n .join('')}</tr>`\n )\n .join('')}</tbody></table>`;\n return shell(o.title, `Now, next and later · ${count} items · Printed on ${e(o.printedOn)}`, legendHtml(o), t, body);\n }\n const f = fitScale(o.scale, PRINT_TIMELINE_W);\n const head = `<svg width=\"${f.width}\" height=\"34\" viewBox=\"0 0 ${f.width} 34\" aria-hidden=\"true\">${f.major\n .map((m) => `<line x1=\"${m.x}\" x2=\"${m.x}\" y1=\"0\" y2=\"34\" stroke=\"${t.line}\"/><text x=\"${m.x + 4}\" y=\"11\" font-weight=\"600\" fill=\"${t.ink}\">${e(m.label)}</text>`)\n .join('')}${f.minor.map((m) => `<line x1=\"${m.x}\" x2=\"${m.x}\" y1=\"16\" y2=\"34\" stroke=\"${t.lineSoft}\"/><text x=\"${m.x + 4}\" y=\"28\" fill=\"${t.muted}\">${e(m.label)}</text>`).join('')}</svg>`;\n const rows = o.lanes\n .map((l, i) => `<tr${i % 2 ? ' class=\"alt\"' : ''}><td class=\"lane\">${e(l.label)} <span class=\"muted\">${l.items.length}</span></td><td class=\"tl\">${laneSvg(l, f, o)}</td></tr>`)\n .join('');\n const range = `${o.scale.minor[0]?.label ?? ''} – ${o.scale.minor[o.scale.minor.length - 1]?.label ?? ''}`;\n const body = `<table><colgroup><col style=\"width:${PRINT_LANE_W}px\"><col style=\"width:${f.width}px\"></colgroup><thead><tr><th>Lane</th><th class=\"tl\">${head}</th></tr></thead><tbody>${rows}</tbody></table>${trayTable(o)}`;\n return shell(o.title, `${e(range)} · ${count} items · Printed on ${e(o.printedOn)}`, legendHtml(o), t, body);\n}\n\n/* ---------- PNG ---------- */\n\nconst TITLE_H = 48;\nconst LEGEND_H = 36;\nconst FONT = 'Geist, system-ui, -apple-system, \"Segoe UI\", sans-serif';\n\nexport function timelinePngSize(o: Pick<TimelineExport, 'lanes' | 'scale'>): { width: number; height: number; ratio: number } | null {\n const body = o.lanes.reduce((h, l) => h + l.height, 0) || 60;\n const width = Math.ceil(LANE_HEAD_W + o.scale.width);\n const height = Math.ceil(TITLE_H + HEAD_H + body + LEGEND_H);\n const ratio = exportRatio(width, height);\n return ratio === null ? null : { width, height, ratio };\n}\n\nfunction roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + rr, y);\n ctx.arcTo(x + w, y, x + w, y + h, rr);\n ctx.arcTo(x + w, y + h, x, y + h, rr);\n ctx.arcTo(x, y + h, x, y, rr);\n ctx.arcTo(x, y, x + w, y, rr);\n ctx.closePath();\n}\n\nexport function drawTimeline(ctx: CanvasRenderingContext2D, size: { width: number; height: number; ratio: number }, o: TimelineExport): void {\n const c = (v: string | undefined, fb: string) => canvasColor(ctx, v, fb);\n const T = { surface: c(o.theme.surface, '#fff'), alt: c(o.theme.surfaceAlt, '#f6f6f3'), ink: c(o.theme.ink, '#111'), muted: c(o.theme.muted, '#666'), line: c(o.theme.line, '#ddd'), soft: c(o.theme.lineSoft, '#eee') };\n const s = o.scale;\n const X0 = LANE_HEAD_W;\n const Y0 = TITLE_H + HEAD_H;\n ctx.save();\n ctx.scale(size.ratio, size.ratio);\n ctx.fillStyle = T.surface;\n ctx.fillRect(0, 0, size.width, size.height);\n ctx.textBaseline = 'middle';\n ctx.fillStyle = T.ink;\n ctx.font = `600 16px ${FONT}`;\n ctx.fillText(o.title, 16, 24);\n // Header: years (or fiscal years) over months, quarters or halves.\n for (const m of s.major) {\n ctx.fillStyle = T.line;\n ctx.fillRect(X0 + m.x, TITLE_H, 1, size.height - TITLE_H - LEGEND_H);\n ctx.fillStyle = T.ink;\n ctx.font = `600 12px ${FONT}`;\n ctx.fillText(m.label, X0 + m.x + 8, TITLE_H + 13);\n }\n for (const m of s.minor) {\n ctx.fillStyle = T.soft;\n ctx.fillRect(X0 + m.x, TITLE_H + 26, 1, size.height - TITLE_H - 26 - LEGEND_H);\n ctx.fillStyle = T.muted;\n ctx.font = `400 11px ${FONT}`;\n ctx.fillText(m.label, X0 + m.x + 8, TITLE_H + 39);\n }\n ctx.fillStyle = T.line;\n ctx.fillRect(0, Y0 - 1, size.width, 1);\n // Lanes.\n for (const l of o.lanes) {\n const y = Y0 + l.top;\n if (l.index % 2) {\n ctx.fillStyle = T.alt;\n ctx.fillRect(0, y, size.width, l.height);\n }\n ctx.fillStyle = T.soft;\n ctx.fillRect(0, y + l.height - 1, size.width, 1);\n ctx.fillStyle = T.ink;\n ctx.font = `600 13px ${FONT}`;\n ctx.fillText(`${l.label} ${l.items.length}`, 16, y + 20, LANE_HEAD_W - 28);\n if (l.collapsed) continue;\n for (const p of l.items) drawItem(ctx, p, l, o, T, X0, y);\n }\n ctx.fillStyle = T.line;\n ctx.fillRect(LANE_HEAD_W - 1, TITLE_H, 1, size.height - TITLE_H - LEGEND_H);\n const legendY = size.height - LEGEND_H;\n if (o.today >= s.startDay && o.today < s.endDay) {\n ctx.fillStyle = brand.ember;\n ctx.fillRect(X0 + s.x(o.today) + s.dayWidth / 2 - 1, TITLE_H, 2, legendY - TITLE_H);\n }\n drawLegend(ctx, o, T, legendY, size.width);\n ctx.restore();\n}\n\n/** Status swatches, then Milestone and Today, along the bottom (board packs need a key). */\nfunction drawLegend(ctx: CanvasRenderingContext2D, o: TimelineExport, T: { surface: string; ink: string; muted: string; line: string }, y: number, width: number) {\n ctx.fillStyle = T.surface;\n ctx.fillRect(0, y, width, LEGEND_H);\n ctx.fillStyle = T.line;\n ctx.fillRect(0, y, width, 1);\n const seen = new Map<string, Appearance>();\n for (const l of o.lanes) for (const p of l.items) {\n const key = (p.item.status ?? '').trim();\n if (key && !seen.has(key)) seen.set(key, o.looks.get(p.id) ?? fallback(o.theme));\n }\n ctx.font = `400 12px ${FONT}`;\n ctx.textBaseline = 'middle';\n let x = 16;\n const cy = y + LEGEND_H / 2;\n for (const [label, look] of seen) {\n const color = canvasColor(ctx, look.color, '#2E54FF');\n roundRect(ctx, x, cy - 5, 16, 10, 3);\n if (look.style === 'outline') {\n ctx.setLineDash([3, 2]);\n ctx.strokeStyle = color;\n ctx.stroke();\n ctx.setLineDash([]);\n } else {\n ctx.save();\n if (look.style === 'done') ctx.globalAlpha = 0.3;\n ctx.fillStyle = color;\n ctx.fill();\n ctx.restore();\n }\n ctx.fillStyle = T.muted;\n ctx.fillText(label, x + 22, cy);\n x += 22 + ctx.measureText(label).width + 18;\n }\n ctx.save();\n ctx.translate(x + 5, cy);\n ctx.rotate(Math.PI / 4);\n ctx.fillStyle = T.ink;\n ctx.fillRect(-4, -4, 8, 8);\n ctx.restore();\n ctx.fillStyle = T.muted;\n ctx.fillText('Milestone', x + 16, cy);\n x += 16 + ctx.measureText('Milestone').width + 18;\n ctx.fillStyle = brand.ember;\n ctx.fillRect(x, cy - 6, 2, 12);\n ctx.fillStyle = T.muted;\n ctx.fillText('Today', x + 8, cy);\n}\n\nfunction drawItem(ctx: CanvasRenderingContext2D, p: Placed, l: Lane, o: TimelineExport, T: { surface: string; ink: string }, X0: number, laneY: number) {\n const s = o.scale;\n const look = o.looks.get(p.id) ?? fallback(o.theme);\n const color = canvasColor(ctx, look.color, '#2E54FF');\n const y = laneY + LANE_PAD + (l.row.get(p.id) ?? 0) * ROW_H;\n ctx.font = `500 12px ${FONT}`;\n if (p.milestone) {\n const cx = X0 + s.x(p.start) + s.dayWidth / 2;\n ctx.save();\n ctx.translate(cx, y + 13);\n ctx.rotate(Math.PI / 4);\n ctx.fillStyle = look.style === 'outline' ? T.surface : color;\n ctx.fillRect(-6, -6, 12, 12);\n ctx.strokeStyle = color;\n ctx.lineWidth = 2;\n ctx.strokeRect(-6, -6, 12, 12);\n ctx.restore();\n ctx.fillStyle = T.ink;\n ctx.fillText(p.item.name, cx + 12, y + 13.5);\n return;\n }\n const x0 = X0 + s.x(p.start);\n const w = Math.max(6, s.x(p.end + 1) - s.x(p.start));\n roundRect(ctx, x0, y + 4, w, ROW_H - 8, 6);\n if (look.style === 'outline') {\n ctx.fillStyle = T.surface;\n ctx.fill();\n ctx.setLineDash([4, 3]);\n ctx.strokeStyle = color;\n ctx.lineWidth = 1.5;\n ctx.stroke();\n ctx.setLineDash([]);\n } else if (look.style === 'done') {\n ctx.save();\n ctx.globalAlpha = 0.25;\n ctx.fillStyle = color;\n ctx.fill();\n ctx.restore();\n } else {\n ctx.fillStyle = color;\n ctx.fill();\n }\n const inside = w >= LABEL_INSIDE_MIN;\n ctx.save();\n if (inside) {\n ctx.beginPath();\n ctx.rect(x0 + 6, y, w - 10, ROW_H);\n ctx.clip();\n }\n ctx.fillStyle = inside && look.style === 'solid' ? canvasColor(ctx, look.text, '#fff') : T.ink;\n ctx.fillText(p.item.name, inside ? x0 + 8 : x0 + w + 6, y + 15.5, inside ? undefined : labelWidth(p.item.name) + 40);\n ctx.restore();\n}\n\n/** Draw and download. False when it's too big for one image. */\nexport function exportTimelinePng(o: TimelineExport, fileName: string, doc: Document = document): boolean {\n const size = timelinePngSize(o);\n if (!size) return false;\n const canvas = doc.createElement('canvas');\n canvas.width = Math.round(size.width * size.ratio);\n canvas.height = Math.round(size.height * size.ratio);\n const ctx = canvas.getContext('2d');\n if (!ctx) return false;\n drawTimeline(ctx, size, o);\n downloadCanvas(canvas, fileName, doc);\n return true;\n}\n","import { accentForText, darkTheme, lightTheme, themeVars, type ComponentTheme } from '@ninthforge/tokens';\nimport type { TimelineColors, TimelineProps } from './types';\n\n/**\n * Theme → CSS variables, the same rules as the Gantt: a custom accent also drives the focus\n * ring, tint and selection, and small text on the accent always reaches 4.5:1\n * (`--nf-accent-solid` is the accent shaded just enough when neither white nor ink passes).\n */\nexport function resolveTheme(theme: TimelineProps['theme'], accent?: string, colors?: Partial<TimelineColors>): Record<string, string> {\n const base = theme === 'dark' ? darkTheme : lightTheme;\n const overrides: Partial<ComponentTheme> = { ...(typeof theme === 'object' ? theme : {}), ...hostColors(colors) };\n const ownText = overrides.accentText;\n if (accent) {\n overrides.accent = accent;\n overrides.focus = overrides.focus ?? accent;\n const surface = overrides.surface ?? base.surface;\n const dark = theme === 'dark';\n overrides.accentTint = overrides.accentTint ?? `color-mix(in srgb, ${accent} ${dark ? 32 : 20}%, ${surface})`;\n overrides.selected = overrides.selected ?? `color-mix(in srgb, ${accent} ${dark ? 14 : 6}%, ${surface})`;\n }\n const finalAccent = overrides.accent ?? base.accent;\n const pair = ownText ? { background: finalAccent, text: ownText } : accentForText(finalAccent);\n overrides.accentText = pair.text;\n const vars: Record<string, string> = { ...themeVars(overrides, base), '--nf-accent-solid': pair.background };\n if (colors?.today) vars['--nf-now'] = colors.today;\n else if (colors?.foreground) vars['--nf-now'] = colors.foreground;\n return vars;\n}\n\n/** Host foreground and background (high contrast) as theme values: they replace every grey. */\nexport function hostColors(c?: Partial<TimelineColors>): Partial<ComponentTheme> {\n const o: Partial<ComponentTheme> = {};\n // lineSoft is also a background (segmented controls, hovers), so it takes the background.\n if (c?.background) o.surface = o.surfaceAlt = o.selected = o.lineSoft = o.tooltipInk = c.background;\n if (c?.foreground) o.ink = o.muted = o.line = o.tooltipBg = o.focus = c.foreground;\n return o;\n}\n\n/** The accent as a plain colour (for computing bar colours in JS). */\nexport function accentOf(theme: TimelineProps['theme'], accent?: string): string {\n if (accent) return accent;\n if (typeof theme === 'object' && theme.accent) return theme.accent;\n return (theme === 'dark' ? darkTheme : lightTheme).accent;\n}\n\n/** Plain colour values (not CSS variables) for print and PNG. Print passes `forceLight`: paper is white. */\nexport function palette(theme: TimelineProps['theme'], accent: string | undefined, forceLight: boolean): ComponentTheme {\n const base = theme === 'dark' && !forceLight ? darkTheme : lightTheme;\n const own: Partial<ComponentTheme> = typeof theme === 'object' && !forceLight ? theme : {};\n return { ...base, ...own, ...(accent ? { accent, accentText: own.accentText ?? accentForText(accent).text } : {}) };\n}\n","import { useEffect, useState } from 'react';\nimport { browserHost, verifyLicenses, type LicenseResult, type VerifyOptions } from '@ninthforge/license';\n\n/**\n * The options a component verifies with: its product and tenant, plus the page's host from\n * the shared `browserHost()`, so host-bound website demo keys work only on their own hosts.\n */\nexport function licenseOptions(opts: Pick<VerifyOptions, 'product' | 'tenantId'>): VerifyOptions {\n return { product: opts.product, tenantId: opts.tenantId, host: browserHost() };\n}\n\n/**\n * Licence state for a component. `key` may hold several keys (the licence-key environment\n * variable holds a tenant's Plan and Charts keys together). Starts as `null` (checking) so the watermark\n * never flashes for paying customers; verification is offline and async.\n */\nexport function useLicense(key: string | PromiseLike<string | undefined> | undefined, opts: Pick<VerifyOptions, 'product' | 'tenantId'>): LicenseResult | null {\n const [result, setResult] = useState<LicenseResult | null>(null);\n const { product, tenantId } = opts;\n useEffect(() => {\n let live = true;\n // A pending key (PCF reading the licence-key environment variable) keeps the state at\n // \"checking\", so no watermark flashes while it arrives.\n Promise.resolve(key)\n .catch(() => undefined)\n .then((k) => verifyLicenses(k, licenseOptions({ product, tenantId })))\n .then((r) => {\n if (live) setResult(r);\n });\n return () => {\n live = false;\n };\n }, [key, product, tenantId]);\n return result;\n}\n","import type { TimelineItem } from './types';\n\n/*\n * A sample roadmap (it mirrors the Dev harness, tools/dataverse/roadmap-harness.mjs): six themes,\n * Jul 2026 → Dec 2027 (a July fiscal year), overlaps that pack into rows, milestones, one-day\n * initiatives that are not milestones, dependencies and three items without dates.\n */\ntype Row = [name: string, lane: string, start: string | null, end: string | null, status: string, horizon: string, dependsOn?: string | null, milestone?: boolean];\n\nconst ROWS: Row[] = [\n ['Self-serve onboarding', 'Customer', '2026-07-06', '2026-09-25', 'Done', 'Now'],\n ['In-app help centre', 'Customer', '2026-08-17', '2026-11-27', 'On track', 'Now'],\n ['Customer portal v2', 'Customer', '2026-10-05', '2027-03-26', 'At risk', 'Now', 'Self-serve onboarding'],\n ['NPS programme', 'Customer', '2027-01-11', '2027-04-30', 'Not started', 'Next'],\n ['Mobile app beta', 'Customer', '2027-04-05', '2027-08-27', 'Not started', 'Next', 'Customer portal v2'],\n ['Portal launch', 'Customer', '2027-03-29', null, 'Not started', 'Next', 'Customer portal v2'],\n ['Loyalty scheme', 'Customer', '2027-08-02', '2027-12-17', 'Not started', 'Later'],\n ['Pricing review', 'Growth', '2026-07-13', '2026-08-28', 'Done', 'Now'],\n ['Partner programme', 'Growth', '2026-09-07', '2027-01-29', 'On track', 'Now'],\n ['APAC expansion', 'Growth', '2026-11-02', '2027-06-25', 'At risk', 'Now'],\n ['Marketplace listing', 'Growth', '2027-02-01', '2027-03-12', 'Not started', 'Next', 'Partner programme'],\n ['EMEA launch', 'Growth', '2027-07-05', '2027-12-10', 'Not started', 'Later', 'APAC expansion'],\n ['Board strategy day', 'Growth', '2026-11-20', '2026-11-20', 'On track', 'Now', null, false],\n ['FY27 plan sign-off', 'Growth', '2026-07-31', null, 'Done', 'Now'],\n ['Cloud migration', 'Platform', '2026-07-06', '2026-12-18', 'On track', 'Now'],\n ['API v3', 'Platform', '2026-09-14', '2027-02-26', 'Off track', 'Now'],\n ['Event bus', 'Platform', '2026-10-12', '2027-01-22', 'On track', 'Now', 'Cloud migration'],\n ['Legacy decommission', 'Platform', '2027-01-04', '2027-05-28', 'Not started', 'Next', 'Cloud migration'],\n ['Multi-region', 'Platform', '2027-05-03', '2027-10-29', 'Not started', 'Later', 'Legacy decommission'],\n ['Cut-over weekend', 'Platform', '2026-12-12', null, 'On track', 'Now', 'Cloud migration'],\n ['Platform review', 'Platform', '2027-02-15', '2027-02-15', 'Not started', 'Next', null, false],\n ['Data warehouse', 'Data', '2026-07-20', '2026-12-11', 'On track', 'Now'],\n ['Self-serve reporting', 'Data', '2026-10-19', '2027-03-19', 'At risk', 'Now', 'Data warehouse'],\n ['Data quality', 'Data', '2026-08-03', '2026-10-30', 'Done', 'Now'],\n ['ML forecasting', 'Data', '2027-02-01', '2027-07-30', 'Not started', 'Next', 'Data warehouse'],\n ['Data catalogue', 'Data', '2027-06-07', '2027-11-26', 'Not started', 'Later'],\n ['Warehouse live', 'Data', '2026-12-14', null, 'On track', 'Now', 'Data warehouse'],\n ['ISO 27001', 'Security', '2026-07-06', '2027-06-25', 'On track', 'Now'],\n ['SSO everywhere', 'Security', '2026-09-01', '2026-11-30', 'Off track', 'Now'],\n ['Pen test', 'Security', '2026-11-16', '2026-11-27', 'Not started', 'Now'],\n ['Zero trust network', 'Security', '2027-01-11', '2027-08-27', 'Not started', 'Next', 'SSO everywhere'],\n ['Certification audit', 'Security', '2027-06-28', null, 'Not started', 'Next', 'ISO 27001'],\n ['Security awareness', 'Security', '2027-03-01', '2027-04-30', 'Not started', 'Next'],\n ['New office fit-out', 'Operations', '2026-08-03', '2026-10-16', 'Done', 'Now'],\n ['ERP upgrade', 'Operations', '2026-10-05', '2027-04-30', 'At risk', 'Now'],\n ['Procurement portal', 'Operations', '2026-12-07', '2027-03-26', 'On track', 'Now'],\n ['Finance close automation', 'Operations', '2027-03-01', '2027-07-30', 'Not started', 'Next', 'ERP upgrade'],\n ['ERP go-live', 'Operations', '2027-05-03', null, 'Not started', 'Next', 'ERP upgrade'],\n ['Sustainability report', 'Operations', '2027-09-06', '2027-11-12', 'Not started', 'Later'],\n ['Year-end close', 'Operations', '2027-06-30', null, 'Not started', 'Next'],\n ['Offsite', 'Operations', '2027-02-24', '2027-02-24', 'Not started', 'Next', null, false],\n ['Voice assistant', 'Customer', null, null, 'Not started', 'Later'],\n ['Open data API', 'Data', null, null, 'Not started', 'Later'],\n ['Carbon tracking', 'Operations', null, null, 'Not started', 'Later']\n];\n\nexport const SAMPLE_LANES = ['Customer', 'Growth', 'Platform', 'Data', 'Security', 'Operations'];\n\nconst slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n\nexport function sampleRoadmap(): TimelineItem[] {\n return ROWS.map(([name, lane, start, end, status, horizon, dep, milestone]) => ({\n id: slug(name),\n name,\n lane,\n start: start ?? undefined,\n end: end ?? undefined,\n status,\n horizon,\n dependsOn: dep ? [slug(dep)] : undefined,\n milestone\n }));\n}\n"],"mappings":"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","names":["React","useEffect","useMemo","useRef","useState","brand","fallback","fallback","slug","ratio","next","e","next","matches","fallback","React","next","parts","clean","e","e","next","body","ratio","useEffect","useState","useState","useEffect","useMemo","useState","useRef","useEffect","items","e","next","mode","style"]}
|